# 第12次课 讲稿

> 课程：小程序开发
> 主题：地图定位与网络通信
> 教材：《微信小程序开发实战（第2版）》第5章
> 时长：3学时（理论 1 + 实践 2）
> 重点：地图组件、定位API、WebSocket实时通信
> 难点：地图标记与交互、WebSocket连接管理

---

## 一、开场（0:00）

今天是第5章的下半部分，也是常用 API 的收尾：地图定位和 WebSocket。

这两个东西的共同特点是——**它们都不是"请求一次拿一次结果"的模式**。

地图定位涉及授权、坐标系、逆地理编码，链条比较长。WebSocket 更特殊：它是**长连接**，建立以后双方随时可以互相发消息，不像 wx.request 那样一问一答。聊天、实时报价、协同编辑，都得靠它。

先提醒一个必踩的坑：**坐标系**。GPS 给的是 wgs84 坐标，国内地图用的是 gcj02 火星坐标，两者差几百米。用错了会出现"定位显示我在马路对面"的经典问题。

---

## 二、明确本课目标（约 5:00）

把目标写在白板上或直接翻到课件第 2 页，逐条念一遍：

- 掌握地图组件的使用
- 理解定位API与逆地理编码
- 掌握WebSocket实时通信
- 能够实现位置相关功能
- 案例：案例5-3 查看附近的美食餐厅、案例5-4 在线聊天

本次课 3 学时的安排是：**理论讲解 1 学时，上机实践 2 学时**——地图与 WebSocket 讲解 1 学时，案例 5-3/5-4 实操 2 学时。

---

## 三、知识点讲解

### 幻灯片 3：地图组件

**【8:00 讲解】**

**<map> 组件**

原生地图展示

**longitude / latitude**

中心经纬度

**scale**

缩放级别(3-20)

**markers**

标记点数组

**bindmarkertap**

标记点击事件

**bindregionchange**

视野变化事件


### 幻灯片 4：定位API

**【13:00 讲解】**

**wx.getLocation**

获取当前位置(需授权)

- type: wgs84(GPS坐标) / gcj02(国测局坐标)
**wx.openLocation**

打开微信内置地图导航

- scope.userLocation 授权管理
- 逆地理编码: 需调用第三方API(腾讯/高德地图)

### 幻灯片 5：WebSocket实时通信

**【18:00 讲解】**

**wx.connectSocket**

创建WebSocket连接

**wx.onSocketOpen**

连接打开

**wx.sendSocketMessage**

发送消息

**wx.onSocketMessage**

接收消息

**wx.closeSocket / wx.onSocketClose**

关闭连接


### 幻灯片 6：案例5-3 查看附近的美食餐厅 / 案例5-4 在线聊天

**【23:00 讲解】**

**案例5-3 查看附近的美食餐厅**

getLocation 定位 → map markers 打点 → openLocation 导航

**案例5-4 在线聊天**

connectSocket 建连，onSocketMessage 收消息，sendSocketMessage 发消息

- markers 数组字段：id / latitude / longitude / iconPath / callout
- 断线重连：在 onSocketClose 中延时重连，并设置重连次数上限
- 综合运用地图与 WebSocket 完成两个教材案例

---

## 四、难点突破：用类比讲清楚

下面这几个点是学生最容易卡住的地方，抽象讲不通就换成类比。

### HTTP 请求 vs WebSocket

HTTP 像寄信：你写一封，对方回一封，每次都要重新贴邮票封信封。WebSocket 像打电话：拨通一次，之后想说就说，对方也能随时插话，挂断才结束。

### 两套坐标系

像同一个地址用了两套门牌号系统。wgs84 是国际标准（GPS 原始值），gcj02 是国内加密偏移后的值。国内地图组件按 gcj02 渲染，所以调 getLocation 时 type 要传 gcj02，否则点会偏。

### 逆地理编码

拿到经纬度只是"31.23, 121.47"，用户看不懂。要变成"上海市黄浦区南京东路"，需要查一张巨大的对照表——这张表微信没有，得调腾讯位置服务或高德的接口。这就是为什么这一步需要额外申请 key。

### 断线重连

像通话中信号断了要重播。但不能一断就疯狂重播，应该间隔递增（1秒、2秒、4秒……）并设上限，否则弱网环境下会把服务器打爆。这个策略叫指数退避。

---

## 五、代码演示环节

以下每一段都建议**现场敲**，不要直接贴完整代码。边敲边问"下一行该写什么"，让学生跟上节奏。

### 演示 1：地图组件

**文件：** `map-component.wxml`　**预期效果：** 地图组件效果

```html
<!-- map 地图组件 -->
<map
id="myMap"
longitude="113.324520"
latitude="23.099994"
scale="14"
markers="{{markers}}"
show-location
style="width: 100%; height: 300px;"
bindmarkertap="onMarkerTap"
bindregionchange="onRegionChange"
></map>

// markers 标记点数据
Page({
data: {
markers: [{
id: 1,
longitude: 113.324520,
latitude: 23.099994,
title: '广州塔',
iconPath: '/images/marker.png',
width: 30,
height: 30,
callout: {
content: '广州塔\n高度:600米',
color: '#333',
fontSize: 14,
borderRadius: 8,
bgColor: '#ffffff',
display: 'ALWAYS'
}
}]
},

onMarkerTap(e) {
console.log('点击标记:', e.markerId)
},

onRegionChange(e) {
if (e.type === 'end') {
console.log('视野变化', e.causedBy)
}
}
})
```

**讲解口径：**

小程序 **map** 组件用于展示地图，核心属性：**longitude** 和 **latitude** 设置地图中心点经纬度，**scale** 控制缩放级别，**markers** 数组定义标记点，每个标记包含 id、经纬度、标题、图标和 **callout** 气泡窗。**bindmarkertap** 监听标记点击，**bindregionchange** 监听视野变化。show-location 属性显示当前定位蓝点。

### 演示 2：定位API

**文件：** `location-api.js`　**预期效果：** 定位API流程

```js
// 获取当前位置
Page({
data: { location: {} },

getLocation() {
wx.getLocation({
type: 'gcj02',
altitude: 'true',
success: (res) => {
const { latitude, longitude, speed, accuracy } = res
this.setData({ location: res })
console.log(`纬度:${latitude}, 经度:${longitude}`)
console.log(`速度:${speed}, 精度:${accuracy}`)
}
})
},

// 打开微信内置地图查看位置
openLocation() {
wx.openLocation({
latitude: 23.099994,
longitude: 113.324520,
name: '广州塔',
address: '广东省广州市海珠区',
scale: 18
})
},

// ⚠️ 坐标系说明
// wgs84: GPS原始坐标（国际标准）
// gcj02: 国测局坐标（中国标准，有偏移）
// map组件和openLocation需要gcj02坐标

// app.json 必须声明权限
// "requiredPrivateInfos": [
//   "getLocation", "chooseLocation"
// ]
// "permission": {
//   "scope.userLocation": {
//     "desc": "你的位置信息将用于小程序位置接口的效果展示"
//   }
// }
})
```

**讲解口径：**

定位 API：**wx.getLocation** 获取当前位置，type 参数指定坐标系，**gcj02** 是国测局坐标有偏移处理，map 组件和 openLocation 需要此坐标系；**wgs84** 是 GPS 原始坐标。返回纬度、经度、速度和精度。**wx.openLocation** 打开微信内置地图查看指定位置，可设置名称和地址。使用定位功能需在 app.json 声明 **requiredPrivateInfos** 和 permission 权限。

### 演示 3：WebSocket实时通信

**文件：** `websocket.js`　**预期效果：** WebSocket 通信流程

```js
// WebSocket 实时通信
Page({
data: { messages: [], inputVal: '' },

onLoad() {
this.connectSocket()
},

// 建立连接
connectSocket() {
wx.connectSocket({
url: 'wss://example.com/chat',
header: { 'content-type': 'application/json' },
success() { console.log('连接请求已发送') }
})

// 监听连接打开
wx.onSocketOpen((res) => {
console.log('WebSocket连接已打开')
this.socketOpen = true
})

// 监听服务器消息
wx.onSocketMessage((res) => {
const msg = JSON.parse(res.data)
this.setData({ messages: [...this.data.messages, msg] })
})

// 监听连接关闭
wx.onSocketClose(() => {
console.log('WebSocket已断开')
this.socketOpen = false
})
},

// 发送消息
sendMessage() {
if (!this.socketOpen) return
wx.sendSocketMessage({
data: JSON.stringify({
type: 'chat',
content: this.data.inputVal,
time: new Date().getTime()
})
})
this.setData({ inputVal: '' })
},

onUnload() {
wx.closeSocket()
}
})
```

**讲解口径：**

WebSocket 实现双向实时通信：**wx.connectSocket** 建立连接，url 必须是 **wss://** 协议；**wx.onSocketOpen** 监听连接成功；**wx.onSocketMessage** 监听服务器推送的消息，收到后 JSON.parse 解析并更新列表；**wx.sendSocketMessage** 发送消息，data 为字符串格式；**wx.onSocketClose** 监听断开。注意小程序同时只能存在一个 WebSocket 连接，页面卸载时需 **closeSocket** 关闭。

### 演示 4：案例5-3地图定位+案例5-4在线聊天

**文件：** `map-chat.js`　**预期效果：** 地图定位 & 在线聊天

```js
// 案例5-3：地图定位 map-location.js
Page({
data: { latitude: 23.099994, longitude: 113.324520, markers: [] },

onLoad() {
this.moveToLocation()
},

moveToLocation() {
wx.getLocation({
type: 'gcj02',
success: (res) => {
this.setData({
latitude: res.latitude,
longitude: res.longitude,
markers: [{
id: 1,
latitude: res.latitude,
longitude: res.longitude,
iconPath: '/images/my.png',
width: 30,
height: 30,
title: '我的位置'
}]
})
}
})
},

goToNav() {
wx.openLocation({
latitude: this.data.latitude,
longitude: this.data.longitude
})
}
})

// 案例5-4：在线聊天 chat.js（核心逻辑）
Page({
data: { messages: [], inputVal: '' },

onLoad() {
this.connect()
},

connect() {
wx.connectSocket({ url: 'wss://chat.example.com' })
wx.onSocketOpen(() => { this.connected = true })
wx.onSocketMessage((res) => {
const msg = JSON.parse(res.data)
this.setData({ messages: [...this.data.messages, msg] })
})
wx.onSocketClose(() => {
// 自动重连
setTimeout(() => this.connect(), 3000)
})
},

send() {
if (!this.connected || !this.data.inputVal) return
wx.sendSocketMessage({
data: JSON.stringify({ content: this.data.inputVal })
})
this.setData({ inputVal: '' })
}
})
```

**讲解口径：**

案例5-3地图定位：调用 **wx.getLocation** 获取 gcj02 坐标，setData 更新地图中心点和标记点，**openLocation** 打开导航。案例5-4在线聊天：用 **connectSocket** 建立 wss 连接，**onSocketMessage** 接收消息并追加到列表，**sendSocketMessage** 发送消息。关键点：断线后通过 **onSocketClose** 监听并 setTimeout 3秒后自动重连，页面卸载时 closeSocket 关闭连接。

---

## 六、常见错误与排查

上机环节学生一定会遇到下面这些问题，提前讲一遍能省掉大量重复答疑。

| # | 现象 | 原因与解决 |
|:-:|------|------------|
| 1 | 定位点偏移几百米 | type 传了 wgs84 却在 map 组件上显示。国内一律用 gcj02。 |
| 2 | getLocation 直接失败 | 需要在 app.json 里声明 requiredPrivateInfos，并配置隐私协议，否则新版基础库直接拒绝。这是近年新增的合规要求。 |
| 3 | markers 不显示 | marker 的 id 必须是数字类型，iconPath 必须是本地或 https 图片，width/height 不设会用原图尺寸。 |
| 4 | map 组件被其他元素盖住 | map 是原生组件，层级最高。想在它上面放东西要用 cover-view / cover-image，普通 view 会被压在下面。 |
| 5 | WebSocket 消息收不到 | 要在 connectSocket 之后、onSocketOpen 之内才能发消息。在连接建立前调 sendSocketMessage 会失败。 |
| 6 | 页面卸载后连接还在 | onUnload 里必须 closeSocket，否则切页面后还在收消息，且重进会建立第二个连接。 |

---

## 七、上机实践指引

本次课对应教材案例 2 个，源码已放在站点 `code/session12/` 下：

- **【案例5-3】查看附近的美食餐厅** — 含 知识储备、案例实现 共 2 个工程
- **【案例5-4】在线聊天** — 含 知识储备、案例实现、服务器端 共 3 个工程

建议顺序：先带学生跑一遍「知识储备」里的最小示例，确认每个知识点都能单独跑通，再进「案例实现」做完整案例。直接上完整案例，出错了不知道错在哪。

---

## 八、本课小结

回到课件小结页，逐条回顾：

- 地图组件
- 定位API
- WebSocket实时通信
- 案例5-3地图定位+案例5-4在线聊天

第4、5两章的 API 到这里讲完了。回头看，这些 API 的使用套路高度一致：**申请授权 → 创建实例/发起调用 → 监听回调 → 用完释放**。

案例5-3 和 5-4 做完，你就具备了做 LBS 应用和即时通讯应用的基本能力。下次课开始进综合项目，把前面学的全部串起来。

---

## 九、布置作业

三档任务，基础必做，进阶建议做，挑战选做。

### 【基础】完成附近美食

按照案例5-3，实现定位与地图标注功能，在地图上显示附近美食

### 【进阶】完成在线聊天

按照案例5-4，使用WebSocket实现实时聊天功能

### 【挑战】位置共享

结合地图和WebSocket，实现多人位置实时共享，在地图上显示所有在线用户的位置标记

---

> 配套资源：课件 `slides-v2/session12.pptx`　课堂视频 `videos-output/session12.mp4`　扩展阅读 `docs/第12次课_扩展阅读.md`
