From 685d3ac646f090b3b7b8eef5837a45457fd296bf Mon Sep 17 00:00:00 2001 From: zhangli <123879394@qq.com> Date: Sun, 27 Jul 2025 12:49:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BE=AE=E4=BF=A1=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E6=B3=A8=E5=86=8C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 + .../sczx/user/controller/AuthController.java | 12 +-- .../user/dto/WxMiniProgramRegRequest.java | 24 +++++ .../com/sczx/user/service/IUserService.java | 10 +- .../user/service/impl/UserServiceImpl.java | 98 ++++++++++++++----- .../dto/WechatDecryptedPhoneInfo.java | 20 ++++ .../dto/WechatDecryptedUserInfo.java | 21 ++++ .../dto/WechatEncryptedDataResponse.java | 14 +++ .../user/thirdpart/integ/WeichatInteg.java | 91 ++++++++++++++++- 9 files changed, 256 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/sczx/user/dto/WxMiniProgramRegRequest.java create mode 100644 src/main/java/com/sczx/user/thirdpart/dto/WechatDecryptedPhoneInfo.java create mode 100644 src/main/java/com/sczx/user/thirdpart/dto/WechatDecryptedUserInfo.java create mode 100644 src/main/java/com/sczx/user/thirdpart/dto/WechatEncryptedDataResponse.java diff --git a/pom.xml b/pom.xml index 7d1cbd1..bc703af 100644 --- a/pom.xml +++ b/pom.xml @@ -223,6 +223,11 @@ 1.2.83 + + org.bouncycastle + bcprov-jdk15on + 1.68 + diff --git a/src/main/java/com/sczx/user/controller/AuthController.java b/src/main/java/com/sczx/user/controller/AuthController.java index 241d9a4..6b99250 100644 --- a/src/main/java/com/sczx/user/controller/AuthController.java +++ b/src/main/java/com/sczx/user/controller/AuthController.java @@ -2,7 +2,7 @@ package com.sczx.user.controller; import com.sczx.user.common.Result; import com.sczx.user.dto.LoginResponse; -import com.sczx.user.dto.RegReq; +import com.sczx.user.dto.WxMiniProgramRegRequest; import com.sczx.user.service.IUserService; import com.sczx.user.util.JwtUtil; import io.swagger.annotations.Api; @@ -34,11 +34,11 @@ public class AuthController { return Result.ok(phoneNumber); } - @ApiOperation(value = "小程序注册", notes = "注册") - @PostMapping("/mini-program/register") - public Result wechatRegister( - @RequestBody RegReq regReq) { - Boolean result = userService.miniProgramRegister(regReq.getOpenId(), regReq.getNickName(), regReq.getPhoneNumber(), regReq.getAvatarUrl()); + @ApiOperation(value = "微信小程序注册", notes = "微信小程序注册") + @PostMapping("/mini-program/wechat/register") + public Result wechatRegister( + @RequestBody WxMiniProgramRegRequest wxMiniProgramRegRequest) { + LoginResponse result = userService.wxMiniProgramRegister(wxMiniProgramRegRequest); return Result.ok(result); } diff --git a/src/main/java/com/sczx/user/dto/WxMiniProgramRegRequest.java b/src/main/java/com/sczx/user/dto/WxMiniProgramRegRequest.java new file mode 100644 index 0000000..d83baaf --- /dev/null +++ b/src/main/java/com/sczx/user/dto/WxMiniProgramRegRequest.java @@ -0,0 +1,24 @@ +package com.sczx.user.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@ApiModel(value = "微信小程序注册请求") +@Data +public class WxMiniProgramRegRequest { + @ApiModelProperty(value = "微信code") + String code; + + @ApiModelProperty(value = "微信手机号加密数据") + String phoneEncryptedData; + + @ApiModelProperty(value = "微信手机号加密数据iv") + String phoneIv; + + @ApiModelProperty(value = "微信用户加密数据") + String userEncryptedData; + + @ApiModelProperty(value = "微信手机号加密数据iv") + String userIv; +} diff --git a/src/main/java/com/sczx/user/service/IUserService.java b/src/main/java/com/sczx/user/service/IUserService.java index 77ad33b..7615458 100644 --- a/src/main/java/com/sczx/user/service/IUserService.java +++ b/src/main/java/com/sczx/user/service/IUserService.java @@ -2,6 +2,7 @@ package com.sczx.user.service; import com.sczx.user.dto.LoginResponse; import com.sczx.user.dto.SimpleUserInfoDTO; +import com.sczx.user.dto.WxMiniProgramRegRequest; /** * @Author: 张黎 @@ -25,14 +26,11 @@ public interface IUserService { String getWxPhoneNumber(String phoneCode); /** - * 小程序注册 - * @param openId - * @param userName - * @param phoneNumber - * @param avatarUrl + * 微信小程序注册 + * @param wxMiniProgramRegRequest * @return */ - Boolean miniProgramRegister(String openId, String userName,String phoneNumber,String avatarUrl); + LoginResponse wxMiniProgramRegister(WxMiniProgramRegRequest wxMiniProgramRegRequest); /** * 微信小程序登录 * @param code 微信登录code diff --git a/src/main/java/com/sczx/user/service/impl/UserServiceImpl.java b/src/main/java/com/sczx/user/service/impl/UserServiceImpl.java index bc56284..950f3f8 100644 --- a/src/main/java/com/sczx/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/sczx/user/service/impl/UserServiceImpl.java @@ -6,20 +6,26 @@ import com.sczx.user.common.enums.MiniProgramTypeEnum; import com.sczx.user.convert.UserInfoConvert; import com.sczx.user.dto.LoginResponse; import com.sczx.user.dto.SimpleUserInfoDTO; +import com.sczx.user.dto.WxMiniProgramRegRequest; import com.sczx.user.exception.BizException; import com.sczx.user.po.BaseUserPO; import com.sczx.user.repository.BaseUserRepo; import com.sczx.user.service.IUserService; +import com.sczx.user.thirdpart.dto.WechatDecryptedPhoneInfo; +import com.sczx.user.thirdpart.dto.WechatDecryptedUserInfo; +import com.sczx.user.thirdpart.dto.WechatMiniProgramResponse; import com.sczx.user.thirdpart.integ.WeichatInteg; import com.sczx.user.util.JwtUtil; import com.sczx.user.util.RedisUtil; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; /** * @Author: 张黎 @@ -65,22 +71,57 @@ public class UserServiceImpl implements IUserService { } @Override - public Boolean miniProgramRegister(String openId, String nickName, String phoneNumber, String avatarUrl) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(BaseUserPO::getPhoneNumber, phoneNumber); - BaseUserPO baseUserPO = baseUserRepo.getOne(queryWrapper); - if(Objects.nonNull(baseUserPO)){ - throw new BizException("该手机号用户已注册"); + public LoginResponse wxMiniProgramRegister(WxMiniProgramRegRequest wxMiniProgramRegRequest) { + try{ + // 1. 通过code获取session_key和openid + WechatMiniProgramResponse sessionInfo = weichatInteg.getSessionInfoByCode(wxMiniProgramRegRequest.getCode()); + if (sessionInfo == null || sessionInfo.getOpenid() == null) { + throw new BizException("获取微信用户信息失败"); + } + + String openid = sessionInfo.getOpenid(); + String sessionKey = sessionInfo.getSession_key(); + + // 2. 解密手机号数据 + WechatDecryptedPhoneInfo phoneInfo = null; + if (StringUtils.isNotBlank(wxMiniProgramRegRequest.getPhoneEncryptedData()) && StringUtils.isNotBlank(wxMiniProgramRegRequest.getPhoneIv()) + && StringUtils.isNotBlank(sessionKey)) { + phoneInfo = weichatInteg.decryptPhoneNumber(sessionKey, wxMiniProgramRegRequest.getPhoneEncryptedData(), wxMiniProgramRegRequest.getPhoneIv()); + } + + // 3. 解密用户基本信息(昵称、头像等) + WechatDecryptedUserInfo userInfo = null; + if (StringUtils.isNotBlank(wxMiniProgramRegRequest.getUserEncryptedData()) && StringUtils.isNotBlank(wxMiniProgramRegRequest.getUserIv()) + && StringUtils.isNotBlank(sessionKey)) { + userInfo = weichatInteg.decryptUserInfo(sessionKey, wxMiniProgramRegRequest.getUserEncryptedData(), wxMiniProgramRegRequest.getUserIv()); + } + + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(BaseUserPO::getPhoneNumber, phoneInfo.getPurePhoneNumber()); + BaseUserPO baseUserPO = baseUserRepo.getOne(queryWrapper); + BaseUserPO newUserPO = new BaseUserPO(); + if(Objects.isNull(baseUserPO)){ + newUserPO.setWechatOpenid(openid); + newUserPO.setUserName(phoneInfo.getPurePhoneNumber()); + newUserPO.setPhoneNumber(phoneInfo.getPurePhoneNumber()); + newUserPO.setPassword(MD5Utils.md5Hex("88888888", "UTF-8")); + newUserPO.setNickName(Optional.ofNullable(userInfo).map(WechatDecryptedUserInfo::getNickName).orElse(null)); + newUserPO.setAvatarUrl(Optional.ofNullable(userInfo).map(WechatDecryptedUserInfo::getAvatarUrl).orElse(null)); + newUserPO.setRoleId(1); + }else { + newUserPO.setId(baseUserPO.getId()); + newUserPO.setWechatOpenid(openid); + newUserPO.setNickName(Optional.ofNullable(userInfo).map(WechatDecryptedUserInfo::getNickName).orElse(null)); + newUserPO.setAvatarUrl(Optional.ofNullable(userInfo).map(WechatDecryptedUserInfo::getAvatarUrl).orElse(null)); + } + baseUserRepo.saveOrUpdate(newUserPO); + + return getLoginResponse(openid,MiniProgramTypeEnum.WECHAT.getType()); + }catch (Exception e){ + log.error("微信小程序注册失败", e); + throw new BizException("微信小程序注册失败"); } - baseUserPO = new BaseUserPO(); - baseUserPO.setUserName(nickName); - baseUserPO.setNickName(nickName); - baseUserPO.setPhoneNumber(phoneNumber); - baseUserPO.setPassword(MD5Utils.md5Hex("88888888", "UTF-8")); - baseUserPO.setAvatarUrl(avatarUrl); - baseUserPO.setRoleId(1); - baseUserRepo.save(baseUserPO); - return true; } @Override @@ -89,15 +130,7 @@ public class UserServiceImpl implements IUserService { if (openid == null) { throw new BizException("无效的微信登录code"); } - // 模拟登录逻辑 - SimpleUserInfoDTO simpleUserInfoDTO = getUserInfoByProgramId(openid, MiniProgramTypeEnum.WECHAT.getType()); - - String token = jwtUtil.generateToken(simpleUserInfoDTO, simpleUserInfoDTO.getUserName()); - - LoginResponse loginResponse = new LoginResponse(); - loginResponse.setToken(token); - loginResponse.setUserInfo(simpleUserInfoDTO); - return loginResponse; + return getLoginResponse(openid,MiniProgramTypeEnum.WECHAT.getType()); } @@ -133,4 +166,21 @@ public class UserServiceImpl implements IUserService { BaseUserPO baseUserPO = baseUserRepo.getOne(queryWrapper); return UserInfoConvert.INSTANCE.poToSimpleDTO(baseUserPO); } + + /** + * 获取登录信息 + * @param programId + * @param programType + * @return + */ + private LoginResponse getLoginResponse(String programId, String programType) { + SimpleUserInfoDTO simpleUserInfoDTO = getUserInfoByProgramId(programId, programType); + + String token = jwtUtil.generateToken(simpleUserInfoDTO, simpleUserInfoDTO.getUserName()); + + LoginResponse loginResponse = new LoginResponse(); + loginResponse.setToken(token); + loginResponse.setUserInfo(simpleUserInfoDTO); + return loginResponse; + } } diff --git a/src/main/java/com/sczx/user/thirdpart/dto/WechatDecryptedPhoneInfo.java b/src/main/java/com/sczx/user/thirdpart/dto/WechatDecryptedPhoneInfo.java new file mode 100644 index 0000000..7c14656 --- /dev/null +++ b/src/main/java/com/sczx/user/thirdpart/dto/WechatDecryptedPhoneInfo.java @@ -0,0 +1,20 @@ +package com.sczx.user.thirdpart.dto; + +import lombok.Data; + +/** + * 微信解密手机信息 + */ +@Data +public class WechatDecryptedPhoneInfo { + private String phoneNumber; + private String purePhoneNumber; + private String countryCode; + private Watermark watermark; + + @Data + public static class Watermark { + private String appid; + private Long timestamp; + } +} diff --git a/src/main/java/com/sczx/user/thirdpart/dto/WechatDecryptedUserInfo.java b/src/main/java/com/sczx/user/thirdpart/dto/WechatDecryptedUserInfo.java new file mode 100644 index 0000000..8c4bb99 --- /dev/null +++ b/src/main/java/com/sczx/user/thirdpart/dto/WechatDecryptedUserInfo.java @@ -0,0 +1,21 @@ +package com.sczx.user.thirdpart.dto; + +import lombok.Data; + +@Data +public class WechatDecryptedUserInfo { + private String nickName; + private Integer gender; + private String language; + private String city; + private String province; + private String country; + private String avatarUrl; + private Watermark watermark; + + @Data + public static class Watermark { + private String appid; + private Long timestamp; + } +} diff --git a/src/main/java/com/sczx/user/thirdpart/dto/WechatEncryptedDataResponse.java b/src/main/java/com/sczx/user/thirdpart/dto/WechatEncryptedDataResponse.java new file mode 100644 index 0000000..9ec5daa --- /dev/null +++ b/src/main/java/com/sczx/user/thirdpart/dto/WechatEncryptedDataResponse.java @@ -0,0 +1,14 @@ +package com.sczx.user.thirdpart.dto; + +import lombok.Data; + +/** + * 微信小程序返回的加密数据 + */ +@Data +public class WechatEncryptedDataResponse { + private String encryptedData; + private String iv; + private String signature; + private String rawData; +} \ No newline at end of file diff --git a/src/main/java/com/sczx/user/thirdpart/integ/WeichatInteg.java b/src/main/java/com/sczx/user/thirdpart/integ/WeichatInteg.java index 99a1cf1..3a48bf6 100644 --- a/src/main/java/com/sczx/user/thirdpart/integ/WeichatInteg.java +++ b/src/main/java/com/sczx/user/thirdpart/integ/WeichatInteg.java @@ -1,6 +1,9 @@ package com.sczx.user.thirdpart.integ; import com.fasterxml.jackson.databind.ObjectMapper; +import com.sczx.user.exception.BizException; +import com.sczx.user.thirdpart.dto.WechatDecryptedPhoneInfo; +import com.sczx.user.thirdpart.dto.WechatDecryptedUserInfo; import com.sczx.user.thirdpart.dto.WechatMiniProgramResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -10,6 +13,10 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -42,7 +49,7 @@ public class WeichatInteg { return objectMapper.readValue(response, WechatMiniProgramResponse.class); } catch (Exception e) { log.error("获取微信session信息异常", e); - return null; + throw new BizException("获取微信session信息异常"); } } @@ -54,17 +61,95 @@ public class WeichatInteg { public String getWechatOpenIdByCode(String code) { WechatMiniProgramResponse response = getSessionInfoByCode(code); if (response == null) { - return null; + throw new BizException("获取微信openId信息异常"); } if (response.getErrcode() != null && response.getErrcode() != 0) { log.error("微信接口调用失败,错误码:{},错误信息:{}", response.getErrcode(), response.getErrmsg()); - return null; + throw new BizException("获取微信openId信息异常"); } return response.getOpenid(); } + /** + * 解密微信加密数据 + * @param sessionKey 会话密钥 + * @param encryptedData 加密数据 + * @param iv 初始化向量 + * @return 解密后的数据字符串 + */ + public String decryptWechatData(String sessionKey, String encryptedData, String iv) { + try { + byte[] sessionKeyBytes = Base64.getDecoder().decode(sessionKey); + byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedData); + byte[] ivBytes = Base64.getDecoder().decode(iv); + + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(sessionKeyBytes, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + byte[] result = cipher.doFinal(encryptedDataBytes); + String resultStr = new String(result); + + // 去除填充字符 + int paddingIndex = resultStr.indexOf("\0"); + if (paddingIndex > 0) { + resultStr = resultStr.substring(0, paddingIndex); + } + + return resultStr; + } catch (Exception e) { + log.error("解密微信数据异常", e); + throw new BizException("解密微信数据异常"); + } + } + + /** + * 解密手机号信息 + * @param sessionKey 会话密钥 + * @param encryptedData 加密的手机号数据 + * @param iv 初始化向量 + * @return 解密后的手机号信息 + */ + public WechatDecryptedPhoneInfo decryptPhoneNumber(String sessionKey, String encryptedData, String iv) { + try { + String decryptedJson = decryptWechatData(sessionKey, encryptedData, iv); + if (decryptedJson == null) { + return null; + } + + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(decryptedJson, WechatDecryptedPhoneInfo.class); + } catch (Exception e) { + log.error("解密手机号信息异常", e); + throw new BizException("解密手机号信息异常"); + } + } + + /** + * 解密用户信息(昵称、头像等) + * @param sessionKey 会话密钥 + * @param encryptedData 加密的用户数据 + * @param iv 初始化向量 + * @return 解密后的用户信息 + */ + public WechatDecryptedUserInfo decryptUserInfo(String sessionKey, String encryptedData, String iv) { + try { + String decryptedJson = decryptWechatData(sessionKey, encryptedData, iv); + if (decryptedJson == null) { + return null; + } + + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(decryptedJson, WechatDecryptedUserInfo.class); + } catch (Exception e) { + log.error("解密用户信息异常", e); + throw new BizException("解密用户信息异常"); + } + } + /** * 获取访问令牌(用于调用其他微信接口) * @return access_token