用 Node.js + 微信小程序实现「附近门店」功能,含完整代码
·
前言:这个“附近门店”功能,其实没你想的那么简单
做本地生活类产品时,“附近门店”几乎是标配功能:
- 用户打开页面自动定位
- 展示附近门店列表
- 按距离排序
- 可在地图上查看位置
- 一键导航
听起来非常简单,甚至很多人觉得:
“调个地图 API 就行了”
但真正上线之后我们发现:
这个功能的复杂度,远不在“实现”,而在“位置数据链路”。
一、整体实现流程
整个系统分为两部分:
小程序端
- 获取用户 GPS
- 请求后端获取门店列表
- 渲染列表 + 地图标注
Node.js 后端
- 接收用户坐标
- 调用 POI 搜索接口
- 调用逆地理编码
- 返回标准化数据
二、整体架构
小程序
↓
Node.js 后端
↓
位置服务层(POI / 逆解析 / 坐标转换)
↓
地图服务 Provider
三、小程序端实现
1. 获取用户位置
// pages/nearby/nearby.js
Page({
data: {
stores: [],
markers: [],
userLat: null,
userLng: null,
keyword: '',
currentAddress: '',
loading: false,
},
onLoad() {
this.getUserLocation()
},
getUserLocation() {
wx.getLocation({
type: 'gcj02',
success: (res) => {
const { latitude, longitude } = res
this.setData({
userLat: latitude,
userLng: longitude,
})
this.fetchNearbyStores(latitude, longitude, '')
this.getReverseGeocode(latitude, longitude)
},
fail: () => {
wx.showToast({
title: '请开启定位权限',
icon: 'none',
})
},
})
},
2. 拉取附近门店
async fetchNearbyStores(lat, lng, keyword = '') {
this.setData({ loading: true })
wx.request({
url: 'https://your-server.com/api/nearby-stores',
method: 'POST',
data: {
lat,
lng,
radius: 3000,
keyword,
},
success: (res) => {
const stores = res.data.list || []
this.setData({
stores,
markers: this.buildMarkers(stores),
})
},
complete: () => {
this.setData({ loading: false })
},
})
},
3. 逆地理编码
getReverseGeocode(lat, lng) {
wx.request({
url: 'https://your-server.com/api/reverse-geocode',
method: 'POST',
data: { lat, lng },
success: (res) => {
this.setData({
currentAddress: res.data.shortAddress,
})
},
})
},
4. 构建地图标注
buildMarkers(stores) {
return stores.map((store, index) => ({
id: index,
latitude: store.lat,
longitude: store.lng,
title: store.name,
width: 30,
height: 30,
callout: {
content: `${store.name}\n${store.distanceText}`,
display: 'BYCLICK',
},
}))
},
})
四、小程序页面结构
<view class="container">
<view class="location">
📍 {{currentAddress || '定位中...'}}
</view>
<map
latitude="{{userLat}}"
longitude="{{userLng}}"
markers="{{markers}}"
scale="15"
style="width:100%;height:400rpx"
/>
<input
placeholder="搜索门店"
bindinput="onSearchInput"
value="{{keyword}}"
/>
<scroll-view scroll-y>
<view
wx:for="{{stores}}"
wx:key="id"
bindtap="onStoreClick"
>
<view>{{item.name}}</view>
<view>{{item.address}}</view>
<view>距您 {{item.distanceText}}</view>
</view>
</scroll-view>
</view>
五、Node.js 后端实现
1. 基础服务
npm install express axios dotenv
require('dotenv').config()
const express = require('express')
const axios = require('axios')
const app = express()
app.use(express.json())
const BASE = 'https://lts.maiyun.net/api/service'
const HEADERS = {
Authorization: `Bearer ${process.env.LTS_API_KEY}`,
}
六、附近门店 API
app.post('/api/nearby-stores', async (req, res) => {
const { lat, lng, radius = 3000, keyword = '' } = req.body
const { data } = await axios.post(
`${BASE}/poi/search`,
{
keyword,
location: { lat, lng },
radius,
page: 1,
pageSize: 20,
},
{ headers: HEADERS }
)
const list = (data.list || []).map((poi) => {
const distance = poi.distance || 0
return {
id: poi.id,
name: poi.name,
address: poi.address,
lat: poi.point.lat,
lng: poi.point.lng,
distanceText:
distance > 1000
? `${(distance / 1000).toFixed(1)}km`
: `${distance}m`,
}
})
res.json({ list })
})
七、逆地理编码
app.post('/api/reverse-geocode', async (req, res) => {
const { lat, lng } = req.body
const { data } = await axios.post(
`${BASE}/geocode`,
{
from: 3,
point: { lat, lng },
},
{ headers: HEADERS }
)
const ctx = data.address?.context || []
const district = ctx.find((c) => c.type === 'district')?.name || ''
const township = ctx.find((c) => c.type === 'township')?.name || ''
res.json({
shortAddress: `${district}${township}`,
})
})
八、关键优化点(非常重要)
1. 不要在前端调用地图 API
所有位置能力必须走后端:
- 防止 Key 泄露
- 防止被刷接口
- 方便统一缓存
2. POI 必须做缓存
const cache = new Map()
function getCacheKey(lat, lng, keyword) {
return `${lat.toFixed(3)}_${lng.toFixed(3)}_${keyword}`
}
3. 逆解析不要每次请求都调用
建议在“写入阶段”解析一次即可。
九、架构演进(关键部分)
当业务放大后,我们做了一次结构调整:
业务系统
↓
Location Service(统一位置层)
↓
LTS / 地图服务 Provider
这样做之后:
- 调用量下降约 60%+
- 代码重复逻辑消失
- 多地图 SDK 混用问题消失
- 后续切换服务商成本极低
十、为什么最后选择 LTS
在位置服务抽象完成后,我们评估 Provider 时主要看三点:
- POI 覆盖能力
- 逆地理编码准确性
- 接入成本
最终选择 LTS 的原因很简单:
它提供的是“位置能力层”,而不是单一地图 API。
对我们这种已经做了位置中台的系统来说: 切换成本几乎为 0
写在最后
“附近门店”功能看起来简单,但本质上它涉及三层能力:
- 定位
- POI搜索
- 地理编码
如果不做抽象,很容易变成:
到处调用地图 API,到处重复计算,到处埋坑。
真正的优化不是“换一个更便宜的API”,而是:
让位置能力变成统一服务,而不是散落在业务里的调用。
如果你后续要做同类系统:
- 顺风车
- 同城配送
- 外勤管理
- 门店系统
建议一开始就考虑“位置中台”,否则后期重构成本会非常高。
如果需要了解服务商信息,可以去直接访问 迈云LTS
更多推荐
所有评论(0)