整个流程,
1.前端调用授权url 接口(创建一个重定向的请求方法,访问自动回调方法wechat.mp.callbackUrl的地址)。
2.微信自动回调方法里判断该用户是需要注册还是直接登录(如果直接登录就返回token)
是注册还是登录返回到配置文件中的 wechat.mp.url前端中转地址后面拼接上参数用来标识 是注册还是登录,比如是注册就在地址后拼接 action=register 如果是直接登录就返回action=jump后面在拼接上&token=值 &openId=值 &accountId=值,用重定向的方式
微信公众号后台配置地址:微信公众平台 (微信扫码就能登录进来)
1.配置文件
1.1 properties文件
# 微信公众平台授权
wechat.mp.clientId=wxb90a4c***f2de80
wechat.mp.secret=8e4380916bac1***d11a30e0cb7af68
//该回调地址需要在公众号后台配置
wechat.mp.callbackUrl=https://gw-dev.公司域名部分.com.cn/training/h5/social/callback
//这个是前端 中转页, (验证是否注册还是登录重定向到该页面,前端根据参数判断)
wechat.mp.url=https://gw-dev.公司域名部分.com.cn/training/doc.html#/home
1.2 java代码配置文件
1.2.1 WeChatMpProperty.class(自动注入配置)
package com.公司域名部分.training.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* @author
* @description
* @date 2022/12/1
*/
@Data
@ConfigurationProperties(prefix = "wechat.mp")
public class WeChatMpProperty {
private String clientId;
private String secret;
private String callbackUrl;
private String url;
private String token;
private String aesKey;
@Value("${useRedis:true}")
private Boolean useRedis;
private List<Msg> msgs;
@Data
public static class Msg {
private String title;
private String templateId;
}
}
1.2.2 WxMpConfiguration.class(公众号配置类)
package com.公司简写.training.config;
import com.公司简写.training.common.constants.RedisConstant;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* @author
* @description
* @date 2023/2/28
*/
@AllArgsConstructor
@Configuration
@EnableConfigurationProperties(WeChatMpProperty.class)
public class WxMpConfiguration {
private final WeChatMpProperty weChatMpProperty;
private final StringRedisTemplate redisTemplate;
@Bean
public WxMpService wxMpService() {
WxMpService service = new WxMpServiceImpl();
WxMpDefaultConfigImpl configStorage;
if(weChatMpProperty.getUseRedis()) {
configStorage = new WxMpRedisConfigImpl(new RedisTemplateWxRedisOps(redisTemplate), RedisConstant.REDIS_MP);
}else {
configStorage = new WxMpDefaultConfigImpl();
}
configStorage.setAppId(weChatMpProperty.getClientId());
configStorage.setSecret(weChatMpProperty.getSecret());
service.setWxMpConfigStorage(configStorage);
return service;
}
}
2. 生成授权url及微信回调方法
2.1 controller代码
package com.公司简写.training.controller.wechat.auth;
import com.公司简写.boot.controller.BaseController;
import com.公司简写.training.service.SocialUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import me.zhyd.oauth.model.AuthCallback;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
/**
* 公众号
* @author
* @date 2022/12/1
*/
@Api(tags = "h5-公众号")
@RestController("h5SocialController")
@RequiredArgsConstructor
@RequestMapping("/h5/social")
public class SocialController extends BaseController {
private final SocialUserService socialUserService;
@GetMapping("/render")
@ApiOperation(value = "生成授权url", notes = "生成授权url")
@ApiImplicitParams({
@ApiImplicitParam(name = "part", value = "前端跳转到页面的标识", required = false, dataType = "String"),
@ApiImplicitParam(name = "type", value = "1.老师 2.家长", required = true, dataType = "Integer")
})
public void renderAuth(@RequestParam(required = false) final String part,
@RequestParam final Integer type,
HttpServletResponse response) {
socialUserService.renderAuth(part,type, response);
}
/**
* 该方法是微信自动回调的方法
*/**
@RequestMapping("/callback")
@ApiOperation(value = "第三方授权登录", notes = "第三方授权登录")
public void socialCallback(@RequestParam(required = false) final String part,
@RequestParam final Integer type,
@SpringQueryMap AuthCallback callback,
HttpServletResponse response) {
socialUserService.socialCallback(part,type, callback, response);
}
}
serviceImpl方法
package com.公司简写.training.service.impl;
import com.公司简写.boot.service.impl.BaseServiceImpl;
import com.公司简写.cloud.authen.api.api.AuthTokenApi;
import com.公司简写.cloud.authen.api.dto.AuthTokenDTO;
import com.公司简写.cloud.authen.api.vo.AuthTokenVO;
import com.公司简写.cloud.usercenter.api.api.AuthenticationApi;
import com.公司简写.cloud.usercenter.api.dto.AuthSmsCodeDTO;
import com.公司简写.cloud.usercenter.api.enums.ExceptionEnum;
import com.公司简写.cloud.usercenter.api.vo.AuthSmsCodeVO;
import com.公司简写.framework.exception.InternalOutException;
import com.公司简写.framework.utils.base.ExceptionUtils;
import com.公司简写.training.api.dto.wechat.RegisterValidCodeDTO;
import com.公司简写.training.common.constants.AppCode;
import com.公司简写.training.common.enums.TerminalEnum;
import com.公司简写.training.common.model.TrainParent;
import com.公司简写.training.common.model.TrainTeacher;
import com.公司简写.training.common.vo.SocialVO;
import com.公司简写.training.config.WeChatMpProperty;
import com.公司简写.training.service.SocialUserService;
import com.公司简写.training.service.TrainParentService;
import com.公司简写.training.service.TrainTeacherService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.request.AuthWeChatMpRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.Objects;
/**
* * @description: 公众号登录
* * @author:
* * @create: 2023/6/26 14:50
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class SocialUserServiceImpl extends BaseServiceImpl implements SocialUserService {
private final TrainParentService parentService;
private final TrainTeacherService teacherService;
private final AuthTokenApi authTokenApi;
private final AuthenticationApi authenticationApi;
private final WeChatMpProperty weChatMpProperty;
@Value("${sms_template_id}")
private String sms_template_id;
//part=login.html type=1
//authorizeUrl:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxb90*****4f2de80&redirect_uri=https%3A%2F%2Fhy-test.cunwedu.com.cn%2Fh5%2Fsocial%2Fcallback%3Fpart%3Dlogin.html%26type%3D1&response_type=code&scope=snsapi_userinfo&
// state=56d5a46bb89e8cb1e02bdc0ff2b8ffd4#wechat_redirect
@Override
public void renderAuth(String part, Integer type, HttpServletResponse response) {
AuthRequest authRequest = getAuthRequest(part, type, response);
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
if (log.isDebugEnabled()) {
log.debug("微信网页授权获取用户基本信息链接:{}", authorizeUrl);
}
redirect(response, authorizeUrl);
}
//type是标识是老师还是家长授权
@Override
public void socialCallback(String part, Integer type, AuthCallback callback, HttpServletResponse response) {
log.debug("第三方回调入参: part=[{}] ,type=[{}]", part, type);
String accountId = null, account = null;
try {
AuthRequest authRequest = getAuthRequest(part, type, response);
AuthResponse<AuthUser> authResponse = authRequest.login(callback);
boolean isRegister = true;
if (authResponse.ok()) {
log.debug("第三方回调:第三方获取信息通过");
AuthUser authUser = authResponse.getData();
AuthToken authToken = authUser.getToken();
String openId;
String unionId = "";
if (Objects.nonNull(authToken)) {
openId = authToken.getOpenId();
unionId = authToken.getUnionId();
} else {
openId = authUser.getUuid();
}
String finalOpenId = openId;
if (type.equals(TerminalEnum.WECHAT_APPLET_TEACHER.getCode())) {
//老师
TrainTeacher teacher = teacherService.queryList(
q -> q.eq(TrainTeacher::getOpenId, finalOpenId))
.stream().findFirst().orElse(null);
if (Objects.nonNull(teacher)) {
isRegister = false;
//把帐号赋值 用于创建token
accountId = teacher.getUserId();
account = teacher.getUserAccount();
}
} else {
//家长端
TrainParent parent = parentService.queryList(q -> q.eq(TrainParent::getOpenId, finalOpenId))
.stream().findFirst().orElse(null);
if (Objects.nonNull(parent)) {
isRegister = false;
//把帐号赋值 用于创建token
accountId = parent.getUserId();
account = parent.getUserAccount();
}
}
if (isRegister) {
redirectRegister(response, openId, unionId, part, type);
return;
} else {
AuthTokenDTO dto = new AuthTokenDTO();
dto.setAppCode(AppCode.CODE);
dto.setUserId(accountId);
dto.setUserAccount(account);
AuthTokenVO token = validData(authTokenApi.create(dto));
SocialVO socialVO = new SocialVO();
socialVO.setAccessToken(token.getAccessToken());
socialVO.setAccountId(accountId);
socialVO.setOpenId(finalOpenId);
socialVO.setUnionId(unionId);
redirectLogin(response, socialVO, part, type);
return;
}
} else {
log.info("登录失败: 第三方授权失败Code {}, Msg {}", authResponse.getCode(), authResponse.getMsg());
}
} catch (Exception e) {
log.error("第三方登录异常:{}", ExceptionUtils.stackTraceText(e));
}
errorResponse(response);
}
/**
* 重定向到登录页
*
* @param response
* @param socialVO
* @param part
*/
private void redirectLogin(HttpServletResponse response, SocialVO socialVO, String part, Integer type) {
String url = weChatMpProperty.getUrl();
url += "?action=jump&token=" + socialVO.getAccessToken()
+ "&openId=" + socialVO.getOpenId()
+ "&accountId=" + socialVO.getAccountId();
String unionId = socialVO.getUnionId();
if (StringUtils.isNotEmpty(unionId)) {
url += "&unionId=" + unionId;
}
url = dealPart(response, part, url, "&part=", type);
log.debug("登录响应:跳转URL[{}]", url);
redirect(response, url);
}
/**
* 注册 response 处理
*
* @param response
* @param openId
* @param unionId
* @param part
*/
private void redirectRegister(HttpServletResponse response, String openId, String unionId, String part, Integer type) {
String url = weChatMpProperty.getUrl();
url += "?action=register&openId=" + openId;
if (StringUtils.isNotEmpty(unionId)) {
url += "&unionId=" + unionId;
}
url = dealPart(response, part, url, "&part=", type);
log.debug("注册响应:跳转URL[{}]", url);
redirect(response, url);
}
/**
* 重定向
*
* @param response
* @param url
*/
private void redirect(HttpServletResponse response, String url) {
try {
response.sendRedirect(url);
} catch (Exception e) {
throw new InternalOutException(ExceptionEnum.OFFICIAL_ERROR);
}
}
/**
* 创建 request
*
* @param part 前端跳转到页面的标识
* @param type 1.老师 2.家长
* @param response
* @return
*/
public AuthRequest getAuthRequest(String part, Integer type, HttpServletResponse response) {
String callbackUrl = weChatMpProperty.getCallbackUrl();
callbackUrl = dealPart(response, part, callbackUrl, "?part=", type);
return new AuthWeChatMpRequest(AuthConfig.builder()
.clientId(weChatMpProperty.getClientId())
.clientSecret(weChatMpProperty.getSecret())
.redirectUri(callbackUrl)
.build());
}
/**
* 处理 part
*
* @param response
* @param part
* @param url
* @param s
* @return
*/
private String dealPart(HttpServletResponse response, String part, String url, String s, Integer type) {
if (StringUtils.isNoneBlank(part)) {
try {
part = URLEncoder.encode(part, "UTF-8");
} catch (Exception e) {
log.info("创建request异常:{}", ExceptionUtils.stackTraceText(e));
errorResponse(response);
}
url += s + part + "&type=" + type;
}
return url;
}
/**
* 异常错误 response 处理
*
* @param response
*/
private void errorResponse(HttpServletResponse response) {
String url = weChatMpProperty.getUrl();
url += "?action=error";
log.debug("异常响应:跳转URL[{}]", url);
redirect(response, url);
}
}
3. 微信回调方法处理完就会重定向到前端,前端根据url 后面拼接的参数判断是注册还是登录,继续往下处理