主题
房屋管理(一)
本页对应 房屋管理,共 8 段视频,约 1 小时 6 分。
学习目标
- 能在响应拦截器中根据状态码 401 判断 token 失效,并读取、传递 refresh token。
- 能调用刷新 token 接口,校验返回状态码,并用新 token 与 refresh token 替换本地及应用实例中的旧值。
- 能实现无感请求:获取失败请求的 config,替换 Authorization 中的 token,重新发起请求并返回响应结果。
- 能判断 refresh token 过期条件,获取当前页面路径,跳转登录页,并在登录成功后回跳原页面。
- 能完成腾讯位置服务接入:配置合法域名、声明位置权限、引入 SDK、使用 key 实例化服务。
- 能获取用户经纬度,调用地点搜索查询周边住宅小区,限制返回条数、处理失败回调、过滤字段并渲染到页面。
知识点
刷新 token 的整体思路
为什么需要:登录成功后本地保存的 token 有时效,转写稿中设置为 8 小时;过期后服务端会返回 401,如果直接跳登录页,用户体验较差。怎么想:用有效期更长的 refreshToken(转写稿中为 3 天)向服务端换取新 token,再继续原业务。怎么做:在响应拦截器统一识别 401,读取 refreshToken,调用刷新接口,保存新 token 和 refreshToken。易错点:不要把 401 当普通业务错误直接提示;refreshToken 也会过期,不能无限刷新。
判断 401 并读取 refreshToken
为什么需要:所有接口响应都会经过响应拦截器,在这里判断失效最集中。怎么想:看响应数据里的 data.code === 401,而不是只看 HTTP 状态码。怎么做:先保留笔记中的判断代码:
javascript
// utils/http.js
// 配置响应拦截器
http.intercept.response = async function ({ data, config }) {
// 如果状态码为401,则表明token已失效
if (data.code === 401) {
// 获取应用实例来读取 refreshToken
const app = getApp()
console.log(app.refreshToken)
}
// 只保留data数据,其它的都过滤掉
return data
}同时要在 app.js 启动时读取本地存储:
javascript
// app.js
App({
onLaunch() {
// 读取 token
this.getToken()
},
getToken() {
// 读取本地的token
this.token = wx.getStorageSync('token')
this.refreshToken = wx.getStorageSync('refreshToken')
},
})易错点:getApp() 要在应用实例创建后使用;只读 token 不读 refreshToken,后面刷新接口就拿不到凭证。调试时可把本地 token 故意改成错误值模拟失效,重新编译后控制台会看到 401,并打印出 refreshToken。
调用接口换取新 token
为什么需要:refreshToken 必须通过请求头发给服务端,服务端才会返回新 token。怎么想:用 POST /refreshToken,请求头 Authorization: 'Bearer ' + app.refreshToken。怎么做:保留笔记代码:
javascript
// utils/http.js
// 配置响应拦截器
http.intercept.response = async function ({ data, config }) {
// 如果状态码为401,则表明token已失效
if (data.code === 401) {
// 获取应用实例
const app = getApp()
// 1.1 调用接口获取新的 token
const res = await http({
url: '/refreshToken',
method: 'POST',
header: {
Authorization: 'Bearer ' + app.refreshToken,
},
})
// 检测接口是否调用成功
if (res.code !== 10000) return wx.utils.toast('更新token失败!')
// 重新存储新的 token
app.setToken('token', res.data.token)
app.setToken('refreshToken', res.data.refreshToken)
}
// 只保留data数据,其它的都过滤掉
return data
}接口文档地址见笔记链接:接口文档地址。
易错点:Bearer 后要有空格;这里传的是 refreshToken,不是已失效的旧 token;要判断 res.code !== 10000;保存时两个 token 都要更新。调试时看 Network,应能看到 /refreshToken 请求成功并返回新 token,再用 app.setToken 覆盖旧值。
无感重试与重新登录
为什么需要:刷新成功后,原来失败的接口不会自动重发,用户仍需手动刷新页面。怎么想:从拦截器参数 config 拿到原请求参数,把请求头里的旧 token 替换成新 token,再调用一次 http(config),并 return 重试结果。怎么做:转写稿中用 Object.assign 覆盖 config.header.Authorization,值写成 'Bearer ' + res.data.token,然后再次发起请求。若 401 且 config.url 包含 /refreshToken,说明 refreshToken 也过期,应跳登录页;跳转前用 getCurrentPages() 取最后一个页面的 route 作为 redirectUrl,登录成功后再回原页面。易错点:重试必须用新 token;要 return 重试结果;route 通常没有前导斜杠,传给登录页时要补 /;刷新接口自身 401 时不能再递归刷新,否则会死循环。
调试与易错点小结
调试时可按转写稿流程:先把本地 token 改成错误值,重新编译,观察原接口返回 401;确认控制台打印 refreshToken;再确认 Network 中 /refreshToken 被调用并返回新 token;最后检查本地旧 token 是否被 setToken 替换。无感请求调试时,Network 应呈现“原接口 401 → 刷新 token → 再次调用原接口成功”。重新登录调试时,把 token 和 refreshToken 都改错,应跳登录页并携带原页面地址。易错点集中在:状态码位置、Bearer 空格、两个 token 的保存、重试结果返回、刷新接口 401 的死循环判断。
先理解“无感请求”要解决什么问题
为什么需要: 调用某个接口时,往往要等请求发出去、响应回来,才知道 token 是否过期。若服务端返回 401,说明 token 已失效。此时虽然可以刷新 token,但原来那次接口调用并没有拿到数据,用户仍然看不到内容。
怎么想: 刷新 token 成功后,不要只停留在“换新 token”,而要把刚才失败的原请求自动再发一次。用户不需要手动刷新页面,也感觉不到 token 曾经失效,这就是本节所说的“无感请求”。笔记里也说明,这是作者自己起的名字,重点理解背后的实现逻辑。
怎么做: 在响应拦截器中判断 data.code === 401,先调用刷新接口拿到新 token,再重放原请求。
易错点: 转写稿中“无干请求”是口误/同音字,应为“无感请求”;“头痕”“偷痕”应理解为 token;“肯fi”应理解为 config。
在响应拦截器里捕获 401
为什么需要: 每个请求都单独判断 token 是否过期会很重复,放在响应拦截器里统一处理更合适。
怎么想: 只要某个响应返回 401,就进入刷新流程。这里判断的是业务数据中的 data.code,不是随便判断 HTTP 状态。
怎么做: 在 http.intercept.response 中解构出 { data, config },先判断 data.code === 401。
易错点: 不要写成 data.code = 401;要使用严格相等或至少正确比较。转写稿中“扣”指 code,“date”是口误,代码中实际是 data。
javascript
// 配置响应拦截器
http.intercept.response = async function ({ data, config }) {
// 如果状态码为401,则表明token已失效
if (data.code === 401) {
// 获取应用实例
const app = getApp()
// 1.1 调用接口获取新的 token
const res = await http({
url: '/refreshToken',
method: 'POST',
header: {
Authorization: 'Bearer ' + app.refreshToken,
},
})
// 检测接口是否调用成功
if (res.code !== 10000) return wx.utils.toast('更新token失败!')
// 重新存储新的 token
app.setToken('token', res.data.token)
app.setToken('refreshToken', res.data.refreshToken)
// 1.2 获取到原来接口请求的参数
config = Object.assign(config, {
header: {
// 更新后的 token
Authorization: 'Bearer ' + res.data.token,
},
})
// 重新发请求
return http(config)
}
// 只保留data数据,其它的都过滤掉
return data
}刷新 token 后要重放原请求
为什么需要: 只刷新 token 只能保证后续请求可用,但当前失败的原接口仍然没有数据。转写稿的调试过程也提到:刷新后如果不重新编译,原接口不会自动再调用,页面数据不会立刻回来。
怎么想: 响应拦截器参数里的 config 保存着原请求信息。转写稿中打印 config 可以看到 url 是 /user/info,header 里仍是旧 token。因此要拿 config 重新发请求。
怎么做: 刷新成功后执行 return http(config),把重放结果返回出去。
易错点: 不能只写 http(config) 而不 return,否则调用方拿不到重放结果;转写稿中“rap片出去”就是要把结果 return 出去。
覆盖旧 Authorization,避免再次 401
为什么需要: 原 config.header.Authorization 里是旧 token,直接重放仍会失败。
怎么想: 刷新接口已经返回新 token,要用它覆盖原请求头里的 Authorization。
怎么做: 用 Object.assign(config, { header: { Authorization: 'Bearer ' + res.data.token } }) 覆盖。转写稿中“object点上额赞”应纠正为 Object.assign。
易错点: 本示例的写法会整体替换 header,若原请求还有其他自定义头,需要额外合并;同时要确认 res.data.token 确实来自刷新接口返回。
javascript
// 获取到原来接口请求的参数
config = Object.assign(config, {
header: {
// 更新后的 token
Authorization: 'Bearer ' + res.data.token,
},
})调试过程与边界提醒
为什么需要: 无感请求涉及“原请求失败 → 刷新 → 重放”多个步骤,必须看网络请求确认。
怎么想: 测试时不可能等 8 小时,可以手动把本地 token 改错,模拟失效。转写稿中就是把 E 改成 D 来制造 401。
怎么做: 打开 Network,应看到:原接口返回 401,随后请求 /refreshToken,刷新成功后再次调用原接口并拿到数据。
易错点: 若刷新接口本身也失败,应提示“更新token失败!”并中断;另外转写稿后续提到 refreshToken 也可能过期,此时不能再无限刷新,而应走重新登录流程。本节代码重点是无感重放,边界逻辑要结合后续“重新登录”内容理解。
为什么还要处理“重新登录”
刷新 token 的逻辑解决的是普通 token 过期:响应拦截器发现 401 后,用 refreshToken 调 /refreshToken 换新 token,再重发原请求。但转写稿里明确提醒:refreshToken 也会过期,小程序里设置为 3 天。用户超过 3 天没打开小程序,refreshToken 失效,就无法再刷新 token,只能让用户重新登录。
所以这一节的关键不是“再刷新一次”,而是判断“连 refreshToken 都失效了”时,停止刷新流程,跳转登录页,并记住用户原来所在页面,登录成功后能回跳。
判断 refreshToken 过期的两个条件
为什么需要:401 只说明当前请求未授权,不能直接断定 refreshToken 过期。因为普通 token 过期时也会返回 401,此时应该先走刷新 token 流程。
怎么想:在响应拦截器里先看 data.code === 401;再看本次请求地址 config.url 是否包含 /refreshToken。如果刷新接口本身也返回 401,才说明 refreshToken 过期。
怎么做:把判断放在刷新 token 逻辑之前,并 return 掉,避免继续用过期 refreshToken 再刷新,形成错误循环。
javascript
// 配置响应拦截器
http.intercept.response = async function ({ data, config }) {
// 如果状态码为401,则表明token已失效
if (data.code === 401) {
// 获取应用实例
const app = getApp()
// 1.3 状态为 401 且接口为 /refreshToken 表明 refreshToken 也过期了
if (config.url.includes('/refreshToken')) {
// 获取当前页面的路径,保证登录成功后能跳回到原来页面
const pageStack = getCurrentPages()
const currentPage = pageStack.pop()
const redirectURL = currentPage.route
// 跳由跳转(登录页面)
return wx.redirectTo({
url: '/pages/login/index?redirectURL=/' + redirectURL,
})
}
// 1.1 调用接口获取新的 token
const res = await http({
url: '/refreshToken',
method: 'POST',
header: {
Authorization: 'Bearer ' + app.refreshToken,
},
})
// 检测接口是否调用成功
if (res.code !== 10000) return wx.utils.toast('更新token失败!')
// 重新存储新的 token
app.setToken('token', res.data.token)
app.setToken('refreshToken', res.data.refreshToken)
// 1.2 获取到原来接口请求的参数
config = Object.assign(config, {
header: {
// 更新后的 token
Authorization: 'Bearer ' + res.data.token,
},
})
// 重新发请求
return http(config)
}
// 只保留data数据,其它的都过滤掉
return data
}易错点:不要先调用 /refreshToken 再判断它是否 401;否则逻辑顺序就反了。config.url 用 includes('/refreshToken') 判断,能覆盖带查询参数的情况。
跳转登录页并携带回跳地址
为什么需要:如果直接跳登录页,用户登录后不知道回哪里,体验差。转写稿中通过 getCurrentPages() 获取当前页面实例,再用 route 拿路径。
怎么想:页面栈最后一项是当前页。取到 currentPage.route 后,把它作为 redirectURL 传给登录页。登录成功后,登录页再按这个参数回跳。
怎么做:用 wx.redirectTo 跳 /pages/login/index,并拼接 redirectURL。注意 route 通常不带前导斜杠,所以代码里手动拼了 '/' + redirectURL。
易错点:currentPage.route 不是完整 URL;本例只处理页面路径,未展开原页面参数。若原页面带参数,需要额外处理,但笔记代码没有展开,不能擅自加复杂逻辑。redirectTo 会关闭当前页,适合“必须重新登录”的场景。
调试与验证过程
转写稿里的调试很直接:先手动把普通 token 改错,模拟普通 token 过期;再手动把 refreshToken 也改错,模拟 3 天未打开导致两者都失效。重新编译后,请求返回 401,刷新接口也返回 401,代码进入 /refreshToken 分支,页面跳转到登录页。
调试时还打印了 currentPage,确认通过 route 能拿到当前页面路径;同时发现路径没有斜杠,所以跳转时用 '/' + redirectURL 拼接。登录页参数里能看到 redirectURL,登录成功后能回到原页面,页面数据继续正常获取。
易错点汇总
401判断要分两层:普通 token 过期走刷新;/refreshToken也401才重新登录。/refreshToken分支必须return,否则会继续执行后面的刷新逻辑。- 当前页面路径用
getCurrentPages()取栈顶页面,再读route。 - 拼接
redirectURL时注意前导斜杠。 - 登录页要接收并使用
redirectURL,否则回跳失效。
位置服务要解决什么问题
为什么需要:添加房屋时,第一步不是让用户手填地址,而是根据用户当前位置查询附近小区,再选小区、楼栋、房间。怎么想:位置服务(LBS)依赖第三方地图服务,本项目采用腾讯位置服务;小程序只负责取经纬度,周边搜索、逆地址解析交给 SDK。怎么做:先注册腾讯位置服务账号、创建应用、生成 key,再到小程序管理后台添加 request 合法域名;申请 key 时勾选 WebService API,下载微信小程序 JavaScript SDK 1.2。易错点:多个合法域名用英文分号分隔;key 要用自己申请的,不能共用讲师的 key。
声明并获取用户位置
为什么需要:搜索周边小区必须先有用户经纬度。怎么想:小程序提供 wx.getLocation(),它支持返回 Promise,可配合 async/await。怎么做:先在 app.json 声明 requiredPrivateInfos 和 permission,再到小程序后台“开发管理→接口设置”申请权限。易错点:不声明不能用;该接口上线前要审核,开发环境不校验,上线会校验类目与权限;页面获取位置时会出现定位标志,这是正常现象。
json
{
"requiredPrivateInfos": [
"getLocation",
"chooseLocation"
],
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
}
}javascript
Page({
async getLocation() {
// 调用 API
const {latitude, longitude} = await wx.getLocation()
console.log(latitude, longitude)
}
})实例化 SDK 并搜索周边小区
为什么需要:小程序 API 只给经纬度,查“住宅小区”要用腾讯位置服务 SDK。怎么想:把下载的 SDK 解压放到项目 libs 目录,新建 utils/qqmap.js 实例化并导出;页面导入实例后调用 search。怎么做:search 传 keyword: '住宅小区'、location: [latitude, longitude].join(',')、page_size: 5,在 success 里拿 result.data。易错点:纬度在前、经度在后;page_size 不要拼错;keyword 拼成 keywords 时 fail 会提示“请求参数有误,keyword 没有传”。调试时先 console.log(result),能看到 data 中有 id、title、_distance。
javascript
// utils/qqmap.js
// 导入腾讯位置服务 SDK
import QQMapWX from '../libs/qqmap-wx-jssdk'
// 实例化位置服务(使用个人申请的 key)
export default new QQMapWX({
key: '填写自已的 KEY',
})javascript
// 导入腾讯位置服务
import QQMap from '../../../utils/qqmap'
Page({
onLoad() {
// 获取用户经纬度
this.getLocation()
},
async getLoaction() {
// 调用小和序 API 获取用户位置
const { latitude, longitude } = await wx.getLocation()
// 获取周边小区
this.getPoint(latitude, longitude)
},
getPoint(latitude, longitude) {
// search 是实现地点搜索功能的方法
QQMap.search({
keyword: '住宅小区', //搜索关键词
location: [latitude, longitude].join(','), //设置周边搜索中心点
page_size: 5,
success: (result) => {
// 过滤掉多余的数据
const points = result.data.map(({ id, title, _distance }) => {
return { id, title, _distance }
})
// 渲染数据
this.setData({ points })
},
})
}
})注意:笔记代码中 getLoaction 与 this.getLocation() 拼写不一致,实际应统一为 getLocation。
逆地址解析与重新选择位置
为什么需要:页面既要显示“当前地点”,也要允许用户换位置。怎么想:当前地点用逆地址解析,根据经纬度取具体地址;换位置用 wx.chooseLocation 打开地图让用户选。怎么做:reverseGeocoder 传 location,从 result.address 取地址;chooseLocation 返回新经纬度后再调用 getPoint。易错点:reverseGeocoder 的回调参数要按 { result: { address } } 解构;重新选位置后不要忘记重新搜索周边小区。
javascript
// 导入腾讯位置服务
import QQMap from '../../../utils/qqmap'
Page({
// ...
getPoint(latitude, longitude) {
// 逆地址解析(根据经纬度来获取地址)
QQMap.reverseGeocoder({
location: [latitude, longitude].join(','),
success: ({ result: { address } }) => {
// 数据数据
this.setData({ address })
},
})
// search 是实现地点搜索功能的方法
QQMap.search({
keyword: '住宅小区', //搜索关键词
location: [latitude, longitude].join(','), //设置周边搜索中心点
page_size: 5,
success: (result) => {
// 过滤掉多余的数据
const points = result.data.map(({ id, title, _distance }) => {
return { id, title, _distance }
})
// 渲染数据
this.setData({ points })
},
})
}
})javascript
// 导入腾讯位置服务
import QQMap from '../../../utils/qqmap'
Page({
// ...
chooseLocation() {
// 调用小程序 API 获取新的位置
const { latitude, longitude } = await wx.chooseLocation()
// 获取新的位置附近的小区
this.getPoint(latitude, longitude)
},
getPoint(latitude, longitude) {
// ...
}
})渲染结果与易错点
为什么需要:result.data 字段多,页面只需名称和标识。怎么想:先用 map 过滤成 { id, title, _distance },再 setData({ points })。怎么做:WXML 用 wx:for 遍历 points,wx:key="id",标题绑定 item.title。易错点:不 setData 页面不更新;wx:key 要写稳定字段;fail 回调里可用 result.message 看错误原因。
xml
<!-- 检测用户登录状态 -->
<authorization>
<view class="locate">
<van-cell-group border="{{false}}" title="当前地点">
...
</van-cell-group>
<van-cell-group border="{{false}}" title="附近社区">
<van-cell
wx:for="{{points}}"
wx:key="id"
title="{{item.title}}"
link-type="navigateTo"
url="/house_pkg/pages/building/index"
is-link
/>
</van-cell-group>
</view>
</authorization>课件图示




代码示例
示例一:刷新 token + 无感请求 + 重新登录(响应拦截器)
js
// app.js
App({
onLaunch() {
// 读取 token
this.getToken()
},
getToken() {
// 读取本地的 token
this.token = wx.getStorageSync('token')
this.refreshToken = wx.getStorageSync('refreshToken')
},
// 同时写入应用实例和本地存储,方便任意页面通过 getApp() 读取
setToken(key, value) {
this[key] = value
wx.setStorageSync(key, value)
},
})js
// utils/http.js
// 配置响应拦截器
http.intercept.response = async function ({ data, config }) {
// 如果状态码为 401,则表明 token 已失效
if (data.code === 401) {
// 获取应用实例
const app = getApp()
// 1.3 状态为 401 且接口为 /refreshToken,表明 refreshToken 也过期了
if (config.url.includes('/refreshToken')) {
// 获取当前页面的路径,保证登录成功后能跳回到原来页面
const pageStack = getCurrentPages()
const currentPage = pageStack.pop()
const redirectURL = currentPage.route
// 路由跳转(登录页面)
return wx.redirectTo({
url: '/pages/login/index?redirectURL=/' + redirectURL,
})
}
// 1.1 调用接口获取新的 token(refreshToken 通过 Authorization 头传递)
const res = await http({
url: '/refreshToken',
method: 'POST',
header: {
Authorization: 'Bearer ' + app.refreshToken,
},
})
// 检测接口是否调用成功
if (res.code !== 10000) return wx.utils.toast('更新token失败!')
// 重新存储新的 token
app.setToken('token', res.data.token)
app.setToken('refreshToken', res.data.refreshToken)
// 1.2 获取到原来接口请求的参数,并用新 token 覆盖旧 token
config = Object.assign(config, {
header: {
// 更新后的 token
Authorization: 'Bearer ' + res.data.token,
},
})
// 重新发请求(无感请求)
return http(config)
}
// 只保留 data 数据,其它的都过滤掉
return data
}要点与易错点
if (data.code === 401)是整个逻辑的入口,判断依据是接口返回体里的code,不是 HTTP 状态码;拦截器最后统一return data,所以业务层拿到的就是data。app.refreshToken能读到,前提是app.js的onLaunch里已把token/refreshToken从本地存储读进应用实例;漏掉这一步getApp().refreshToken就是undefined。- 刷新接口的
Authorization里放的是 refreshToken,不是普通 token(普通 token 已失效),且必须拼'Bearer ' + xxx,空格不能少。 if (res.code !== 10000) return wx.utils.toast(...)用return中断后续逻辑,避免拿失败结果去覆盖本地 token。- 无感请求的关键是
config里带着上一次请求的完整参数,但header.Authorization是旧的,必须用Object.assign覆盖成新 token;最后return http(config)的return不能丢,否则外层拿不到数据。 - 判断 refreshToken 是否过期要同时满足两个条件:
code === 401且config.url.includes('/refreshToken');getCurrentPages().pop()拿到的route不带前导斜杠,拼redirectURL时要手动补/。
示例二:位置服务准备(app.json 声明 + SDK 实例化)
json
{
"requiredPrivateInfos": [
"getLocation",
"chooseLocation"
],
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
}
}
}js
// utils/qqmap.js
// 导入腾讯位置服务 SDK
import QQMapWX from '../libs/qqmap-wx-jssdk'
// 实例化位置服务(使用个人申请的 key)
export default new QQMapWX({
key: '填写自已的 KEY',
})要点与易错点
requiredPrivateInfos里必须声明getLocation和chooseLocation,不声明这两个 API 直接不可用;permission.scope.userLocation的desc是授权弹窗文案。- 下载的
qqmap-wx-jssdk目录放到项目里后要改名为libs,utils/qqmap.js的import路径按实际目录写,写错会报模块找不到。 key要用自己申请的 key,不能照抄示例;只有勾选了 WebServiceAPI 并配置了 request 合法域名的 key 才能正常调用。- 导出的是一份
new出来的实例(export default new QQMapWX(...)),页面里直接import QQMap from '...'使用即可,无需重复实例化。 - 正式上线前
getLocation需要在小程序后台「开发管理 → 接口设置」申请权限,开发环境不校验。
示例三:获取用户位置 + 周边小区搜索 + 渲染
js
// pages/house_pkg/locate/index.js
// 导入腾讯位置服务
import QQMap from '../../../utils/qqmap'
Page({
data: {
address: '',
points: [], // 初始给空数组,避免 wxml 遍历 undefined
},
onLoad() {
// 获取用户经纬度
this.getLocation()
},
async getLocation() {
// 调用小程序 API 获取用户位置(支持 Promise)
const { latitude, longitude } = await wx.getLocation()
// 获取周边小区
this.getPoint(latitude, longitude)
},
async chooseLocation() {
// 调用小程序 API 获取新的位置
const { latitude, longitude } = await wx.chooseLocation()
// 获取新的位置附近的小区
this.getPoint(latitude, longitude)
},
getPoint(latitude, longitude) {
// 逆地址解析(根据经纬度来获取具体地址)
QQMap.reverseGeocoder({
location: [latitude, longitude].join(','),
success: ({ result: { address } }) => {
this.setData({ address })
},
})
// search 是实现地点搜索功能的方法
QQMap.search({
keyword: '住宅小区', // 搜索关键词
location: [latitude, longitude].join(','), // 设置周边搜索中心点
page_size: 5, // 控制返回条数
success: (result) => {
// 过滤掉多余的数据
const points = result.data.map(({ id, title, _distance }) => {
return { id, title, _distance }
})
// 渲染数据
this.setData({ points })
},
fail: (err) => {
// 出错时查看原因
console.log(err.message)
},
})
},
})html
<!-- pages/house_pkg/locate/index.wxml -->
<!-- 检测用户登录状态 -->
<authorization>
<view class="locate">
<van-cell-group border="{{false}}" title="当前地点">
<van-cell title="{{address}}" is-link bind:click="chooseLocation" />
</van-cell-group>
<van-cell-group border="{{false}}" title="附近社区">
<van-cell
wx:for="{{points}}"
wx:key="id"
title="{{item.title}}"
link-type="navigateTo"
url="/house_pkg/pages/building/index"
is-link
/>
</van-cell-group>
</view>
</authorization>要点与易错点
wx.getLocation()/wx.chooseLocation()都支持 Promise,所以可以直接await并解构出latitude, longitude;调用时可以不传任何参数。location参数是「纬度,经度」的字符串,顺序不能反:[latitude, longitude].join(','),用+拼接也同理。page_size不要拼错成pagesize/pageSize,写错时返回条数不变;条数默认 10,最大 20。result.data字段很多,用map只保留id/title/_distance,wx:key="id"才能生效;漏掉_distance下划线会拿到undefined。fail回调里通过err.message看错误原因(例如关键词参数名写错导致「请求参数有误」),便于排查。- 页面
data里先给points: [],否则首次渲染遍历undefined会报错;setData({ points })后 wxml 用item.title取值。
常见错误
现象:接口返回 401 但拦截器没进刷新逻辑
原因: 用 HTTP 状态码判断,实际业务状态码在data.code。
修法: 在响应拦截器中判断data.code === 401。现象:401 后
app.refreshToken为undefined
原因:app.js只读取了token,未读取refreshToken,或未调用getToken()。
修法: 在onLaunch中读取本地token与refreshToken并挂到应用实例。现象:刷新 token 接口返回鉴权失败
原因:/refreshToken请求头误用旧token,或Bearer后少空格。
修法: 使用Authorization: 'Bearer ' + app.refreshToken。现象:刷新成功后原接口仍拿不到数据
原因: 只保存新 token,未用原config重发请求,或重发时未替换旧Authorization。
修法: 用新 token 覆盖config.header.Authorization后return http(config)。现象:
refreshToken也过期时反复请求、死循环或页面卡住
原因: 未识别当前请求就是/refreshToken且返回 401。
修法: 先判断config.url.includes('/refreshToken'),命中后重定向登录页并return。现象:登录成功后回不到原页面
原因: 跳登录页时未带redirectURL,或route前少拼/。
修法: 从getCurrentPages()取当前页route,拼接后传给登录页。现象:
wx.getLocation调用失败或上线审核不通过
原因:app.json未声明requiredPrivateInfos/permission,或类目权限未申请。
修法: 补齐getLocation、chooseLocation声明与scope.userLocation描述,并按后台要求申请权限。现象:
QQMap.search返回条数不对或报参数错误
原因:page_size拼写错误,或keyword被误写成keywords。
修法: 按文档使用page_size、keyword,并检查location经纬度顺序与逗号拼接。
练习
在响应拦截器中补充逻辑:当
data.code === 401时,通过getApp()读取并打印refreshToken,同时保证不会影响正常响应返回。补全“无感请求”代码:刷新 token 成功后,基于原
config重发请求,并确保新Authorization覆盖旧值,最后返回重发结果。实现
refreshToken过期处理:当请求路径包含/refreshToken且业务码为 401 时,跳转登录页,并携带当前页面路径作为redirectURL。使用腾讯位置服务 SDK:根据用户经纬度搜索周边 5 个“住宅小区”,只保留
id、title、_distance,并渲染到页面列表。检查并补全
app.json:声明getLocation、chooseLocation所需配置,并添加用户位置权限说明。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 享+生活-刷新token-1 | 06:21 |
| 2 | 享+生活-刷新token-2 | 07:06 |
| 3 | 享+生活-无感请求 | 06:55 |
| 4 | 享+生活-重新登录 | 09:12 |
| 5 | 享+生活-添加房屋(位置服务)-1 | 08:07 |
| 6 | 享+生活-添加房屋(位置服务)-2 | 09:31 |
| 7 | 享+生活-添加房屋(位置服务)-3 | 10:11 |
| 8 | 享+生活-添加房屋(位置服务)-4 | 09:22 |
小结
- 刷新 token 的入口应统一放在响应拦截器,判断依据是业务码
401。 refreshToken必须提前从本地存储读取到应用实例,否则刷新逻辑拿不到凭证。- 刷新接口的鉴权头使用
refreshToken,不是已失效的普通token。 - 无感请求的关键是复用原请求
config,替换新 token 后重发并返回结果。 refreshToken也失效时必须终止刷新链路,跳转登录并保留回跳地址。- 位置服务要先完成 key、合法域名、隐私接口声明等准备工作。
- 地图 SDK 返回数据通常需要二次过滤,页面只渲染业务需要的字段。
- 位置相关 API 在开发环境可用不代表上线可用,权限与类目需提前确认。