通过wx.login获取 临时登录凭证code,向后端换取token。 可以做到无感登录。
时序图:

说明:
1、客户端调用 wx.login() 获取 临时登录凭证code,通过 wx.request() 发起网络请求,将 code 传给服务端
2、服务端使用 code + appid + appsecret 向微信换取 (调用 auth.code2Session 接口)用户唯一标识openid 和 会话密钥session_key
3、服务端自定义 登录状态token(与openid、session_key关联)返回客户端
4、客户端将 登录状态token 存入 缓存storage(推荐使用 wx.setStorageSync(‘key’, ‘value’) 同步存储)
5、客户端wx.request() 发起请求时,携带 登录状态token (推荐使用 wx.getStorageSync(‘key’) 同步获取)
6、服务端通过 登录状态token 查询到对应 openid 和 session_key
7、验证成功后,返回业务数据给客户端
注意:
1、会话密钥session_key 是对⽤户数据进⾏加密签名的密钥。为了应⽤⾃身的数据安全,开发者服务器不应该把会话密钥下发到⼩程序,也不应该对外提供这个密钥。
2、临时登录凭证code 只能使⽤⼀次
code(以uni-app框架为例):
新建http.js,封装登录方法
// baseurl
let baseUrl = 'https://test.com.cn'
// 请求封装
async function _request(url, method, data = {}) {
let res = await requestPromise(url, method, data)
if (res.code == 200) {
return Promise.resolve(res)
} else if (res.code == 401) { // 无感刷新
return await login(url, method, data)
} else {
return Promise.reject(res)
}
}
// 登录
async function login(url, method, data) {
let openIdUrl = new String()
// #ifdef MP-WEIXIN
openIdUrl = '微信登录接口地址'
//#endif
//#ifdef MP-ALIPAY
openIdUrl = '支付宝登录接口地址'
//#endif
let res = await requestPromise(openIdUrl, 'POST', { code: await _getAppCode(), source: 'MP' })
if (res.code == 200) {
// 将token,userid存入缓存
uni.setStorageSync('token', res.data.token)
uni.setStorageSync('userId', res.data.userId)
// 再次发起请求
return await _request(url, method, data)
} else {
return Promise.reject(res)
}
}
// 发送request请求
function requestPromise(url, method, data = {}) {
return new Promise((resolve, reject) => {
uni.request({
header: {
'Content-Type': 'application/json;charset=UTF-8',
'X-Token': uni.getStorageSync('token') || new String(),
'X-UserId': uni.getStorageSync('userId') || new String()
},
url: `${baseUrl}${url}`,
method: method,
data: data,
success: result => {
resolve(result)
},
fail: error => {
reject(error)
},
})
})
}
// 获取临时登录凭证code
function _getAppCode() {
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
uni.login({
provider: 'weixin',
success(res) {
resolve(res.code)
},
fail(err) {
reject(err)
}
})
// #endif
// #ifdef MP-ALIPAY
// 系统建议使用支付宝原生写法
my.getAuthCode({
scopes: 'auth_base',
success(res) {
resolve(res.authCode)
},
fail(err) {
reject(err)
}
})
// #endif
})
}
module.exports = {
$get: function(url, data, onSuccess, onError) {
_request(url, 'GET', data).then(res => {
onSuccess && onSuccess(res)
}).catch(err => {
onError && onError(err)
})
},
$put: function(url, data, onSuccess, onError) {
_request(url, 'PUT', data).then(res => {
onSuccess && onSuccess(res)
}).catch(err => {
onError && onError(err)
})
},
$post: function(url, data, onSuccess, onError) {
_request(url, 'POST', data).then(res => {
onSuccess && onSuccess(res)
}).catch(err => {
onError && onError(err)
})
},
$delete: function(url, data, onSuccess, onError) {
_request(url, 'DELETE', data).then(res => {
onSuccess && onSuccess(res)
}).catch(err => {
onError && onError(err)
})
},
baseUrl: baseUrl
}
新建api.js,对接口进行封装
import https from '../utils/https.js'
export function test(params) {
return new Promise((resolve, reject) => {
https.$get("/api", params, res => {
return resolve(res)
}, err => {
return reject(err)
})
})
}
过button按钮的bindgetphonenumber事件,弹出手机号授权,获取到加密数据后,向后端换取token。
说明:
1、过button按钮的bindgetphonenumber事件获取手机号加密数据,按钮需要设置open-type=“getPhoneNumber”
2、调用 wx.login() 获取 临时登录凭证code
3、将加密数据(encryptedData、iv、signature、rawData)和 临时登录凭证code传给服务端
4、服务端使用 code + appid + appsecret 向微信换取 (调用 auth.code2Session 接口)用户唯一标识openid 和 会话密钥session_key
5、服务端根据session_key,appid ,encryptedData,iv解密手机号
6、服务端自定义 登录状态token(与openid、session_key关联)返回客户端
7、客户端将 登录状态token 存入 缓存storage(推荐使用 wx.setStorageSync(‘key’, ‘value’) 同步存储)
8、客户端wx.request() 发起请求时,携带 登录状态token (推荐使用 wx.getStorageSync(‘key’) 同步获取)
9、服务端通过 登录状态token 查询到对应 openid 和 session_key
10、验证成功后,返回业务数据给客户端
注意:
在回调中调⽤ wx.login 登录,可能会刷新登录态。此时服务器使⽤ code 换取的sessionKey 不是加密时使⽤的 sessionKey,导致解密失败。建议开发者提前进⾏ login;或者在回调中先使⽤ checkSession 进⾏登录态检查,避免 login刷新登录态。
也就是说在触发getPhoneNumber方法(用户点击button)之前,就需要获取最新code。
code(以uni-app框架为例):
新建wxLogin.vue
<template>
<view class="wx-login">
<!-- #ifdef MP-WEIXIN -->
<u-button type="primary" text="微信用户一键登录" open-type="getPhoneNumber" :plain="true" @getphonenumber="getUserPhoneNumber"></u-button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<button open-type="getPhoneNumber" :plain="true"@getphonenumber="getUserPhoneNumber" scope='userInfo'>支付宝用户一键登录</button>
<!-- #endif -->
</view>
</template>
<script>
import { mapActions } from 'vuex'
export default {
async created() {
this.code = await this.getAppCode()
},
data() {
return {
// 用户凭证
code: new String()
}
},
methods: {
...mapActions(['Login']),
// 微信用户手机号登录
getUserPhoneNumber(event) {
if(event.detail.errMsg !== 'getPhoneNumber:ok') return
uni.showToast({
title: '登录中',
icon: 'loading',
mask: true
})
event.detail.code = this.code
this.Login({ userInfo: event.detail }).then(async ({ code, msg }) => {
if (code == 200) { // 登录成功,跳转首页
uni.reLaunch({ url: '/pages_home/home/index' })
} else {
this.code = await this.getAppCode()
uni.showToast({
icon: 'none',
title: msg,
duration: 2000
})
}
})
},
// 获取code
getAppCode() {
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
uni.login({
provider: 'weixin',
success(res) {
resolve(res.code)
},
fail(err) {
reject(err)
}
})
// #endif
// #ifdef MP-ALIPAY
my.getAuthCode({
scopes: 'auth_base',
success(res) {
resolve(res.authCode)
},
fail(err) {
reject(err)
}
})
// #endif
})
},
}
}
</script>
<style lang="less" scoped>
.wx-login {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 0 32rpx 60rpx;
button {
font-size: 28rpx;
font-weight: 400;
}
}
</style>
新建store/index.js 封装登录逻辑
import Vue from "vue"
import Vuex from 'vuex'
import { loginByMobile } from '@/api/login.js'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
token: new String(),
userId: new String(),
userInfo: new Object(),
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_USER: (state, userInfo) => {
state.userInfo = userInfo
},
SET_USERID: (state, userId) => {
state.userId = userId
},
},
actions: {
// 登录
Login({ commit }, { userInfo }) {
// 微信手机号登录
userInfo.type = 'WX_MP'
delete userInfo.errMsg
return new Promise((resolve, reject) => {
loginByMobile(userInfo).then(response => {
if (response.code == 200) {
uni.setStorageSync('USER_ID', response.data.userId)
uni.setStorageSync('ACCESS_TOKEN', response.data.token)
uni.setStorageSync('userInfo', response.data)
commit('SET_USERID', response.data.userId)
commit('SET_TOKEN', response.data.token)
commit('SET_USER', response.data)
}
resolve(response)
}).catch(error => {
reject(error)
})
})
},
// 登出
Logout({ commit, state }) {
return new Promise((resolve) => {
commit('SET_USERID', new String())
commit('SET_TOKEN', new String())
commit('SET_USER', new Object())
uni.removeStorageSync('USER_ID')
uni.removeStorageSync('ACCESS_TOKEN')
uni.removeStorageSync('userInfo')
resolve('200')
})
},
},
})
export default store
main.js中引入vuex 并且挂载到vue实例上
//引入vuex 并且挂载到vue实例上
import store from "./store/index.js"
Vue.prototype.$store = store
request.js封装
import env from './env.js'
// http
export const request = (url, method, data = {}) => {
return new Promise((resolve, reject) => {
uni.request({
header: {
'Content-Type': 'application/json;charset=UTF-8',
'X-Token': uni.getStorageSync('ACCESS_TOKEN'),
'X-UserId': uni.getStorageSync('USER_ID'),
'satoken': uni.getStorageSync('userInfo').satoken
},
url: `${env.root}${url}`,
method: method,
data: data,
success: res => {
if (res.data.code == 401 || res.data.message == '请先登录') {
// token过期 重新登录
uni.showToast({
icon: 'none',
title: '登录超时,请重新登录',
duration: 2000
})
uni.removeStorageSync('ACCESS_TOKEN')
uni.removeStorageSync('USER_ID')
uni.removeStorageSync('userInfo')
// 跳转登录
uni.redirectTo({ url: '/pages/login/index' })
reject(res.data)
} else { // 返回内容
resolve(res.data)
}
},
error: err => {
console.error("请求失败", err)
reject(err)
},
complete: () => {},
})
})
}
// upload
export const upload = (url, filePath, name, formData = {}) => {
return new Promise((resolve, reject) => {
uni.uploadFile({
header: {
'Content-Type': 'application/json;charset=UTF-8',
'X-Token': uni.getStorageSync('ACCESS_TOKEN'),
'X-UserId': uni.getStorageSync('USER_ID'),
'satoken': uni.getStorageSync('userInfo').satoken
},
url: `${env.root}${url}`,
filePath: filePath,
name: name,
formData: formData,
success: res => {
if (res.data.code == 401 || res.data.message == '请先登录') {
// token过期 重新登录
uni.showToast({
icon: 'none',
title: '登录超时,请重新登录',
duration: 2000
})
uni.removeStorageSync('ACCESS_TOKEN')
uni.removeStorageSync('USER_ID')
uni.removeStorageSync('userInfo')
// 跳转登录
uni.redirectTo({ url: '/pages/login/index' })
reject(res.data)
} else { // 返回内容
resolve(JSON.parse(res.data))
}
},
error: err => {
console.error("请求失败", err)
reject(err)
},
complete: () => {},
})
})
}
通过button按钮的click事件,调用 wx.getUserProfile() 弹出授权框,获取到用户加密数据后,向后端换取token。
说明:
1、通过 wx.getUserProfile() 获取用户信息,此方法需要通过button按钮的click事件触发
2、调用 wx.login() 获取 临时登录凭证code
3、将加密数据(encryptedData、iv)和 临时登录凭证code传给服务端
4、服务端使用 code + appid + appsecret 向微信换取 (调用 auth.code2Session 接口)用户唯一标识openid 和 会话密钥session_key
5、服务端自定义 登录状态token(与openid、session_key关联)返回客户端,同时返回用户信息
6、客户端将 登录状态token 存入 缓存storage(推荐使用 wx.setStorageSync(‘key’, ‘value’) 同步存储)
7、客户端wx.request() 发起请求时,携带 登录状态token (推荐使用 wx.getStorageSync(‘key’) 同步获取)
8、服务端通过 登录状态token 查询到对应 openid 和 session_key
9、验证成功后,返回业务数据给客户端
注意:
在回调中调⽤ wx.login 登录,可能会刷新登录态。此时服务器使⽤ code 换取的sessionKey 不是加密时使⽤的 sessionKey,导致解密失败。建议开发者提前进⾏ login;或者在回调中先使⽤ checkSession 进⾏登录态检查,避免 login刷新登录态。
code(以uni-app框架为例):
<template>
<view class="content">
<image src="logo.png"></image>
<view class="title">申请获取以下权限</view>
<text class="msg">获取你的公开信息(昵称、头像、地区等)</text>
<!-- #ifdef MP-WEIXIN -->
<button class="btn" @click="wxgetUserInfo">授权登录</button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<button class="btn" open-type="getAuthorize" @getAuthorize="alipaygetUserInfo" @error="onAuthError" scope='userInfo'>授权登录</button>
<!-- #endif -->
</view>
</template>
<script>
import { loginByWx, loginByAlipay } from '@/api/user.js'
export default {
data() {
return {
code: new String()
}
},
async onLoad() {
this.code = await this.getAppCode()
},
methods: {
// 获取微信用户信息
async wxgetUserInfo() {
try {
// 微信登录
// #ifdef MP-WEIXIN
let userData = await this._getwxUserData()
// 调用后台接口登录
let loginRes = await this.appLogin(userData)
// savecache
uni.setStorageSync('isLogin', true)
uni.setStorageSync('userInfo', {
headImg: loginRes.headImg,
userName: loginRes.userName
});
uni.setStorageSync('token', loginRes.token)
uni.setStorageSync('userId', loginRes.userId)
uni.navigateBack({
delta: 1
});
// #endif
} catch(err) {
this.onAuthError()
}
},
// 支付宝用户登录
async alipaygetUserInfo() {
try {
// 支付宝登录
// #ifdef MP-ALIPAY
let userData = await this._getalipayUserData()
// 调用后台接口登录
let loginRes = await this.appLogin(userData)
loginRes.userName = userData.nickName
loginRes.headImg = userData.avatar
// savecache
uni.setStorageSync('isLogin', true)
uni.setStorageSync('userInfo', {
headImg: loginRes.headImg,
userName: loginRes.userName
})
uni.setStorageSync('token', loginRes.token)
uni.setStorageSync('userId', loginRes.userId)
uni.navigateBack({
delta: 1
})
// #endif
} catch(err) {
this.onAuthError()
}
},
// 授权失败
onAuthError() {
uni.showToast({
title: '授权失败,请确认授权已开启',
mask: true,
icon: 'none'
})
},
// 获取支付宝用户加密数据
_getalipayUserData() {
return new Promise((resolve, reject) => {
my.getOpenUserInfo({
success: res => {
let userInfo = JSON.parse(res.response).response
resolve(userInfo)
},
fail: err => {
reject(err)
}
})
})
},
// 获取微信用户加密数据
_getwxUserData() {
// 用户信息接口调整,使用uni.getUserInfo() 获取到的用户信息是一个灰色的头像和微信用户
// 需要使用 uni.getUserProfile() 获取用户信息,此方法需要按钮触发 @click=func
return new Promise((resolve, reject) => {
uni.getUserProfile({
desc: '完善用户信息',
success: data => {
console.log("用户信息:", data)
resolve(data)
},
fail: err => {
reject(err)
}
})
})
},
// 获取 临时登录凭证code
getAppCode() {
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
uni.login({
provider: 'weixin',
success(res) {
resolve(res.code)
},
fail(err) {
reject(err)
}
})
// #endif
// #ifdef MP-ALIPAY
// 系统建议使用支付宝原生写法
my.getAuthCode({
scopes: 'auth_base',
success(res) {
resolve(res.authCode)
},
fail(err) {
reject(err)
}
})
// #endif
})
},
// 用户登录
appLogin(detail) {
return new Promise(async(resolve, reject) => {
try {
// 微信登录
// #ifdef MP-WEIXIN
let params = {
code: this.code,
source: 'MP',
encryptedData: detail.encryptedData,
iv: detail.iv
}
let wxloginRes = await loginByWx(params)
if(wxloginRes.code == 200) {
resolve(wxloginRes.data)
} else {
reject(wxloginRes)
}
// #endif
// #ifdef MP-ALIPAY
// 系统建议使用支付宝原生写法
let alipayloginRes = await loginByAlipay({
code: this.code
});
if(alipayloginRes.code == 200) {
resolve(alipayloginRes.data)
} else {
reject(alipayloginRes)
}
// #endif
} catch(err) {
reject(err)
}
});
},
}
}
</script>
<style lang="less">
view, text, image, input {
box-sizing: border-box;
}
.content {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100vh;
padding: 160rpx 40rpx 0;
image {
width: 275rpx;
height: 104rpx;
}
.title {
width: 100%;
margin-top: 80rpx;
font-size: 30rpx;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
text-align: left;
}
.msg {
display: block;
width: 100%;
margin-top: 16rpx;
font-size: 28rpx;
color: rgba(0, 0, 0, 0.65);
text-align: left;
}
.btn {
position: absolute;
bottom: 160rpx;
width: 670rpx;
height: 96rpx;
background: #00B391;
border-radius: 8rpx;
font-size: 34rpx;
font-weight: 400;
color: #FFFFFF;
line-height: 96rpx;
}
}
</style>
我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU