(1)小程序端调用 wx.login方法获取用户登录凭证code,将code发送给小程序后台服务器;服务器调用登录凭证校验接口(需要传参appid+appsecret+code),进而换取用户登录态信息,包括用户的唯一标识(openid)及本次登录的会话密钥(session_key)等,将这些信息存入缓存中。
(2)点击button按钮触发获取微信手机号弹框!
(3)绑定微信账号,存在相应手机号账号时自动登录;不存在时自动创建

ios软件授权码弹窗源码 app授权码_ios软件授权码弹窗源码

1.App登录,如果没有绑定过,重新绑定,es_third_login插入一条记录,并且要存unionId字段
2.APP登录,如果之前绑定过,并且之前的es_third_login表没有存unionId,则更新原数据存入unionId
3.小程序登录,如果之前APP未授权登录,没有存入unionId,首次授权的时候,会在es_third_login插入一条记录
4.小程序登录,如果之前APP授权登录过,存过unionId,那么就直接登录,不会插入数据。

小程序和APP的unionId是相同的,小程序和APP微信联合登录,是通过unionId实现的。只要es_third_login表中同一个会员存在unionId就可以直接登录。

App登录: 调用 thirdLoginSubmit接口

小程序登录: 调用thirdLoginSubmit接口

授权登录:调用getUnionId接口获取到UnionId调用thirdLoginSubmit接口;

thirdLoginSubmit登录接口:

@Override
 public BaseResponse<Map<String,Object>> thirdLoginSubmit(@RequestBody ThirdLoginInfoRequest request){
 try {
 if(request==null){
 return MessageResponse.messageResponse("500","false","shop.requestParam.empty",null);
 }
 String deviceId=request.getDeviceId();
 String pluginId=request.getPluginId();
 String openId=request.getOpenId();
 String appType=request.getAppType();
 String unionId = request.getUnionId();
 ThirdLogin thirdLogin = thirdLoginService.find(pluginId, openId);
 ThirdLogin thirdLoginByUnionId = null;
 if (thirdLogin == null){
 thirdLoginByUnionId = thirdLoginService.findByUnionId(unionId);
 }else{
 thirdLoginByUnionId = thirdLogin;
 }
 HashMap<String, Object> retMap = new HashMap<String, Object>();
 if(thirdLoginByUnionId!=null){
 Member member = memberService.find(thirdLoginByUnionId.getMember());
 Map<String, Object> result = registerMemberUtil.loginCheck(member, RegisterMemberUtil.Type.app, deviceId, appType, request.getAppVersion());
 if("error".equals(result.get("type"))){
 return MessageResponse.messageResponse("500", "false", result.get("message").toString(), null);
 }else {
 member=(Member)result.get("member");
 }
 //老账户unionId为空的,存入unionId
 if(StringUtils.isBlank(thirdLoginByUnionId.getUnionId())){
 thirdLoginByUnionId.setUnionId(request.getUnionId());
 thirdLoginByUnionId.setLastModifiedDate(new Date());
 thirdLoginService.update(thirdLoginByUnionId);
 }
 VipDto vipDto = new VipDto();
 vipDto.setMessageId(UUID.randomUUID().toString().replace("-",""));
 vipDto.setMemberId(member.getId());
 vipDto.setAppType(member.getAppType());
 vipDto.setDeviceId(member.getDeviceId());
 vipDto.setMessageType(MessageType.login);
 loginProducer.send(GsonUtil.toJson(vipDto));
 ckToReceiptProducer.send(vipDto);
 retMap.put("root", "login");
 String appToken=JWTUtils.createAppToken(member.getId(),member.getPassword(),member.getDeviceId(),member.getAppLoginDate());
 if(StringUtils.isNotEmpty(appToken) && appToken.startsWith("CharlesKeith ")){
 RedisUtils.set(APP_TOKEN + member.getId(), appToken);
 appToken=appToken.replaceFirst("CharlesKeith ","");
 }
 retMap.put(Constants.TOKEN_PARAMETER_NAME, appToken);
 retMap.put(Constants.USER_ID, member.getId());
 retMap.put(Constants.USERNAME, member.getUsername());
 retMap.put(Constants.USERTYPE, member.getType().name());
 retMap.put(Constants.REGISTER_LOCATION, member.getRegisterLocation());
 retMap.put(Constants.MOBILE, member.getMobile());
 retMap.put("valid",valid(member.getId()));
 //添加购物车
 addCart(request,member);
 }else {
 retMap.put("root", "bindOrRegister");
 }
 return BaseResponse.successResponse(retMap);
 }catch (Exception e){
 e.printStackTrace();
 }
 return BaseResponse.errorResponse();
 }


首先从request当中获取到unionId,pluginId,openId,先根据pluginId,和openId查询第三方绑定信息,根据pluginId和openId查询到的第三方绑定信息为空则根据unionId查询第三方绑定信息

如果查询到的第三方绑定信息为空则返回一个map,封装"root":"bindOrRegister"信息给前端;

如果查询到的第三方绑定信息不为空则:从第三方绑定信息中获取到memberId查询会员信息,对会员信息进行登录校验,校验第三方登录信息是否为空,为空的话把request中的unionid,修改时间跟新保存第三方信息.

异步调用登录活动发劵,同步小票信息,封装"root","login"到map中生成token信息,把token和会员信息封装到map当中返回给前端,跟新购物车信息;

thirdBindOrRegister第三方登录或注册接口:

@Override
 public BaseResponse<Map<String,Object>> thirdBindOrRegister(@RequestBody ThirdLoginInfoRequest request){
 try {
 if(request==null){
 return MessageResponse.messageResponse("500","false","shop.parameter.notempty",null);
 }
 String mobile=request.getMobile();
 String captcha=request.getCaptcha();
 VerificationCodeRequest codeRequest=new VerificationCodeRequest();
 codeRequest.setPhoneNumber(mobile);
 VerificationCodeResponse codeResponse = null;
 BaseResponse<VerificationCodeResponse> verificationCode = smsClient.getVerificationCode(codeRequest);
 if(verificationCode!=null && "true".equals(verificationCode.getStatus())){
 codeResponse=verificationCode.getResult();
 }
 if(codeResponse==null || !codeResponse.getVerifyCode().equals(captcha)){
 return MessageResponse.messageResponse("500","false","shop.code.wrong",null);
 }
 String pluginId=request.getPluginId();
 String deviceId=request.getDeviceId();
 String openId=request.getOpenId();
 String appType=request.getAppType();
 String unionId = request.getUnionId();
 //小程序不需要传openId
 if(!"wx".equals(appType)){
 if(StringUtils.isBlank(pluginId) || StringUtils.isBlank(openId)){
 return MessageResponse.messageResponse("500","false","shop.common.invalid",null);
 }
 }
 Member member = memberService.findByMobile(mobile);
 if(member!=null){
 ThirdLogin thirdLogin = thirdLoginService.findByPluginId(pluginId, member.getId());
 if(Validator.isNotNullOrEmpty(thirdLogin) && Validator.isNotNullOrEmpty(thirdLogin.getOpenId()) && thirdLogin.getOpenId().equals(openId)){
 return MessageResponse.messageResponse("500","false","shop.thirdAccount.error",null);
 }
 Map<String, Object> checkResult = registerMemberUtil.loginCheck(member, RegisterMemberUtil.Type.app, appType, null,request.getAppVersion());
 if(checkResult.get("type").equals("error")){
 return MessageResponse.messageResponse("500","false",checkResult.get("message").toString(),null);
 }else{
 member=(Member) checkResult.get("member");
 }
 VipDto vipDto=new VipDto();
 vipDto.setMessageId(UUID.randomUUID().toString().replace("-",""));
 vipDto.setMemberId(member.getId());
 vipDto.setDeviceId(member.getDeviceId());
 vipDto.setAppType(member.getAppType());
 vipDto.setMessageType(MessageType.login);
 loginProducer.send(GsonUtil.toJson(vipDto));
 ckToReceiptProducer.send(vipDto);
 }else{
 // 注册操作(没有密码)
 member=new Member();
 member.setCreatedDate(new Date());
 member.setLastModifiedDate(new Date());
 member.setRegisterLocation(Constants.REGISTER_APP);
 member.setMemberRank(memberRankService.findDefault().getId());
 member.setAppLoginDate(new Date());
 member.setMobile(mobile);
 member.setDeviceId(deviceId);
 member.setAppType(appType);
 member.setType(MemberType.normal);
 member.setPoint(Long.valueOf(0L));
 member.setBalance(BigDecimal.ZERO);
 member.setAmount(BigDecimal.ZERO);
 member.setIsLocked(Boolean.valueOf(false));
 member.setIsEnabled(Boolean.valueOf(true));
 member.setLoginFailureCount(Integer.valueOf(0));
 member.setAppVersion(request.getAppVersion());
 Map<String,Object> checkResult = registerMemberUtil.registerCheck(member, RegisterMemberUtil.Type.app,null);
 if(checkResult.get("type").equals("error")){
 return MessageResponse.messageResponse("500","false",checkResult.get("message").toString(),null);
 }else {
 member = (Member) checkResult.get("member");
 }
 Member sourceMember = memberService.saveAndReturn(member);
 VipDto vipDto=new VipDto();
 vipDto.setMessageId(UUID.randomUUID().toString().replace("-",""));
 vipDto.setMemberId(sourceMember.getId());
 vipDto.setMessageType(MessageType.register);
 ckToReceiptProducer.send(vipDto);
 }
 ThirdLogin thirdLogin=new ThirdLogin();
 thirdLogin.setCreatedDate(new Date());
 thirdLogin.setLastModifiedDate(new Date());
 thirdLogin.setMember(member.getId());
 thirdLogin.setOpenId(openId);
 thirdLogin.setPluginId(pluginId);
 thirdLogin.setUnionId(unionId);
 thirdLoginService.save(thirdLogin);
 HashMap<String, Object> retMap = new HashMap<String, Object>();
 String appToken=JWTUtils.createAppToken(member.getId(),member.getPassword(),member.getDeviceId(),member.getAppLoginDate());
 if(StringUtils.isNotEmpty(appToken) && appToken.startsWith("CharlesKeith ")){
 RedisUtils.set(APP_TOKEN + member.getId(), appToken);
 appToken=appToken.replaceFirst("CharlesKeith ","");
 }
 retMap.put("root", "login");
 retMap.put(Constants.MOBILE, member.getMobile());
 retMap.put(Constants.USERTYPE, member.getType().name());
 retMap.put(Constants.TOKEN_PARAMETER_NAME, appToken);
 retMap.put(Constants.USER_ID, member.getId());
 retMap.put(Constants.USERNAME, member.getUsername());
 retMap.put(Constants.REGISTER_LOCATION, member.getRegisterLocation());
 retMap.put("valid",valid(member.getId()));
 //添加购物车
 addCart(request,member);
 return BaseResponse.successResponse(retMap);
 }catch (Exception e){
 e.printStackTrace();
 return BaseResponse.errorResponse();
 }
 }

当前端调用登录返回map为"root":"bindOrRegister"信息时调用接口

首先校验request信息不为空,从request中获取到手机号和验证码,对验证码进行校验。从request中获取到pluginId,deviceId,openId,appType,unioId,根据appType判断当不为微信小程序登录时候对pluginId.openId校验不能为空

根据手机号获取到会员信息,会员信息不为空时进行登录操作: 根据pluginId和会员Id查询到该会员的第三方绑定信息,把第三方中的openId与request获取的openId比较是否相同来判断是否已经绑定过该信息;

                                                                                                    对会员信息进行登录校验,异步调用活动发劵,小票同步信息, 根据会员信息和request的信息生成第三方绑定信息保存,生成token,返回"root":"login"和token信息和会员信息到Map中返回给前端跟新购物车信息

                                                  会员信息为空时进行注册操作:  根据request信息生成新会员信息,对会员信息进行注册校验保存会员信息,调用异步小票同步信息。根据会员信息和request的信息生成第三方绑定信息保存,生成token,返回"root":"login"和token信息和会员信息到Map中返回给前端更新购物车信息

以下逻辑是小程序授权登录

一、获取unionId
通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程
 
请求地址
 
GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
请求参数
属性    类型    默认值    必填    说明
appid    string        是    小程序 appId
secret    string        是    小程序 appSecret
js_code    string        是    登录时获取的 code
grant_type    string        是    授权类型,此处只需填写 authorization_code
返回值
Object
返回的 JSON 数据包

属性    类型    说明
openid    string    用户唯一标识
session_key    string    会话密钥
unionid    string    用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回,详见 UnionID 机制说明。
errcode    number    错误码
errmsg    string    错误信息
2.用户未关注公众号等相关主体账号时,通过code获取不到unionId时,则通过session_key、encryptedData、iv获取unionId

encryptedData    string    包括敏感数据在内的完整用户信息的加密数据
iv    string    加密算法的初始向量
接口如果涉及敏感数据(如wx.getUserInfo当中的 openId 和 unionId),接口的明文内容将不包含这些敏感数据。开发者如需要获取敏感数据,需要对接口返回的加密数据(encryptedData) 进行对称解密。 解密算法如下:

对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
对称解密的目标密文为 Base64_Decode(encryptedData)。
对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。
如接口 wx.getUserInfo 敏感数据当中的 watermark:

{
     "openId": "OPENID",
     "nickName": "NICKNAME",
     "gender": GENDER,
     "city": "CITY",
     "province": "PROVINCE",
     "country": "COUNTRY",
     "avatarUrl": "AVATARURL",
     "unionId": "UNIONID",
     "watermark":
     {
         "appid":"APPID",
         "timestamp":TIMESTAMP
     }
 }


a.首先通过code获取到session_key

b.通过session_key解密,从解密后的接送中提取unionId

二、获取session_key
小程序点击个人中心,首先会调thirdLogin/getSessionKey,后端通过传递的code获取到session_key保存到redis,用于后续获取手机号
session_key的过期时间通过小程序的checkSession检查,不需要后端做任何操作,获取到的session_key也不需要返还给小程序,只需自己保存即可
三、获取手机号
文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html

小程序调thirdLogin/getPhoneNumber,传给后端encryptedData,iv。后端通过之前redis保存的session_key解密数据
解密坑很多(前端必须将传递的数据通过encode处理,防止数据传递丢失,同时encryptedData,iv必须保证16个字节长度)
解密成功返给前端手机号,前端拿到手机号后通过手机号授权快速登录
获取得到的开放数据为以下 json 结构:

{
     "phoneNumber": "13580006666",
     "purePhoneNumber": "13580006666",
     "countryCode": "86",
     "watermark":
     {
         "appid":"APPID",
         "timestamp": TIMESTAMP
     }
 }


参数    类型    说明
phoneNumber    String    用户绑定的手机号(国外手机号会有区号)
purePhoneNumber    String    没有区号的手机号
countryCode    String    区号
四、小程序通过手机号快速授权登录
通过手机号查询会员(手机号为之前解密传给前端的手机号)
会员存在,直接将会员数据返回给小程序
会员不存在,直接注册新会员,并将数据返回给小程序(小程序快捷登录的不绑定第三方)
五、开发中的坑
前端传过来的code,通过后台请求微信接口的时候,code只能用一次,重复使用小程序报错msg:code been used, hints
获取手机号的时候,需要用到session_key,在刚开始通过code获取到session_key的时候,可以放到redis里,等下次用的时候直接在redis拿,切忌不可将session_key返回前端然后在获取手机号的时候通过前端传递
保存session_key的到redis时候,一定要记得redis的key要唯一,最开始我就是直接保存的,导致多个用户同时登录时候,只有一个能登录成功,其实是因为他们共用了同一个session_key。一定要加以区分,可以把openId传过来放入key中作为唯一约束
也许前端生成的加密数据和加密向量是正常的,直接复制给我们调微信接口可以获取到数据,但是在传递的时候可能会丢失字符,比如%等字符会解析成其他字符,可通过base64处理