WeChatManagement icon indicating copy to clipboard operation
WeChatManagement copied to clipboard

微信快速/实时获取手机号码进行注册/登陆

Open slarkerino opened this issue 8 months ago • 3 comments

目前小程序很多直接通过微信快速/实时获取的手机号码进行注册/登陆 是否考虑增加这一个功能。(有些项目可能先上线后开发的小程序,通过手机号验证登陆可以直接关联上老账号) @gdlcf88

slarkerino avatar Jun 13 '25 04:06 slarkerino

参考 LoginInput.cs 这个文件的属性,然后提供前端参考:

			wechatLogin(){
				if (wx.getUserProfile){
				  wx.getUserProfile({
				    desc:'用于小程序内头像昵称显示',
				    lang: 'zh_CN',
				    success:res => {
				      this.userProfile = res.userInfo
				      this.wxLogin();
				    },
				    fail: (err) =>{
					  console.dir(err);
				      this.$tools.toast({ title: '授权失败' + err, duration: 1500});
				    }
				  })
				  return;
				}
				this.wxLogin();
			},
			wxLogin () {
			  if(!this.isAgree){
				  this.$tools.toast({ title: '请勾选已阅读用户协议', duration: 1500});
				  return;
			  }
			  uni.showLoading({title: '正在登录...', mask: true})
			  wx.login({
			    success: res_login => {
				  
			      this.$api('base.onLogin', {
			        lookupUseRecentlyTenant: false,
			        code: res_login.code,
			        appId: this.$appConfig.appId,
					scope: 'MyProject offline_access'
			      }, {
			        isEndLoading: false,
			        removeAuthorization: true
			      }).then(res => {
			        let tokens = JSON.parse(res.rawData)
			        // 存下token
					let that = this;
					uni.setStorage({
						key: 'tokens',
						data: {access: tokens.access_token,
					    refresh: tokens.refresh_token},
						success: function () {
							// 存下token解码信息
							that.$tools.decodeAccessToken(tokens)
							that.handleAfterLogin()
						}
					})
			      })
			    }
			  });
			},
import axios from 'axios'
import store from '../store'
import jwtDecode from 'jwt-decode' 
import {ElMessage} from 'element-plus'
const {remote} = require('electron');

const isSandbox = remote.process.argv.includes('--sandbox')
const getBaseUrl = isSandbox ? process.env.SANDBOX_BASE_API : process.env.BASE_API
const getTenantId = isSandbox ? process.env.SANDBOX_TENANT_ID : process.env.TENANT_ID
const getHost = removeHttp(getBaseUrl)

const serves = axios.create({
  baseURL: getBaseUrl,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
    '__tenant': getTenantId
  }
})

function removeHttp(url) {
  return url.replace(/^https?:\/\//, '');
}
// 是否正在刷新的标记
let isRefreshing = false;
// 重试队列,每一项将是一个待执行的函数形式
let retryRequests = []
// 设置请求发送之前的拦截器
serves.interceptors.request.use(config => {
  // 统一在http请求的header都加上toke
  let standbyToken = ''
  if(!store.state.vuex_token){ // 防止store数据未取出 检验缓存是否有token
    let store = JSON.parse(localStorage.getItem('store'))
    standbyToken = store.vuex_token
  }
  config.headers.Authorization = `Bearer ${store.state.vuex_token || standbyToken}`
  return config
}, err => {Promise.reject(err)})

// 设置请求接受拦截器
serves.interceptors.response.use(res => {
  if (Math.floor(res.status / 100) === 2) {
    return res.data
  }
}, err => {
  console.dir(err);
  let status = err.response && err.response.status ? err.response.status : 401;
  let msg = err.response.data.error ? (
    err.response.data.error.details ? err.response.data.error.details :
    err.response.data.error.message ? err.response.data.error.message :
    err.response.data.error_description ? err.response.data.error_description :
    status + '错误') : status + '错误'
  if (status === 400) {
    ElMessage.error(msg ? msg : '请检查传入参数');
  }
  if (status === 401) {
    const config = err.config;
    console.log(config);
    if (!isRefreshing) {
      isRefreshing = true;
      // store.commit('$clearUserData',{
      //   login: false
      // });
      reftoken().then(res => {
        isRefreshing = false;
        let data = JSON.parse(localStorage.getItem('store'));
        data.vuex_user = jwtDecode(res.data.access_token);
        console.log(res);
        data.vuex_token = res.data.access_token;
        data.ref_token = res.data.refresh_token;
        store.state.vuex_token = res.data.access_token;
        store.state.ref_token = res.data.refresh_token;
        localStorage.setItem("store", JSON.stringify(data));
        console.log(data);
        retryRequests.forEach((cb) => cb())
        retryRequests = []
        return serves(config);
      }, err=>{
        isRefreshing = false;
        store.commit('$clearUserData');
      });
    } else {
      // 正在刷新token,返回一个未执行resolve的promise
      return new Promise((resolve) => {
        // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
        // @ts-ignore
        retryRequests.push(() => {
          resolve(serves(config))
        })
      })
    }
  }
  if (status === 403) {
    // 403错误消息处理
    handle403(err.response)
  }
  // 设置接受数据之后,做什么处理
  if (status === 500 || status === 405 || status === 415 || status === 404) {
    ElMessage.error(msg)
  }
  // 判断请求异常信息中是否含有超时timeout字符串
  if (err.message.includes('timeout')) {
    console.log('错误回调', err)
    ElMessage.error('网络超时')
  }
  if (err.message.includes('Network Error')) {
    console.log('错误回调', err)
    ElMessage.error('服务端未启动,或网络连接错误')
  }
  return Promise.reject(err)
})

function reftoken() {
  let data = new URLSearchParams()
  data.append('grant_type', 'refresh_token')
  data.append('client_id', 'MyProject_Swagger')
  data.append('refresh_token',  store.state.ref_token)
  data.append('scope', 'MyProject offline_access')
  let request = axios.create({
    baseURL: getBaseUrl,
    timeout: 10000,
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'X-Requested-With': 'XMLHttpRequest',
      '__tenant': getTenantId
    }
  });
  return request({
    url: '/connect/token',
    method: 'post',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    data
  });
}

// 处理403的提示
function handle403(res) {
  if (!res.data.error) {
    ElMessage.error('无权限访问')
    return;
  }
  const message = res.data.error.message
  console.log('message',message)
  if (message) {
    // 存在message
    if (message.startsWith('Authorization failed!') || message.startsWith('授权失败!')) {
      // 无权限访问
      ElMessage.error('无权限访问')
    } else {
      // 正常message返回
      ElMessage.error(message)
    }
  } else {
    // 无message
    ElMessage.error('403,无错误信息')
  }
}

export { serves as default, getBaseUrl, getHost }

gdlcf88 avatar Jun 13 '25 04:06 gdlcf88

<button open-type="getRealtimePhoneNumber" bindgetrealtimephonenumber="getrealtimephonenumber"></button>
  getrealtimephonenumber (e) {
    console.log(e.detail.code)  // 动态令牌
 this.$api('base.onLoginByPhoneCode', {
			        lookupUseRecentlyTenant: false,
			        code: e.detail.code,
			        appId: this.$appConfig.appId,
					scope: 'MyProject offline_access'
			      }, {
			        isEndLoading: false,
			        removeAuthorization: true
			      }).then(res => {
			        let tokens = JSON.parse(res.rawData)
			        // 存下token
					let that = this;
					uni.setStorage({
						key: 'tokens',
						data: {access: tokens.access_token,
					    refresh: tokens.refresh_token},
						success: function () {
							// 存下token解码信息
							that.$tools.decodeAccessToken(tokens)
							that.handleAfterLogin()
						}
					})
			      })
  }

I think the code looks like this. and the backend service will create a new user with phonenumber if not already or search for user with the phone number and log in, return the token. the response and input will look the same as the current LoginAsync method. I am thinking separate the logic to a LoginByPhoneAsync method and create a WeChatMiniProgramPhoneNumberGrantValidator for IDS4 @gdlcf88

slarkerino avatar Jun 16 '25 05:06 slarkerino

#110 made a new pull request

slarkerino avatar Jun 22 '25 11:06 slarkerino