Skip to content

房屋管理(一) ​

本页对应 房屋管理,共 8 段视频,约 1 小时 6 分。

学习目标 ​

  1. 能在响应拦截器中根据状态码 401 判断 token 失效,并读取、传递 refresh token。
  2. 能调用刷新 token 接口,校验返回状态码,并用新 token 与 refresh token 替换本地及应用实例中的旧值。
  3. 能实现无感请求:获取失败请求的 config,替换 Authorization 中的 token,重新发起请求并返回响应结果。
  4. 能判断 refresh token 过期条件,获取当前页面路径,跳转登录页,并在登录成功后回跳原页面。
  5. 能完成腾讯位置服务接入:配置合法域名、声明位置权限、引入 SDK、使用 key 实例化服务。
  6. 能获取用户经纬度,调用地点搜索查询周边住宅小区,限制返回条数、处理失败回调、过滤字段并渲染到页面。

知识点 ​

刷新 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>

课件图示 ​

课件第5页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

课件第6页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

课件第3页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

课件第4页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

代码示例 ​

示例一:刷新 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 取值。

常见错误 ​

  1. 现象:接口返回 401 但拦截器没进刷新逻辑
    原因: 用 HTTP 状态码判断,实际业务状态码在 data.code。
    修法: 在响应拦截器中判断 data.code === 401。

  2. 现象:401 后 app.refreshToken 为 undefined
    原因: app.js 只读取了 token,未读取 refreshToken,或未调用 getToken()。
    修法: 在 onLaunch 中读取本地 token 与 refreshToken 并挂到应用实例。

  3. 现象:刷新 token 接口返回鉴权失败
    原因: /refreshToken 请求头误用旧 token,或 Bearer 后少空格。
    修法: 使用 Authorization: 'Bearer ' + app.refreshToken。

  4. 现象:刷新成功后原接口仍拿不到数据
    原因: 只保存新 token,未用原 config 重发请求,或重发时未替换旧 Authorization。
    修法: 用新 token 覆盖 config.header.Authorization 后 return http(config)。

  5. 现象:refreshToken 也过期时反复请求、死循环或页面卡住
    原因: 未识别当前请求就是 /refreshToken 且返回 401。
    修法: 先判断 config.url.includes('/refreshToken'),命中后重定向登录页并 return。

  6. 现象:登录成功后回不到原页面
    原因: 跳登录页时未带 redirectURL,或 route 前少拼 /。
    修法: 从 getCurrentPages() 取当前页 route,拼接后传给登录页。

  7. 现象:wx.getLocation 调用失败或上线审核不通过
    原因: app.json 未声明 requiredPrivateInfos/permission,或类目权限未申请。
    修法: 补齐 getLocation、chooseLocation 声明与 scope.userLocation 描述,并按后台要求申请权限。

  8. 现象:QQMap.search 返回条数不对或报参数错误
    原因: page_size 拼写错误,或 keyword 被误写成 keywords。
    修法: 按文档使用 page_size、keyword,并检查 location 经纬度顺序与逗号拼接。

练习 ​

  1. 在响应拦截器中补充逻辑:当 data.code === 401 时,通过 getApp() 读取并打印 refreshToken,同时保证不会影响正常响应返回。

  2. 补全“无感请求”代码:刷新 token 成功后,基于原 config 重发请求,并确保新 Authorization 覆盖旧值,最后返回重发结果。

  3. 实现 refreshToken 过期处理:当请求路径包含 /refreshToken 且业务码为 401 时,跳转登录页,并携带当前页面路径作为 redirectURL。

  4. 使用腾讯位置服务 SDK:根据用户经纬度搜索周边 5 个“住宅小区”,只保留 id、title、_distance,并渲染到页面列表。

  5. 检查并补全 app.json:声明 getLocation、chooseLocation 所需配置,并添加用户位置权限说明。

原视频对照 ​

#视频时长
1享+生活-刷新token-106:21
2享+生活-刷新token-207:06
3享+生活-无感请求06:55
4享+生活-重新登录09:12
5享+生活-添加房屋(位置服务)-108:07
6享+生活-添加房屋(位置服务)-209:31
7享+生活-添加房屋(位置服务)-310:11
8享+生活-添加房屋(位置服务)-409:22

小结 ​

  1. 刷新 token 的入口应统一放在响应拦截器,判断依据是业务码 401。
  2. refreshToken 必须提前从本地存储读取到应用实例,否则刷新逻辑拿不到凭证。
  3. 刷新接口的鉴权头使用 refreshToken,不是已失效的普通 token。
  4. 无感请求的关键是复用原请求 config,替换新 token 后重发并返回结果。
  5. refreshToken 也失效时必须终止刷新链路,跳转登录并保留回跳地址。
  6. 位置服务要先完成 key、合法域名、隐私接口声明等准备工作。
  7. 地图 SDK 返回数据通常需要二次过滤,页面只渲染业务需要的字段。
  8. 位置相关 API 在开发环境可用不代表上线可用,权限与类目需提前确认。