一、统一登录时序图参考
二、微信一键登录实现
1、前端(ios/androd等)获取微信授权code、具体操作请参考微信开放文档
iOS 平台应用授权登录接入代码示例(请参考 iOS 接入指南):
-(void)sendAuthRequest
{
//构造 SendAuthReq 结构体
SendAuthReq* req =[[[SendAuthReq alloc]init]autorelease];
req.scope = @"snsapi_userinfo";
req.state = @"123";
//第三方向微信终端发送一个 SendAuthReq 消息结构
[WXApi sendReq:req];
}
Android 平台应用授权登录接入代码示例(请参考 Android 接入指南):
{
// send oauth request
Final SendAuth.Req req = new SendAuth.Req();
req.scope = "snsapi_userinfo";
req.state = "wechat_sdk_demo_test";
api.sendReq(req);
}
2、后台一键登录、绑定后台账号登录接口实现(accessTokenUrl参考地址:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code)
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ruoyi.common.result.CommonResult;
import com.ruoyi.growth.api.domain.wechat.WechatAccessTokenDO;
import com.ruoyi.growth.api.domain.wechat.WechatAccessTokenVO;
import com.ruoyi.growth.api.domain.wechat.WechatBangdingPhoneNODO;
import com.ruoyi.growth.core.entity.UserCommonEntity;
import com.ruoyi.growth.core.mapper.user.UserMapper;
import com.ruoyi.system.domain.vo.LoginVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* app调用微信一键登录 前端控制器
*/
@RestController
@Slf4j
@RequestMapping("/v1/wechat")
public class WechatAPPLoginController {
@Resource
WechatService wechatService;
@Resource
private UserMapper userMapper;
@Value("${wechat.accessTokenUrl}")
private String accessTokenUrl;
@Value("${wechat.appid}")
private String appid;
@Value("${wechat.secret}")
private String secret;
/**
* 微信一键登录
*/
@PostMapping(value = "/oneKeyLogin")
public CommonResult<WechatAccessTokenVO> oneKeyLogin(@RequestBody WechatAccessTokenDO wechatAccessTokenDO) {
WechatAccessTokenVO accessToken = null;
String requestUrl = accessTokenUrl.replace("APPID", appid)
.replace("SECRET", secret)
.replace("CODE", wechatAccessTokenDO.getCode());
String jsonString = HttpUtil.get(requestUrl);
if (StringUtils.isNotBlank(jsonString)) {
try {
accessToken = com.alibaba.fastjson.JSONObject.parseObject(jsonString, WechatAccessTokenVO.class);
} catch (JSONException e) {
// 获取token失败
log.error("获取token失败, jsonString:{}", jsonString);
return CommonResult.failed(jsonString);
}
}
//根据Unionid查询是否存在
LambdaQueryWrapper<UserCommonEntity> lambdaQueryWrapper = new LambdaQueryWrapper<UserCommonEntity>();
lambdaQueryWrapper.eq(UserCommonEntity::getUnionid, accessToken.getUnionid());
UserCommonEntity userCommonEntity = userMapper.selectOne(lambdaQueryWrapper);
//存在且有手机号做登录
if (!ObjectUtils.isEmpty(userCommonEntity) && !ObjectUtils.isEmpty(userCommonEntity.getPhonenumber())) {
CommonResult<LoginVO> loginVOCommonResult = wechatService.userLogin(userCommonEntity);
if (loginVOCommonResult.getCode() != 200) {
return CommonResult.failed("微信登录失败");
}
accessToken.setLoginVO(loginVOCommonResult.getData());
}
//不存在按微信返回参数返回
return CommonResult.success(accessToken);
}
/**
* 手机号码注册或绑定登录
*/
@PostMapping(value = "/phoneBangdingOrReg")
public CommonResult<LoginVO> phoneBangdingOrReg(@RequestBody WechatBangdingPhoneNODO wechatBangdingPhoneNODO) {
//根据Unionid查询是否存在
LambdaQueryWrapper<UserCommonEntity> lambdaQueryWrapper = new LambdaQueryWrapper<UserCommonEntity>();
lambdaQueryWrapper.eq(UserCommonEntity::getUnionid, wechatBangdingPhoneNODO.getUnionid());
UserCommonEntity userCommonEntity = userMapper.selectOne(lambdaQueryWrapper);
//存在则返回错误
if (ObjectUtils.isNotEmpty(userCommonEntity)) {
return CommonResult.failed("用户已存在不能重复绑定");
}
//不存在就查询绑定号码是否存在存在则直接绑定,不存在就注册
CommonResult<UserCommonEntity> userCommonEntityCommonResult = wechatService.userRegOrBangDing(wechatBangdingPhoneNODO);
if (ObjectUtils.isNotEmpty(userCommonEntityCommonResult) && userCommonEntityCommonResult.getCode() == 200) {
//登录
return wechatService.userLogin(userCommonEntityCommonResult.getData());
}
return CommonResult.failed(userCommonEntityCommonResult.getMessage());
}
}
二、小程序登录(和第三方微信登录一样先获取code、然后发起登录、登录为注册就注册后登录)
1、微信授权code获取
//res是请求成功返回的数据里面包括code码,通过凭证进而换取用户登录态信息
wx.login({
success(res) {
console.log(res);
})
2、后台一键登录、绑定后台账号登录接口实现(accessTokenUrl参考地址:https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code&appid=APPID&secret=SECRET&js_code=CODE)
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.gson.Gson;
import com.ruoyi.common.result.CommonResult;
import com.ruoyi.growth.api.domain.wechat.WechatAccessTokenDO;
import com.ruoyi.growth.api.domain.wechat.WechatAccessTokenVO;
import com.ruoyi.growth.api.domain.wechat.WechatBangdingPhoneNODO;
import com.ruoyi.growth.core.entity.UserCommonEntity;
import com.ruoyi.growth.core.mapper.user.UserMapper;
import com.ruoyi.system.domain.vo.LoginVO;
import com.ruoyi.system.domain.vo.WeChatPhoneVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 微信小程序 控制 前端控制器
*/
@RestController
@Slf4j
@RequestMapping("/v1/wechat/applet")
public class WechatAppletController {
@Resource
WechatService wechatService;
@Resource
private UserMapper userMapper;
@Value("${applet.accessTokenUrl}")
private String accessTokenUrl;
@Value("${applet.getUserPhoneNoAssessTokenUrl}")
private String getUserPhoneNoAssessTokenUrl;
@Value("${applet.getUserPhoneNoUrl}")
private String getUserPhoneNoUrl;
@Value("${applet.appid}")
private String appid;
@Value("${applet.secret}")
private String secret;
@Resource
RestTemplate restTemplate;
private String appletAccessToken = null;
/**
* 小程序一键登录
*/
@PostMapping(value = "/oneKeyLogin")
public CommonResult<WechatAccessTokenVO> oneKeyLogin(@RequestBody WechatAccessTokenDO wechatAccessTokenDO) {
WechatAccessTokenVO accessToken = null;
if (ObjectUtils.isEmpty(appletAccessToken)) {
String requestUrl = accessTokenUrl.replace("APPID", appid).replace("SECRET", secret).replace("CODE", wechatAccessTokenDO.getCode());
String jsonString = HttpUtil.get(requestUrl);
if (StringUtils.isNotBlank(jsonString)) {
try {
accessToken = JSONObject.parseObject(jsonString, WechatAccessTokenVO.class);
} catch (JSONException e) {
// 获取token失败
log.error("获取token失败, jsonString:{}", jsonString);
return CommonResult.failed(jsonString);
}
}
if (accessToken != null) {
appletAccessToken = accessToken.getAccessToken();
}
}
//根据Unionid查询是否存在
LambdaQueryWrapper<UserCommonEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(UserCommonEntity::getUnionid, accessToken.getUnionid());
UserCommonEntity userCommonEntity = userMapper.selectOne(lambdaQueryWrapper);
//存在且有手机号做登录
if (!ObjectUtils.isEmpty(userCommonEntity) && !ObjectUtils.isEmpty(userCommonEntity.getPhonenumber())) {
CommonResult<LoginVO> loginVOCommonResult = wechatService.userLogin(userCommonEntity);
if (loginVOCommonResult.getCode() != 200) {
return CommonResult.failed("微信登录失败");
}
accessToken.setLoginVO(loginVOCommonResult.getData());
}
//不存在按微信返回参数返回
return CommonResult.success(accessToken);
}
/**
* 手机号码注册或绑定登录
*/
@PostMapping(value = "/phoneBangdingOrReg")
public CommonResult<LoginVO> phoneBangdingOrReg(@RequestBody WechatBangdingPhoneNODO wechatBangdingPhoneNODO) {
//根据Unionid查询是否存在
LambdaQueryWrapper<UserCommonEntity> lambdaQueryWrapper = new LambdaQueryWrapper<UserCommonEntity>();
lambdaQueryWrapper.eq(UserCommonEntity::getUnionid, wechatBangdingPhoneNODO.getUnionid());
UserCommonEntity userCommonEntity = userMapper.selectOne(lambdaQueryWrapper);
//存在则返回错误
if (ObjectUtils.isNotEmpty(userCommonEntity)) {
return CommonResult.failed("用户已存在不能重复绑定");
}
//不存在就查询绑定号码是否存在存在则直接绑定,不存在就注册
CommonResult<UserCommonEntity> userCommonEntityCommonResult = wechatService.userRegOrBangDing(wechatBangdingPhoneNODO);
if (ObjectUtils.isNotEmpty(userCommonEntityCommonResult) && userCommonEntityCommonResult.getCode() == 200) {
//登录
return wechatService.userLogin(userCommonEntityCommonResult.getData());
}
return CommonResult.failed(userCommonEntityCommonResult.getMessage());
}
/**
* 获取用户手机号
*/
@PostMapping(value = "/getUserPhoneNo")
public CommonResult<WeChatPhoneVo> getUserPhoneNo(@RequestBody WechatAccessTokenDO wechatAccessTokenDO) {
// 获取token
if (ObjectUtils.isEmpty(appletAccessToken)) {
getUserPhoneNoAssessTokenUrl = getUserPhoneNoAssessTokenUrl.replace("APPID", appid).replace("SECRET", secret);
WechatAccessTokenVO accessToken = JSONObject.parseObject(HttpUtil.get(getUserPhoneNoAssessTokenUrl), WechatAccessTokenVO.class);
appletAccessToken = accessToken.getAccessToken();
}
// ACCESS_TOKEN要放在url参数链接上
getUserPhoneNoUrl = getUserPhoneNoUrl.replace("ACCESS_TOKEN", appletAccessToken);
Map<String, String> paramMap = new HashMap<>();
paramMap.put("code", wechatAccessTokenDO.getCode());
HttpHeaders headers = new HttpHeaders();
HttpEntity<Map<String, String>> httpEntity = new HttpEntity<>(paramMap, headers);
ResponseEntity<Object> response = restTemplate.postForEntity(getUserPhoneNoUrl, httpEntity, Object.class);
Gson gson = new Gson();
String s = gson.toJson(response.getBody());
WeChatPhoneVo weChatPhoneVo = gson.fromJson(s, WeChatPhoneVo.class);
return CommonResult.success(weChatPhoneVo);
}
/**
* 1个小时刷新一次
*/
@Scheduled(cron = "0 0 */1 * * ?")
private void flushAsstocken() {
// 获取token
if (ObjectUtils.isEmpty(appletAccessToken)) {
getUserPhoneNoAssessTokenUrl = getUserPhoneNoAssessTokenUrl.replace("APPID", appid).replace("SECRET", secret);
WechatAccessTokenVO accessToken = JSONObject.parseObject(HttpUtil.get(getUserPhoneNoAssessTokenUrl), WechatAccessTokenVO.class);
appletAccessToken = accessToken.getAccessToken();
}
}
}
三、备注:因为平台要做微信收取登录、小程序登录、公众号等登录,所有用户的微信唯一标识只能用Unionid,一个微信用户对应的微信各平台(小程序、公众号等)的openID是不一样的。还有就是各平台的appid、secret不通用,比如不能用小程序的appid去调用微信授权登录的accessTokenUrl(appid、secret、accessTokenUrl都不能夸平台互换),其他实体类参考:
import com.alibaba.fastjson.annotation.JSONField;
import com.ruoyi.system.domain.vo.LoginVO;
import lombok.Data;
@Data
public class WechatAccessTokenVO {
/**
* 获取微信信息所需的token
*/
@JSONField(name = "access_token")
private String accessToken;
/**
* 微信获取token的返回状态
*/
@JSONField(name = "expires_in")
private Integer expiresIn;
/**
* 授权用户唯一标识
*/
private String unionid;
private String openid;
/**
* 用户授权的作用域,使用逗号(,)分隔
*/
private String scope;
/**
* 微信一键登录成功后快乐成长平台给的合法token,为空就要走注册绑定流程
*/
private LoginVO loginVO;
}
import lombok.Data;
@Data
public class WeChatPhoneVo {
// 用户绑定的手机号(国外手机号会有区号)
private String phoneNumber;
// 没有区号的手机号
private String purePhoneNumber;
// 区号
private String countryCode;
// 数据水印
private String watermark;
}
# 微信APP一键登录
wechat:
accessTokenUrl: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
appid: wx************53
secret: 4************************e
# 微信小程序
applet:
appid: wx*************a
secret: a************************0
# 获取基本信息的assess_token
accessTokenUrl: https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code&appid=APPID&secret=SECRET&js_code=CODE
# 获取用户手机号的assess_token
getUserPhoneNoAssessTokenUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET
# 获取用户手机号
getUserPhoneNoUrl: https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESS_TOKEN