背景
心情手账日历是一款记录每日心情的微信小程序,最近想加入地理打卡功能,顺便把实现过程中踩的坑记下来。这个项目是我独立开发的,所有决策和实现都是一个人摸索完成。
阶段一:模糊定位
起初没有配置隐私声明,只能申请wx.getFuzzyLocation模糊定位。我用uni-app开发,因此通过uni.getFuzzyLocation获取终端的粗略位置。该接口只返回经纬度,不包含地址描述,需要后端调用地图服务做逆地理编码。
uni.getFuzzyLocation({
type: 'gcj02',
success: (pos) => {
// pos = { latitude: 39.90, longitude: 116.40 }
// 只有经纬度,没有城市/区县等文字
// 需要后端 geocode API → "北京市朝阳区望京街道"
}
})
局限:虽然能拿到坐标,但精度较低、偏差明显,难以在地图上精确标记。
阶段二:前端直调地图SDK
拿到经纬度后,需要将其转换为人类可读的地址描述,总不能直接展示火星坐标系下的经纬度给用户。
一开始我直接在前端接入腾讯地图JS SDK(qqmap-wx-jssdk)做逆地理编码:
import QQMapWX from 'qqmap-wx-jssdk'
const qqmap = new QQMapWX({
key: import.meta.env.VITE_TENCENT_MAP_KEY as string,
})
uni.getLocation({
type: 'gcj02',
success: (pos) => {
qqmap.reverseGeocoder({
location: { latitude: pos.latitude, longitude: pos.longitude },
success: (res) => {
const addr = res.result.address
const city = res.result.address_component.city
}
})
}
})
问题:地图Key会直接暴露在前端代码中,安全风险很大。
阶段三:逆地理编码迁移到后端
为了把地图服务商的Key藏起来,我把逆地理编码挪到了后端:
async fn geocode(query, config) -> Result<HttpResponse> {
// 调用腾讯地图 WebService API,Key 放在后端环境变量
let resp = client.get("https://apis.map.qq.com/ws/geocoder/v1/")
.query(&[("location", format!("{},{}", lat, lng)), ("key", config.tencent_map_key)])
.send().await?;
// ...
}
收益:地图服务商的Key只保留在后端,前端无法获取;代价是多了一次网络请求。
阶段四:尝试精确定位
为了进一步提升打卡位置的精度和体验,我尝试申请wx.getLocation精确定位权限:
uni.getLocation({
type: 'gcj02',
isHighAccuracy: true, // GPS 级精度
success: (pos) => {
// 后端逆地理编码 → "北京市朝阳区建国路 93 号"
}
})
审核结果:被拒
反复申请均被驳回。查阅社区资料后发现,微信对个人开发者审核较严,心情记录这类应用不属于“强位置依赖”场景,getLocation权限基本无望,于是我放弃了这个方案。
阶段五(当前):模糊定位 + 手动选点
精确定位这条路走不通,我便采用了折中方案:若自动定位不准确,允许用户自行手动调整位置。于是转而申请了wx.chooseLocation,该权限相对容易通过审核。
现在的流程如下:
用户点击“添加位置”
├── 自动定位 → getFuzzyLocation(返回经纬度)→ 后端 geocode API 逆地理编码 → 地址文本
└── 手动选择 → chooseLocation → 地图选点,直接返回坐标 + 地址
自动定位:通过
getFuzzyLocation获取坐标,后端逆地理编码返回“南京市夫子庙大门口”级别的地址;手动选点:用户在地图上自行点选精确位置,
chooseLocation属于用户主动行为,审核宽松不少。
数据模型中的经纬度字段设计为可空。无论是自动定位还是手动选点,只要成功获取了位置,都会写入坐标;当用户定位失败或取消选点操作时,字段保持为 null。
interface MoodRecord {
location: string // 地址文本(自动定位:后端逆地理编码返回;手动选择:chooseLocation 直接提供)
locationCity: string // 城市名
latitude: number | null // 可为空;自动或手动定位成功时写入,定位失败/取消选点时为 null
longitude: number | null
}