跳到主要内容

第三方账号绑定

概述

  1. 会员可以在个人中心 -> 账号绑定中绑定第三方账号,使用绑定的第三方账号可以进行登录操作
  2. 账号绑定功能暂时只支持在PC客户端使用
  3. 支持绑定的第三方账号包括:微信账号、QQ账号、微博账号和支付宝账号
  4. 绑定成功后,第三方账号和会员信息会进行关联,关联信息存放在es_connect表中

数据库设计

表名:信任登录关联信息表(es_connect)

字段名类型与长度备注
idbigint(20)主键ID
member_idbigint(20)会员ID
union_idbigint(20)第三方账号唯一标识
union_typevarchar(50)绑定类型
unbound_timebigint(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("支付宝登录");
    }

绑定流程

image-20230807144201967

代码展示

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

返回结果为授权页面链接,格式如下:

类型页面链接格式
QQhttps://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=AppID&redirect_uri=RedirectUri&state=UUID
WEIBOhttps://api.weibo.com/oauth2/authorize?client_id=AppID&redirect_uri=RedirectUri&scope=all
WECHAThttps://open.weixin.qq.com/connect/qrconnect?appid=AppID&redirect_uri=RedirectUri&response_type=code&scope=snsapi_login&state=UUID#wechat_redirect
ALIPAYhttps://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=AppID&redirect_uri=RedirectUri&scope=auth_user&state=UUID

第三方账号解绑

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