主頁 > 後端開發 > SpringCloud微服務實戰——搭建企業級開發框架(五十二):第三方登錄-微信小程式授權登錄流程設計和實作

SpringCloud微服務實戰——搭建企業級開發框架(五十二):第三方登錄-微信小程式授權登錄流程設計和實作

2023-03-25 07:34:50 後端開發

??在前面的設計和實作中,我們的微服務開發平臺通過JustAuth來實作第三方授權登錄,通過集成公共組件,著實減少了很多作業量,大多數的第三方登錄直接通過配置就可以實作,而在第三方授權登錄中,微信小程式授權登錄和APP微信授權登錄是兩種特殊的第三方授權登錄,
??JustAuth之所以能夠將多種第三方授權登錄服務整合在一起,抽象公共組件的原因是大多數的授權登錄服務器都是遵循OAuth2.0協議開發,雖然略有不同但可通過配接器進行轉換為統一介面,微信小程式授權登錄和APP的微信授權登錄也是OAutn2.0協議的授權登錄,但在對接的流程中不是完整的OAuth2.0對接流程,
??通常的第三方授權登錄程序中,獲取token的state和code是在回呼客戶端url中獲取的,而微信小程式授權登錄和APP的微信授權登錄獲取token的state和code是使用微信提供的特定方法獲取到的,然后通過微信傳給客戶端,客戶端拿到code之后到后臺取獲取openid等微信用戶資訊,然后,再進行系統登錄相關操作,

一、微信小程式授權登錄、注冊、系結流程設計說明

  • 微信小程式授權登錄、注冊、系結流程圖:

微信小程式登錄、注冊、系結流程

  • 微信小程式授權登錄、注冊、系結流程說明:
    1、用戶進入小程式,
    2、小程式前端通過從快取中獲取是否有token來判定用戶是否登錄,
    3、如果未登錄,那么跳轉到小程式登錄頁,
    4、小程式前端執行微信登錄方法wx.login獲取微信登錄的code(此時并未進行微信授權登錄),
    5、小程式前端通過code向業務后臺發送請求獲取用戶唯一的openid,
    6、業務系統根據openid或者unionid判斷該用戶是否系結了業務用戶,并將是否系結資訊回傳給前臺,
    7、如果沒有系結過,那么前端展示微信授權登錄按鈕,
    8、用戶點擊“授權登錄”按鈕之后,小程式前端會獲取到加密的用戶資訊,
    9、小程式前端將加密的用戶資訊傳到業務后臺進行解密,
    10、業務后臺收到加密用戶資訊后,通過請求微信服務器解密用戶資訊,并將用戶資訊存盤到業務系統表,
    11、后臺將解密后的用戶資訊(非私密資訊)回傳到小程式前臺,
    12、如果是沒有系結的,那么小程式前臺彈出是否獲取當前用戶手機號的彈出框,
    13、用戶選擇是否獲取微信系結的手機號來注冊或系結到業務系統的用戶,
    14、當用戶點擊統一獲取手機號時,微信會回傳加密后的手機號,然后前端將加密后的手機號發送到業務后臺解密,
    15、業務后臺獲取到手機號碼之后,會根據手機號碼在系統用戶表中進行匹配,如果匹配到用戶,那么直接回傳小程式用戶資訊,
    16、當用戶不同意獲取手機號時,那么小程式跳轉到輸入賬號密碼進行系結頁面,
    17、當系結操作執行成功之后,微信小程式呼叫第三方登錄獲取token方式,向業務后臺獲取token,
    18、用戶小程式授權登錄、注冊、系結成功,

二、微信小程式授權登錄、注冊、系結業務后臺功能實作

??微信通過其開放平臺提供小程式登錄功能介面,我們的業務服務可以通過小程式的登錄介面方便地獲取微信提供的用戶身份標識,進而將業務自身用戶體系和微信用戶相結合,從而更完美地在微信小程式中實作業務功能,
??微信小程式提供了對接登錄的SDK,我們只需要按照其官方檔案對接開發即可,同時也有很多開源組件將SDK再次進行封裝,在業務開發中可以更快速的集成小程式各個介面的呼叫,
??出于快速開發的原則,同時也少走彎路、少踩坑,我們可以選擇一款實作比較完善的組件進行微信小程式的對接,weixin-java-miniapp是集成微信小程式相關SDK操作的工具包,我們在專案中集成此工具包來實作微信小程式授權登錄,

1、引入weixin-java-miniapp相關maven依賴,目前發布版本為4.4.0正式版,

??一般在選擇開源工具包時,我們不會選擇最新版,而是選擇穩定版本,但是微信的開放介面經常變動,這里為了能夠兼容最新的微信小程式介面,我們在參考包的時候一定要選擇更新版本,否則會影響部分介面的呼叫,

......
    <properties>
......
        <!-- 微信小程式版本號 -->
        <weixin-java-miniapp.version>4.4.0</weixin-java-miniapp.version>
    </properties>
......
            <dependency>
                <groupId>com.github.binarywang</groupId>
                <artifactId>weixin-java-miniapp</artifactId>
                <version>${weixin-java-miniapp.version}</version>
            </dependency>
......

2、在組態檔application-dev.yml、application-test.yml、application-prod.yml中新增微信小程式需要的配置項,

??關于小程式如何注冊,appid和appsecret如何獲取,這里不展開講,微信開放平臺有詳細的說明檔案,

wx:
  miniapp:
    configs:
      - appid: #微信小程式appid
        secret: #微信小程式secret
        token: #微信小程式訊息服務器配置的token
        aesKey: #微信小程式訊息服務器配置的EncodingAESKey
        msgDataFormat: JSON
3、將weixin-java-miniapp配置類檔案WxMaConfiguration.java和WxMaProperties.java添加到我們的工程中,
  • WxMaConfiguration.java關鍵代碼
......
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {

    private List<Config> configs;

    @Data
    public static class Config {
        /**
         * 設定微信小程式的appid
         */
        private String appid;

        /**
         * 設定微信小程式的Secret
         */
        private String secret;

        /**
         * 設定微信小程式訊息服務器配置的token
         */
        private String token;

        /**
         * 設定微信小程式訊息服務器配置的EncodingAESKey
         */
        private String aesKey;

        /**
         * 訊息格式,XML或者JSON
         */
        private String msgDataFormat;
    }

}
......
  • WxMaProperties.java關鍵代碼
......
    private final WxMaProperties properties;

    @Autowired
    public WxMaConfiguration(WxMaProperties properties) {
        this.properties = properties;
    }

    @Bean
    public WxMaService wxMaService() {
        List<WxMaProperties.Config> configs = this.properties.getConfigs();
        if (configs == null) {
            throw new WxRuntimeException("配置錯誤!");
        }
        WxMaService maService = new WxMaServiceImpl();
        maService.setMultiConfigs(
            configs.stream()
                .map(a -> {
                    WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
                    config.setAppid(a.getAppid());
                    config.setSecret(a.getSecret());
                    config.setToken(a.getToken());
                    config.setAesKey(a.getAesKey());
                    config.setMsgDataFormat(a.getMsgDataFormat());
                    return config;
                }).collect(Collectors.toMap(WxMaDefaultConfigImpl::getAppid, a -> a, (o, n) -> o)));
        return maService;
    }
......
4、新建WxMaUserController.java用于實作微信小程式請求的相關介面,
  • 實作小程式登錄的login介面,此介面會根據微信小程式前端傳來的code進行獲取用戶session_key、openid/unionid,我們的業務系統會根據openid/unionid結合第三方登錄表進行用戶匹配,如果存在該用戶則回傳給小程式前臺已存在的用戶資訊;如果不存在,說明該用戶是第一次微信小程式登錄,那么我們將獲取到的微信唯一身份標識加密,并回傳微信小程式前臺進行下一步系結賬戶或注冊操作,
    /**
     * 登陸介面
     */
    @ApiOperation(value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/小程式登錄介面")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "code", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/小程式code", dataType="String", paramType = "query"),
    })
    @GetMapping("/login")
    public Result<?> login(@PathVariable String appid, String code) {
        
        if (StringUtils.isBlank(code)) {
            return Result.error("code 不能為空");
        }

        if (!wxMaService.switchover(appid)) {
            throw new IllegalArgumentException(String.format("未找到對應appid=[%s]的配置,請核實!", appid));
        }
    
        WeChatMiniAppLoginDTO weChatMiniAppLoginDTO = new WeChatMiniAppLoginDTO();
        try {
            WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(code);
            weChatMiniAppLoginDTO.setOpenid(session.getOpenid());
            weChatMiniAppLoginDTO.setUnionid(session.getUnionid());
            // 通過openId獲取在系統中是否是已經系結過的用戶,如果沒有系結,那么回傳到前臺,提示需要系結或者注冊用戶
            LambdaQueryWrapper<JustAuthSocial> socialLambdaQueryWrapper = new LambdaQueryWrapper<>();
            // 如果微信開通了開放平臺,那么各個渠道(小程式、公眾號等)都會有統一的unionid,如果沒開通,就僅僅使用openId
            if (StringUtils.isBlank(session.getUnionid()))
            {
                socialLambdaQueryWrapper.eq(JustAuthSocial::getOpenId, session.getOpenid())
                        .eq(JustAuthSocial::getSource, "WECHAT_MINI_APP");
            }
            else
            {
                socialLambdaQueryWrapper.eq(JustAuthSocial::getUnionId, session.getUnionid())
                        .and(e -> e.eq(JustAuthSocial::getSource, "WECHAT_MINI_APP")
                                .or().eq(JustAuthSocial::getSource, "WECHAT_OPEN")
                                .or().eq(JustAuthSocial::getSource, "WECHAT_MP")
                                .or().eq(JustAuthSocial::getSource, "WECHAT_ENTERPRISE")
                                .or().eq(JustAuthSocial::getSource, "WECHAT_APP"));
            }
            JustAuthSocial justAuthSocial = justAuthSocialService.getOne(socialLambdaQueryWrapper, false);
            if (null == justAuthSocial)
            {
                weChatMiniAppLoginDTO.setUserInfoAlready(false);
                weChatMiniAppLoginDTO.setUserBindAlready(false);
                justAuthSocial = new JustAuthSocial();
                justAuthSocial.setAccessCode(session.getSessionKey());
                justAuthSocial.setOpenId(session.getOpenid());
                justAuthSocial.setUnionId(session.getUnionid());
                justAuthSocial.setSource("WECHAT_MINI_APP");
                justAuthSocialService.save(justAuthSocial);
            } else {
                justAuthSocial.setAccessCode(session.getSessionKey());
                justAuthSocialService.updateById(justAuthSocial);
            }
    
            // 將socialId進行加密回傳,用于前端進行第三方登錄,獲取token
            DES des = new DES(Mode.CTS, Padding.PKCS5Padding, secretKey.getBytes(), secretKeySalt.getBytes());
            // 這里將source+uuid通過des加密作為key回傳到前臺
            String socialKey = "WECHAT_MINI_APP" + StrPool.UNDERLINE + (StringUtils.isBlank(session.getUnionid()) ? session.getOpenid() : session.getUnionid());
            // 將socialKey放入快取,默認有效期2個小時,如果2個小時未完成驗證,那么操作失效,重新獲取,在system:socialLoginExpiration配置
            redisTemplate.opsForValue().set(AuthConstant.SOCIAL_VALIDATION_PREFIX + socialKey, String.valueOf(justAuthSocial.getId()), socialLoginExpiration,
                    TimeUnit.SECONDS);
            String desSocialKey = des.encryptHex(socialKey);
            weChatMiniAppLoginDTO.setBindKey(desSocialKey);
            
            // 查詢是否系結用戶
            // 判斷此第三方用戶是否被系結到系統用戶
            Result<Object> bindResult = justAuthService.userBindQuery(justAuthSocial.getId());
            if(null != bindResult && null != bindResult.getData() && bindResult.isSuccess())
            {
                weChatMiniAppLoginDTO.setUserInfoAlready(true);
                weChatMiniAppLoginDTO.setUserBindAlready(true);
            } else {
                // 這里需要處理回傳訊息,前端需要根據回傳是否已經系結好的訊息來判斷
                weChatMiniAppLoginDTO.setUserInfoAlready(false);
                weChatMiniAppLoginDTO.setUserBindAlready(false);
            }
            return Result.data(weChatMiniAppLoginDTO);
        } catch (WxErrorException e) {
            log.error(e.getMessage(), e);
            return Result.error("小程式登錄失敗:" + e);
        } finally {
            WxMaConfigHolder.remove();//清理ThreadLocal
        }
    }
  • 實作獲取/解密微信小程式用戶資訊的info介面
    ??當微信小程式前端獲取到用戶授權可以獲取用戶資訊時,微信小程式前端將加密的用戶資訊發送到業務后臺,業務后臺請求微信服務器將用戶資訊解密并保存到我們的第三方用戶登錄表內,
    /**
     * 獲取用戶資訊介面
     */
    @ApiOperation(value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/小程式獲取用戶資訊介面")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "socialKey", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/加密的登錄key,用于系結用戶", required = true, dataType="String", paramType = "query"),
            @ApiImplicitParam(name = "signature", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/使用 sha1( rawData + sessionkey ) 得到字串,用于校驗用戶資訊", required = true, dataType="String", paramType = "query"),
            @ApiImplicitParam(name = "rawData", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/不包括敏感資訊的原始資料字串,用于計算簽名", required = true, dataType="String", paramType = "query"),
            @ApiImplicitParam(name = "encryptedData", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/包括敏感資料在內的完整用戶資訊的加密資料", required = true, dataType="String", paramType = "query"),
            @ApiImplicitParam(name = "iv", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/加密演算法的初始向量", required = true, dataType="String", paramType = "query")
    })
    @GetMapping("/info")
    public Result<?> info(@PathVariable String appid, String socialKey,
                       String signature, String rawData, String encryptedData, String iv) {
        if (!wxMaService.switchover(appid)) {
            throw new IllegalArgumentException(String.format("未找到對應appid=[%s]的配置,請核實!", appid));
        }
    
        // 查詢第三方用戶資訊
        JustAuthSocial justAuthSocial = this.getJustAuthSocial(socialKey);
        if (StringUtils.isBlank(justAuthSocial.getAccessCode()))
        {
            throw new BusinessException("登錄狀態失效,請嘗試重新進入小程式");
        }
        // 用戶資訊校驗
        if (!wxMaService.getUserService().checkUserInfo(justAuthSocial.getAccessCode(), rawData, signature)) {
            WxMaConfigHolder.remove();//清理ThreadLocal
            return Result.error("user check failed");
        }

        // 解密用戶資訊
        WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(justAuthSocial.getAccessCode(), encryptedData, iv);
        WxMaConfigHolder.remove();//清理ThreadLocal
        justAuthSocial.setAvatar(userInfo.getAvatarUrl());
        justAuthSocial.setUnionId(userInfo.getUnionId());
        justAuthSocial.setNickname(userInfo.getNickName());
        justAuthSocialService.updateById(justAuthSocial);
        return Result.data(userInfo);
    }
  • 當在小程式前端系結或注冊賬號時,可以在用戶允許的前提下獲取微信用戶的手機號,同樣,手機號和用戶資訊都是需要傳到業務后臺,然后再請求微信服務器進行解密,獲取到手機號之后,業務后臺根據手機號和系統用戶進行匹配,如果存在,那么直接將微信賬號系結到我們業務系統的當前手機號用戶,如果不存在,那么回傳微信小程式不存在系結用戶的資訊,由小程式前端繼續進行系結或者注冊操作,
    /**
     * 獲取用戶系結手機號資訊
     */
    @ApiOperation(value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/小程式獲取用戶系結手機號資訊")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "socialKey", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/加密的登錄key,用于系結用戶", required = true, dataType="String", paramType = "query"),
            @ApiImplicitParam(name = "encryptedData", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/包括敏感資料在內的完整用戶資訊的加密資料", required = true, dataType="String", paramType = "query"),
            @ApiImplicitParam(name = "iv", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/加密演算法的初始向量", required = true, dataType="String", paramType = "query")
    })
    @GetMapping("/phone")
    public Result<?> phone(@PathVariable String appid, String socialKey, String encryptedData, String iv) {
        if (!wxMaService.switchover(appid)) {
            throw new IllegalArgumentException(String.format("未找到對應appid=[%s]的配置,請核實!", appid));
        }
        // 查詢第三方用戶資訊
        JustAuthSocial justAuthSocial = this.getJustAuthSocial(socialKey);
        if (StringUtils.isBlank(justAuthSocial.getAccessCode()))
        {
            throw new BusinessException("登錄狀態失效,請嘗試重新進入小程式");
        }

        // 解密
        WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(justAuthSocial.getAccessCode(), encryptedData, iv);
        WxMaConfigHolder.remove();//清理ThreadLocal
        
        // 不帶區號的手機,國外的手機會帶區號
        String phoneNumber = phoneNoInfo.getPurePhoneNumber();
        // 查詢用戶是否存在,如果存在,那么直接呼叫系結介面
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getMobile, phoneNumber);
        User userInfo = userService.getOne(lambdaQueryWrapper);
        Long userId;
        // 判斷回傳資訊
        if (null != userInfo && null != userInfo.getId()) {
            userId = userInfo.getId();
        }
        else {
            // 如果用戶不存在,那么呼叫新建用戶介面,并系結
            CreateUserDTO createUserDTO = new CreateUserDTO();
            createUserDTO.setAccount(phoneNumber);
            createUserDTO.setMobile(phoneNumber);
            createUserDTO.setNickname(StringUtils.isBlank(justAuthSocial.getNickname()) ? phoneNumber : justAuthSocial.getNickname());
            createUserDTO.setPassword(StringUtils.isBlank(justAuthSocial.getUnionId()) ? justAuthSocial.getOpenId() : justAuthSocial.getUnionId());
            createUserDTO.setStatus(GitEggConstant.UserStatus.ENABLE);
            createUserDTO.setAvatar(justAuthSocial.getAvatar());
            createUserDTO.setEmail(justAuthSocial.getEmail());
            createUserDTO.setStreet(justAuthSocial.getLocation());
            createUserDTO.setComments(justAuthSocial.getRemark());
            CreateUserDTO resultUserAdd = userService.createUser(createUserDTO);
            if (null != resultUserAdd && null != resultUserAdd.getId()) {
                userId = resultUserAdd.getId();
            } else {
                // 如果添加失敗,則回傳失敗資訊
                return Result.data(resultUserAdd);
            }
        }
        // 執行系結操作
        justAuthService.userBind(justAuthSocial.getId(), userId);
        return Result.success("賬號系結成功");
    }
  • 提供系結當前登錄賬號介面,由微信小程式前端進行呼叫系結
    /**
     * 系結當前登錄賬號
     */
    @ApiOperation(value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/系結當前登錄賬號")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "socialKey", value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/加密的登錄key,用于系結用戶", required = true, dataType="String", paramType = "query")
    })
    @GetMapping("/bind")
    public Result<?> bind(@PathVariable String appid, @NotBlank String socialKey, @CurrentUser GitEggUser user) {
        if (!wxMaService.switchover(appid)) {
            throw new IllegalArgumentException(String.format("未找到對應appid=[%s]的配置,請核實!", appid));
        }
        if (null == user || (null != user && null == user.getId())) {
            throw new BusinessException("用戶未登錄");
        }
        // 查詢第三方用戶資訊
        JustAuthSocial justAuthSocial = this.getJustAuthSocial(socialKey);
        if (StringUtils.isBlank(justAuthSocial.getAccessCode()))
        {
            throw new BusinessException("賬號系結失敗,請嘗試重新進入小程式");
        }
        // 執行系結操作
        justAuthService.userBind(justAuthSocial.getId(), user.getId());
        return Result.success("賬號系結成功");
    }
  • 提供解綁介面,除了系結介面外,我們系統服務應提供微信小程式解綁功能,這里實作解綁介面
    /**
     * 解綁當前登錄賬號
     */
    @ApiOperation(value = "https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/解綁當前登錄賬號")
    @GetMapping("/unbind")
    public Result<?> unbind(@PathVariable String appid, @CurrentUser GitEggUser user) {
        if (!wxMaService.switchover(appid)) {
            throw new IllegalArgumentException(String.format("未找到對應appid=[%s]的配置,請核實!", appid));
        }
        if (null == user || (null != user && null == user.getId())) {
            throw new BusinessException("用戶未登錄");
        }
        LambdaQueryWrapper<JustAuthSocialUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(JustAuthSocialUser::getUserId, user.getId());
        justAuthSocialUserService.remove(queryWrapper);
        return Result.success("賬號解綁成功");
    }

??通過以上介面的功能,基本實作了微信小程式前端進行系結、注冊以及獲取用戶資訊、用戶手機號所需要的介面,下面來實作小程式前端具體的業務實作,

三、微信小程式授權登錄、注冊、系結小程式前端功能實作,

??微信小程式前端開發有多種方式,可以使用微信小程式官方開發方式,也可以使用第三方的開發方式,因為大多數前端都會使用Vue.js開發,而mpvue可以使用開發Vue.js的方式來開發微信小程式,所以這里我們選擇使用mpvue來開發微信小程式,這里不詳細講解mpvue框架的搭建程序,只詳細說明微信小程式授權登錄相關功能,有需要的可以參考mpvue官方檔案,

1、定義微信小程式授權登錄相關介面檔案login.js,將我們業務后臺實作的介面統一管理和呼叫,

??因為我們的開發框架是支持多租戶的,同時也是支持多個小程式的,為了同一套后臺可以支持多個微信小程式,這里選擇在發布的微信小程式中配置appId,由微信小程式前端引數來確定具體的微信小程式,

import fly from '@/utils/requestWx'

// 獲取用戶資訊
export function getOpenId (params) {
  return fly.get(`/wx/user/${params.appId}/login`, params)
}

// 獲取用戶資訊
export function getUserInfo (params) {
  return fly.get(`/wx/user/${params.appId}/info`, params)
}

// 獲取用戶手機號
export function getUserPhone (params) {
  return fly.get(`/wx/user/${params.appId}/phone`, params)
}

// 系結微信賬號
export function bindWeChatUser (params) {
  return fly.get(`/wx/user/${params.appId}/bind`, params)
}

// 解綁微信賬號
export function unbindWeChatUser (params) {
  return fly.get(`/wx/user/${params.appId}/unbind`)
}

// 登錄
export function postToken (params) {
  return fly.post(`/oauth/token`, params)
}

// 退出登錄
export function logout () {
  return fly.post(`/oauth/logout`)
}

// 獲取登錄用戶資訊
export function getLoginUserInfo () {
  return fly.get(`/system/account/user/info`)
}

2、新增login.vue授權登錄頁面,實作微信小程式具體授權登錄、系結、注冊的操作界面,
  • 增加微信授權登錄按鈕
    <div  v-show="!showAccountLogin">
      <van-button color="#1aad19"  block open-type="getUserInfo" @getuserinfo="bindGetUserInfo" >微信授權登錄</van-button>
    </div>
  • 增加微信小程式獲取微信用戶系結手機號的彈出框,應微信官方要求,必須由用戶點擊授權之后,才能夠獲取用戶系結的手機號,
    <van-dialog 
      title="授權驗證"
      @getphonenumber="bindGetUserPhone"
      confirm-button-open-type="getPhoneNumber"
      message="微信一鍵登錄需要系結您的手機號"
      :show="showUserPhoneVisible">
    </van-dialog>
  • 增加賬號系結彈出框,同樣微信官方要求,獲取微信用戶資訊,必須由用戶點擊授權允許,所以這里彈出框里的按鈕也是微信用戶同意授權獲取用戶資訊的按鈕,
    <van-dialog 
      title="賬號系結"
      @getuserinfo="bindUserInfo"
      confirm-button-open-type="getUserInfo"
      message="登錄成功,是否將賬號系結到當前微信?"
      :show="showUserInfoVisible">
    </van-dialog>
3、在methods中新增微信系結相關方法來具體實作微信小程式授權登錄、系結、注冊等操作介面的呼叫,
  • 通過wx.login拿到code,在后臺通過openId判斷是否已經系結用戶,如果已系結用戶,則不需要再進行用戶授權操作,直接登錄.
wxLogin () {
      var that = this
      wx.login({
        success (res) {
          that.code = res.code
          const params = {
            appId: appId,
            code: res.code
          }
          getOpenId(params).then(res => {
            if (res.code === 200 && res.data) {
              const result = res.data
              mpvue.setStorageSync('openid', result.openid)
              mpvue.setStorageSync('unionid', result.unionid)
              mpvue.setStorageSync('bindKey', result.bindKey)
              mpvue.setStorageSync('userBindAlready', result.userBindAlready)
              // 1、如果系結過,那么直接使用系結用戶登錄
              // 2、如果沒有系結過,那彈出獲取用戶資訊和獲取手機號資訊進行系結
              if (result.userBindAlready) {
                const loginParams = {
                  grant_type: 'social',
                  social_key: mpvue.getStorageSync('bindKey')
                }
                postToken(loginParams).then(res => {
                  if (res.code === 200) {
                    console.log(res)
                    const data = https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/res.data
                    mpvue.setStorageSync('token', data.token)
                    mpvue.setStorageSync('refreshToken', data.refreshToken)
                    // 獲取用戶資訊
                    that.loginSuccess()
                  } else {
                    Toast(res.msg)
                  }
                })
              }
            } else {
              Toast(res.msg)
            }
          })
        }
      })
    },
  • 獲取微信用戶資訊實作登陸,微信小程式介面,只允許點擊按鈕,用戶同意后才能獲取用戶資訊,不要直接使用wx.getUserInfo,此介面已過期,微信不再支持,
    bindGetUserInfo: function (res) {
      var that = this
      if (res.mp.detail.errMsg === 'getUserInfo:ok') {
        const userParams = {
          appId: appId,
          socialKey: mpvue.getStorageSync('bindKey'),
          signature: res.mp.detail.signature,
          rawData: res.mp.detail.rawData,
          encryptedData: res.mp.detail.encryptedData,
          iv: res.mp.detail.iv
        }
        getUserInfo(userParams).then(response => {
          const userBindAlready = mpvue.getStorageSync('userBindAlready')
          // 1、如果系結過,那么直接使用系結用戶登錄
          // 2、如果沒有系結過,那彈出獲取用戶資訊和獲取手機號資訊進行系結
          if (userBindAlready) {
            const loginParams = {
              grant_type: 'social',
              social_key: mpvue.getStorageSync('bindKey')
            }
            postToken(loginParams).then(res => {
              if (res.code === 200) {
                console.log(res)
                const data = https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/res.data
                mpvue.setStorageSync('token', data.token)
                mpvue.setStorageSync('refreshToken', data.refreshToken)
                // 獲取用戶資訊
                that.loginSuccess()
              } else {
                // 彈出獲取手機號授權按鈕
                that.showUserPhoneVisible = true
              }
            })
          } else {
            // 彈出獲取手機號授權按鈕
            that.showUserPhoneVisible = true
          }
        })
      } else {
        console.log('點擊了拒絕')
      }
    },
  • 微信通過用戶點擊授權獲取手機號來實作賬號系結操作,如果用戶點擊拒絕,那么提示用戶無法系結當前用戶微信系結的手機號,請用戶繼續授權,
    bindGetUserPhone (e) {
      const that = this
      if (e.mp.detail.errMsg === 'getPhoneNumber:ok') {
        console.log(e.mp.detail)
        // 寫入store
        const params = {
          appId: appId,
          socialKey: mpvue.getStorageSync('bindKey'),
          encryptedData: e.mp.detail.encryptedData,
          iv: e.mp.detail.iv
        }
        getUserPhone(params).then(res => {
          if (res.code === 200) {
            console.log(res)
            const loginParams = {
              grant_type: 'social',
              social_key: mpvue.getStorageSync('bindKey')
            }
            postToken(loginParams).then(res => {
              if (res.code === 200) {
                console.log(res)
                const data = https://www.cnblogs.com/FullStackProgrammer/archive/2023/03/24/res.data
                mpvue.setStorageSync('token', data.token)
                mpvue.setStorageSync('refreshToken', data.refreshToken)
                // 獲取用戶資訊
                that.loginSuccess()
              } else {

              }
            })
          } else {
            that.showUserPhoneVisible = false
            // 獲取用戶資訊
            that.loginSuccess()
            Toast(res.msg)
          }
        })
      } else {
        that.showUserPhoneVisible = false
        Toast('當前拒絕授權手機號登陸,請使用賬號密碼登錄')
      }
    },

??通過以上開發基本實作了微信小程式授權登錄第三方業務系統的功能,在此基礎上,注冊的功能可以根據業務需求來擴展,大多數互聯網業務,都會是微信小程式授權登錄之后就自動注冊用戶,但是有些傳統行業的業務,比如只有某些公司或組織內部的用戶才能登錄,那么是不允許微信授權登錄就自助注冊成系統用戶的,微信小程式前端框架也可以歸根據自己的需求,及擅長的開發方式來選擇,但是微信授權登錄的流程是不變的,可以在此基礎上根據業務需求修改優化,

原始碼地址:

Gitee: https://gitee.com/wmz1930/GitEgg

GitHub: https://github.com/wmz1930/GitEgg

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/548008.html

標籤:其他

上一篇:java環境變數設定

下一篇:【建議收藏】7000+字的TIDB保姆級簡介,你見過嗎

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more