第三方账号绑定
概述
- 会员可以在个人中心 -> 账号绑定中绑定第三方账号,使用绑定的第三方账号可以进行登录操作
- 账号绑定功能暂时只支持在PC客户端使用
- 支持绑定的第三方账号包括:微信账号、QQ账号、微博账号和支付宝账号
- 绑定成功后,第三方账号和会员信息会进行关联,关联信息存放在es_connect表中
数据库设计
表名:信任登录关联信息表(es_connect)
字段名 | 类型与长度 | 备注 |
---|---|---|
id | bigint(20) | 主键ID |
member_id | bigint(20) | 会员ID |
union_id | bigint(20) | 第三方账号唯一标识 |
union_type | varchar(50) | 绑定类型 |
unbound_time | bigint(20) | 解绑时间 |
字段说明:
union_id:第三方账号唯一标识是根据不同的登录类型设置的,如下:
登录类型 union_id说明 微信 通过code获取access_token时获取的union_id,当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。 QQ 通过获取用户OpenID_OAuth2.0接口获取的用户ID,也就是openid 微博 通过code换取授权access_token时获取的微博用户uid 支付宝 通过换取授权访问令牌时获取的支付宝用户ID,也就是user_id union_type:绑定类型可参考枚举类ConnectTypeEnum,如下:
以下类型不仅包含账号绑定使用的,还有第三方账号信任登录时所使用的类型
public enum ConnectTypeEnum {
//QQ联合登录
QQ("QQ"),
//QQ H5 登陆openid
QQ_OPENID("QQ H5 登陆openid"),
//QQ APP 登陆openid
QQ_APP("QQ APP 登陆openid"),
//微博联合登录
WEIBO("微博联合登录"),
//微信联合登录
WECHAT("微信联合登录"),
// 微信小程序联合登录
WECHAT_MINI("微信小程序联合登录"),
//微信H5登录openid
WECHAT_OPENID("微信H5登录 openid"),
//微信APP登录openid
WECHAT_APP("微信APP登录 openid"),
//支付宝登录
ALIPAY("支付宝登录");
}
绑定流程
代码展示
API规划
获取第三方账号绑定信息
API地址: | https://{buyer-api-domain}/buyer/account-binder/list |
---|---|
请求方式: | GET |
请求示例:
GET: https://buyer-api.xxx.com/buyer/account-binder/list
返回结果为ConnectVO信息集合,格式如下:
union_type:绑定类型
is_bind:是否已绑定
[
{
"union_type":"QQ",
"is_bind":true
},
{
"union_type":"WEIBO",
"is_bind":false
},
{
"union_type":"WECHAT",
"is_bind":false
},
{
"union_type":"ALIPAY",
"is_bind":false
}
]
获取授权页面链接地址
API地址: | https://{buyer-api-domain}/buyer/account-binder/pc/{type} |
---|---|
请求方式: | GET |
请求参数说明:
type:QQ-QQ账号,WEIBO-微博账号,WECHAT-微信账号,ALIPAY-支付宝账号
请求示例:
GET: https://buyer-api.xxx.com/buyer/account-binder/pc/WECHAT
返回结果为授权页面链接,格式如下:
第三方账号解绑
API地址: | https://{buyer-api-domain}/buyer/account-binder/unbind/{type} |
---|---|
请求方式: | POST |
请求参数说明:
type:QQ-QQ账号,WEIBO-微博账号,WECHAT-微信账号,ALIPAY-支付宝账号
请求示例:
GET: https://buyer-api.xxx.com/buyer/account-binder/unbind/WECHAT
返回结果:无返回值
代码展示
Controller代码展示:
@Tag(name = "会员账号绑定API")
@RestController
@RequestMapping("/buyer/account-binder")
public class MemberConnectBuyerController {
@Operation(summary = "获取第三方账号绑定信息")
@GetMapping("/list")
public List<ConnectVO> get() {
return connectManager.get();
}
@GetMapping("/pc/{type}")
@Operation(summary = "发起账号绑定:获取授权页面链接地址")
@Parameters({
@Parameter(name = "type", description = "登录方式:QQ,微博,微信,支付宝",example = "QQ,WEIBO,WECHAT,ALIPAY", in=ParameterIn.PATH)
})
public String initiate(@PathVariable("type") @Parameter(hidden = true) String type) throws IOException {
//信任登录类型枚举类
ConnectTypeEnum connectTypeEnum = ConnectTypeEnum.valueOf(type);
//信任登录插件基类
AbstractConnectLoginPlugin connectionLogin = connectManager.getConnectionLogin(connectTypeEnum);
return connectionLogin.getLoginUrl(ClientTypeEnum.PC.name());
}
@Operation(summary = "账号解绑操作")
@PostMapping("/unbind/{type}")
@Parameter(name = "type", description = "登录方式:QQ,微博,微信,支付宝",example = "QQ,WEIBO,WECHAT,ALIPAY", in=ParameterIn.PATH)
public void unbind(@PathVariable("type") String type) {
if (ConnectTypeEnum.WECHAT.name().equals(type)) {
//PC端解绑操作后,清除微信保存相关信息
//解除绑定
connectManager.unbind(ConnectTypeEnum.WECHAT.value());
//解除绑定需要清空OPENID,否则微信服务消息在解除绑定之后会继续推送,并且分销提现会到账(在解绑之前的微信账号内) add by liuyulei 2020-12-22
connectManager.unbind(ConnectTypeEnum.WECHAT_OPENID.value());
//解除小程序自动登录
connectManager.unbind(ConnectTypeEnum.WECHAT_MINI.value());
} else {
connectManager.unbind(type);
}
}
}
回调方法代码展示:
@Override
@Transactional(value = "memberTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public MemberVO callBack(String type, String client, String mem, String uuid) {
//根据回调获取对应的插件,并获取相应的openid
ConnectTypeEnum connectTypeEnum = ConnectTypeEnum.valueOf(type);
AbstractConnectLoginPlugin connectionLogin = this.getConnectionLogin(connectTypeEnum);
debugger.log("调起插件:" + connectionLogin);
Auth2Token auth2Token = connectionLogin.loginCallback(client);
//根据openid查询是否有会员绑定过
QueryWrapper<ConnectDO> wrapper = new QueryWrapper<>();
wrapper.eq("union_id", auth2Token.getUnionid());
wrapper.eq("union_type", type);
wrapper.isNull("unbound_time");
ConnectDO connectDO = connectMapper.selectOne(wrapper);
debugger.log("获取绑定信息:" + connectDO);
Member member = null;
if (connectDO != null) {
member = memberManager.getModel(connectDO.getMemberId());
debugger.log("获取绑定的会员:" + member);
} else if (!"member".equals(mem)) {
//自动注册,当为member的时候,说明是会员中心的绑定,已经有了会员不需要再注册了
LoginUserDTO loginUserDTO = new LoginUserDTO();
loginUserDTO.setUuid(uuid);
loginUserDTO.setTokenOutTime(null);
loginUserDTO.setRefreshTokenOutTime(null);
loginUserDTO.setUnionType(connectTypeEnum);
loginUserDTO.setUnionid(auth2Token.getUnionid());
member = register(loginUserDTO);
}
//将信任登录的相关信息存入redis中
auth2Token.setType(type);
cache.put(CachePrefix.CONNECT_LOGIN.getPrefix() + uuid, auth2Token, javashopConfig.getCaptchaTimout());
this.logger.debug(new Date() + " " + uuid + " 登录授权,授权时间为" + javashopConfig.getCaptchaTimout());
//如果在会员中心绑定账号,不需要返回新的会员信息
if ("member".equals(mem)) {
return null;
}
MemberVO memberVO = null;
if (member != null) {
debugger.log("进行会员登录操作");
memberVO = memberManager.connectLoginHandle(member, uuid);
debugger.log("生成vo:");
debugger.log(memberVO.toString());
}
return memberVO;
}