增加微信小程序注册接口

This commit is contained in:
2025-07-27 12:49:30 +08:00
parent 892ce95919
commit 685d3ac646
9 changed files with 256 additions and 39 deletions

View File

@ -223,6 +223,11 @@
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
</dependencies>
<!-- Build Configuration -->

View File

@ -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<Boolean> 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<LoginResponse> wechatRegister(
@RequestBody WxMiniProgramRegRequest wxMiniProgramRegRequest) {
LoginResponse result = userService.wxMiniProgramRegister(wxMiniProgramRegRequest);
return Result.ok(result);
}

View File

@ -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;
}

View File

@ -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

View File

@ -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<BaseUserPO> 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<BaseUserPO> 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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