尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

高德地图个人开发者Key与安全密钥Vue接入实战

高德地图个人开发者Key与安全密钥Vue接入实战 1. 先把钥匙配好高德地图个人开发者Key的完整创建流程很多人写地图功能卡住的地方不是写不出new AMap.Map()而是卡在第一步——Key 没搞对。项目标题里高德地图个人开发者key以及vue中使用这两件事其实是一前一后强绑定的关系Key 是门禁卡Vue 里的地图组件是刷卡的闸机。门禁卡发错了类型闸机怎么刷都是红灯。我带过的几个前端新人十个里有七个第一次跑地图白屏最后查出来全是 Key 的平台类型选错了或者漏配了安全密钥。所以这一章我打算把开放平台的注册、实名、创建应用、Key 类型选择、安全密钥这几步从头捋一遍全部按个人开发者这个身份来讲——因为个人账号和企业账号在配额、可用服务、认证材料上确实有不少差别尤其是个人账号的日调用量限制做 demo 够用做正式产品就得提前规划。如果你只是想本地跑个地图 demo、做个毕业设计、或者给公司内部做个小工具个人 Key 完全够用下面这套流程你直接照着走就行。1.1 注册账号与实名认证个人开发者的两条路径打开高德开放平台的官网用手机号注册一个账号这一步没什么好说的。真正决定后面能创建几种 Key 的是接下来的认证环节。高德这边把开发者身份分成个人开发者和企业开发者两类个人开发者只需要完成实名认证也就是身份证信息加人脸或者短信核验几分钟就能过。企业开发者则要上传营业执照、对公验证流程长很多但拿到的配额和可调用服务也多。这里有个容易被忽略的点认证主体决定了你账号下所有应用的归属一旦用个人身份认证了后面想升级成企业主体是要重新走一套流程的早期创建的应用和 Key 不会自动迁移。所以如果你现在是在公司做项目别图省事用自己身份证认证否则将来交接会很麻烦。反过来纯自己练手个人认证是最快的路子。另外提醒一句实名认证完成后不是立刻全量开放所有能力部分服务比如逆地理编码、路径规划这类 Web 服务接口个人账号的日调用量是有限额的具体数值官方会调整你在控制台的服务详情里能查到实时数据。做技术选型的时候先把这几个限额看一眼能省掉不少后期返工。1.2 创建应用与Key类型选错后面全是坑认证过了之后进控制台的应用管理点创建新应用。应用名称随便起比如my-vue-map应用类型按实际场景选。注意一个应用下面可以创建多个 Key每个 Key 绑定一个服务平台这是高德的设计逻辑不是一个应用一个 Key。这一点很多人理解错了导致后面想在同一个项目里同时用 JS API 和 Web 服务接口时拿同一个 Key 去调结果报平台不匹配。创建 Key 的时候会让你选服务平台下拉框里大概有这些服务平台典型用途你什么时候需要它Web端 (JS API)浏览器里渲染地图、覆盖物、交互Vue/React 页面里显示地图Web服务后端调用的 HTTP 接口如地理编码、路径规划服务端做地址转坐标Android 平台安卓原生 SDK安卓 App 里嵌地图iOS 平台iOS 原生 SDK苹果 App 里嵌地图微信小程序小程序端 SDK小程序里嵌地图做 Vue 项目你就选Web端 (JS API)。别选成Web服务那玩意儿是给你 Node 后端调 HTTP 用的放在前端渲染地图会直接报错。个人账号每种平台类型的 Key 数量也是有限的一般够用但别乱建建完记不住哪个是哪个。创建完 Key 之后你会在列表里看到一串 32 位的字符串这就是你的 Key业内也叫 apiKey。同一页面上还有一个安全密钥securityJsCode这玩意儿是新版 JS API 绕不开的东西下一小节专门说。1.3 安全密钥jscode新版Key绕不开的一步2021 年 12 月之后新申请的 JS API Key高德强制要求搭配安全密钥使用。原因也简单纯前端 Key 是暴露在浏览器里的谁打开 F12 都能抄走配上安全密钥相当于加了一层校验让 Key 不能被人随便盗用到别的域名上。安全密钥的配置有两种方式我按开发环境和生产环境分开讲因为这两种场景的正确做法完全不一样方式一明文直填仅限本地开发。在引入高德 JS API 之前往 window 上挂一个全局配置window._AMapSecurityConfig { securityJsCode: 你申请到的安全密钥, }这个写法简单直接本地跑 demo 最省事。但它的致命问题是安全密钥和 Key 一样暴露在前端代码里打包上线等于把门锁和钥匙一起贴在门上。所以绝对不能用在生产环境。方式二代理转发生产环境推荐。把serviceHost指向你自己的服务器地址所有请求先到你自己的后端由后端补上安全密钥再转发给高德window._AMapSecurityConfig { serviceHost: https://your-domain.com/_AMapService, }后端那边做一层反向代理把_AMapService路径的请求转到高德的接口上并在转发时补上jscode参数。这么绕一圈浏览器里就看不到安全密钥了安全性高一个量级。具体代理配置我在第二章会给出可直接抄的写法。注意安全密钥和 Key 是配套的一对一关系换了 Key 就得换密钥。如果你看到控制台报INVALID_USER_SCODE或者10009这类错误先别怀疑代码八成是密钥没配或者配错了。2. Vue项目接入前的准备与选型为什么我推荐官方loaderKey 拿到手接下来就是 Vue 这边的接入。市面上能往 Vue 里塞地图的办法不止一种我看到过的就有直接改 index.html 引 script 标签的、用 iframe 嵌一个现成地图页的、还有用第三方封装库的。这些做法能不能跑能。但维护起来的体验差别很大。这一章我把几种方案的取舍讲清楚然后落到官方amap/amap-jsapi-loader的具体配置上顺便把环境变量、TypeScript 类型这些细节一并交代。2.1 三种接入方式的取舍原生 script 标签引入是最原始的做法在index.html里加一行script srchttps://webapi.amap.com/maps?v2.0keyxxx/script然后代码里直接用全局的AMap。好处是简单坏处是把 Key 硬编码在 HTML 里而且脚本加载时机不受 Vue 控制组件挂载时AMap可能还没就绪容易出时序 bug。Vite 或者 Webpack 项目里这么写还得处理构建时对全局变量的引用问题。iframe 嵌地图适用于我只要展示一个静态地图不需要交互的场景。比如后台首页放个位置预览图iframe 是最省事的。但一旦你要加自定义点标记、响应点击事件、做图层切换iframe 就完全不够用了因为你拿不到里面地图实例的引用。官方 loader也就是amap/amap-jsapi-loader是我在 Vue 项目里用得最多的一种。它的核心价值是把脚本加载这件事变成了一个 Promiseawait AMapLoader.load({...})返回的就是AMap命名空间对象加载时机完全由你控制配合 Vue 的onMounted用得特别顺手。而且它是按需加载的你声明了哪些插件它才去加载哪些插件不会把整个地图 SDK 一次性拉下来。提示loader 内部其实有缓存机制同一个页面里多次调用load不会重复请求脚本但参数不一致时会给出警告。所以最好把地图实例封装成单例或者独立的 composable别在每个子组件里各调一次。2.2 环境准备与依赖安装先把依赖装上我用的是 npmpnpm 和 yarn 的命令也一并列出来# npm npm install amap/amap-jsapi-loader --save # pnpm pnpm add amap/amap-jsapi-loader # yarn yarn add amap/amap-jsapi-loader如果你的项目是 TypeScript 的强烈建议再装一个类型声明包不然AMap.Map、AMap.Marker这些全是 any写起来没提示、重构起来心惊胆战npm install amap/amap-jsapi-types --save-dev装完之后在tsconfig.json的types数组里加上amap/amap-jsapi-types编辑器里立刻就能补全。Key 的存放位置这块我的习惯是放进环境变量而不是硬编码在组件里。Vite 项目在根目录建.env.development和.env.production# .env.development VITE_AMAP_KEY你的开发环境Key VITE_AMAP_SECURITY_CODE你的开发环境安全密钥 # .env.production VITE_AMAP_KEY你的生产环境Key然后代码里用import.meta.env.VITE_AMAP_KEY取。这么做有两个好处一是不同环境可以用不同的 Key方便你在控制台看各自的调用量二是 Key 不会跟着组件代码被复制粘贴到别处排查问题时定位更清晰。注意Vite 的环境变量只有以VITE_开头的才会暴露到客户端代码里其他前缀的会被过滤掉。Webpack 项目里对应的是VUE_APP_前缀。这个前缀记错是新手最常见的变量取不到原因之一。2.3 安全密钥在Vue中的两种挂载时机安全密钥必须在地图脚本加载之前挂到 window 上顺序错了就不生效。我见过不少人把它写在onMounted里、写在 loader.load 之后结果报错找半天。本地开发我一般直接在项目的入口文件main.js/main.ts顶部写// main.js window._AMapSecurityConfig { securityJsCode: import.meta.env.VITE_AMAP_SECURITY_CODE, }生产环境则走代理方案同样在入口处挂serviceHost。后端用一个简单的 Node 中间层举例// server/amap-proxy.js —— 仅作示例实际部署到你的服务器 import express from express import { createProxyMiddleware } from http-proxy-middleware const app express() app.use(/_AMapService, createProxyMiddleware({ target: https://restapi.amap.com, changeOrigin: true, pathRewrite: { ^/_AMapService: }, onProxyReq(proxyReq) { // 在转发时统一补上安全密钥前端不需要知道它 const url new URL(proxyReq.path, https://restapi.amap.com) url.searchParams.set(jscode, process.env.AMAP_SECURITY_CODE) proxyReq.path url.pathname url.search }, })) app.listen(3000)这样前端只需要把serviceHost指向https://your-domain.com/_AMapService即可。整套逻辑其实就是前端不认识密钥后端替你签个名。生产环境和开发环境用不同策略是这套方案里我觉得最关键的一条经验。3. Vue中落地高德地图从初始化到组件封装准备工作做完终于到了写代码的环节。这一章我会按能跑起来的最小示例 → 加上常用覆盖物 → 按需加载插件和图层 → 组件销毁的顺序走一遍代码给的都是我实际项目里改出来的Vue 3 组合式 API 写法为主。如果你用的是 Vue 2 的选项式写法逻辑是一样的把onMounted换成mounted、ref换成data里的变量就行。3.1 Vue3组合式API下的地图初始化先看最核心的一段。新建一个MapContainer.vuetemplate div refmapRef classmap-container/div /template script setup import { ref, shallowRef, onMounted, onUnmounted } from vue import AMapLoader from amap/amap-jsapi-loader const mapRef ref(null) const map shallowRef(null) // 用 shallowRef避免 Vue 深度代理地图实例 const AMap shallowRef(null) async function initMap() { AMap.value await AMapLoader.load({ key: import.meta.env.VITE_AMAP_KEY, version: 2.0, plugins: [AMap.Scale, AMap.ToolBar], }) map.value new AMap.value.Map(mapRef.value, { viewMode: 3D, zoom: 12, center: [116.397428, 39.90923], resizeEnable: true, }) } onMounted(() initMap()) onUnmounted(() { map.value?.destroy() map.value null }) /script style scoped .map-container { width: 100%; height: 500px; } /style这段代码里有三个点值得展开讲。第一为什么用shallowRef而不是ref。地图实例是个层级极深的复杂对象内部挂着大量 DOM 引用和事件监听。如果用ref包装Vue 会尝试递归地把它变成响应式代理一来性能开销大二来某些内部方法会因为this指向被改写而报错。shallowRef只代理最外层不碰内部结构这是官方推荐做法。第二容器的尺寸必须提前确定。高德地图在初始化时会读取容器的offsetWidth和offsetHeight来决定画布大小。如果容器高度是 auto 或者 0地图就会渲染成一条缝甚至完全不显示。所以 CSS 里一定要给死高度或者用 flex 布局时确保父容器有确定高度。第三resizeEnable: true这个配置很值得开。它让地图在窗口尺寸变化时自动重算画布省去了手动监听resize事件再调map.resize()的麻烦。提示开发时如果页面用了路由切换从地图页切到别的页再切回来容器尺寸可能是 0地图会白屏。这种情况在onMounted后加一个nextTick或者在路由的activated钩子里调一次map.resize()通常能解决。3.2 点标记、信息窗体与自定义图标地图能显示了接下来八成要加点标记。这段代码是我在一个门店展示项目里写的逻辑是遍历数据数组每一条生成一个 Marker点击 Marker 弹出对应的信息窗体。function addMarkers(list) { const { Marker, Icon, InfoWindow } AMap.value const infoWindow new InfoWindow({ offset: new AMap.value.Pixel(0, -36), isCustom: false, }) list.forEach((item) { const marker new Marker({ position: [item.lng, item.lat], title: item.name, offset: new AMap.value.Pixel(-13, -30), icon: new Icon({ image: /icons/shop.png, size: [26, 36], imageSize: [26, 36], }), extData: item, // 把业务数据挂在标记上事件里直接取 }) marker.on(click, (e) { infoWindow.setContent( div stylepadding:8px 12px; strong${e.target.getExtData().name}/strong p${e.target.getExtData().address}/p /div ) infoWindow.open(map.value, marker.getPosition()) }) marker.setMap(map.value) }) }翻译一下我对这段的几个想法。extData是 Marker 自带的一个字段用来存任意业务数据比自己在外面维护一个 id 到数据的映射表更省事事件回调里e.target.getExtData()直接就拿回来了。InfoWindow的内容我用了模板字符串拼 HTML简单场景够用如果内容复杂、需要交互建议用isCustom: true配一个自定义 DOM这样能用 Vue 的组件渲染维护性更好。如果要在地图上同时放几百个点逐个new Marker性能会撑不住。这时候要用点聚合插件const { MarkerCluster } AMap.value const cluster new MarkerCluster({ gridSize: 60, maxZoom: 17, averageCenter: true, }) cluster.setMap(map.value) // 批量塞点 cluster.setMarkers(markers)gridSize控制聚合的网格大小数值越大聚合越激进maxZoom表示超过这个层级就不再聚合全部展开显示。这两个参数要根据业务点位的密集程度调我在一个全国网点项目里最后用的是 80 和 15效果比较均衡。3.3 插件按需加载与瓦片图层配置AMapLoader.load的plugins数组就是按需加载的入口。你在代码里用到哪个插件就把它写进去没写的插件运行时调用会报xxx is not a constructor之类的错。常用的几个AMap.Scale左下角比例尺AMap.ToolBar缩放和旋转控件AMap.MarkerCluster点聚合AMap.Geolocation定位AMap.PlaceSearchPOI 搜索AMap.Driving/AMap.Walking路径规划插件之间没有依赖关系但加载插件是有网络开销的别一股脑全写上。按页面实际需要声明能省几百 KB。关于瓦片图层热词里常被提到的高德地图瓦片高德本身提供标准矢量瓦片、卫星瓦片同时也支持你自己叠一层瓦片图层上去。比如你要叠一个内部业务网格、气象图、或者自定义底图可以这么写const tileLayer new AMap.value.TileLayer({ getTileUrl(x, y, z) { return https://your-tile-server/${z}/${x}/${y}.png }, zIndex: 200, opacity: 0.85, }) tileLayer.setMap(map.value)这里要说清楚一个概念瓦片地图的坐标系是 Web MercatorURL 里z/x/y的顺序是层级、行、列不同服务商可能把 x 和 y 的顺序写反你换个瓦片源发现图全乱套先检查这个顺序。另外自建瓦片服务要考虑切片工具geoserver或者tippecanoe都能干这事属于另一个话题这里就不展开了。卫星图切换也很简单AMap.TileLayer.Satellite直接用const satellite new AMap.value.TileLayer.Satellite() satellite.setMap(map.value) // 关掉卫星图层 satellite.setMap(null)3.4 组件销毁与内存泄漏处理这是最容易被忽略、后果又最严重的一节。SPA 项目里用户反复进出地图页面如果每次都不销毁实例内存会一路涨上去切个十几二十次页面就开始卡顿。销毁逻辑其实就一行map.destroy()但要保证它一定被执行到。我整理了几种典型场景的处理方式import { onUnmounted, onActivated, onDeactivated } from vue // 页面卸载时彻底销毁 onUnmounted(() { map.value?.destroy() map.value null AMap.value null })如果你用了keep-alive缓存页面onUnmounted不会触发得用onDeactivatedonDeactivated(() { map.value?.clearMap() // 先清掉所有覆盖物 }) onActivated(() { map.value?.resize() // 回到页面时重算尺寸 })clearMap()清的是覆盖物Marker、InfoWindow 这些destroy()才是销毁地图本体。两个别用混。还有一个坑是事件监听如果你在地图上注册了自定义 DOM 事件destroy()不一定能全部清掉最好自己在onUnmounted里手动removeEventListener。提示在开发环境里浏览器控制台如果频繁出现 AMap is already loaded 或者容器里出现两个 canvas 叠加基本可以断定是实例没销毁或者重复初始化了。养成谁创建谁销毁的习惯能省掉 90% 的地图内存问题。4. 踩坑实录常见报错码与排查速查表前面三章把正常路径讲完了但真实项目里让人抓狂的从来不是正常路径而是各种莫名其妙的报错。这一章我把这些年遇到的高频问题整理成速查表每条都附上我的排查思路。这些内容在官方文档里不一定写得直白但都是实打实踩出来的。4.1 常见报错码对照与定位思路报错信息 / 错误码大概率原因我的排查顺序INVALID_USER_KEY(10001)Key 填错、Key 被删除、Key 过期核对控制台 Key 字符串确认没多空格USERKEY_PLAT_NOMATCH(10003)Key 平台类型不对比如把 Web服务Key 用在 JS API回控制台看 Key 绑定的服务平台INVALID_USER_SCODE/ 缺少 jscode没配安全密钥或配置时机太晚检查window._AMapSecurityConfig是否在脚本加载前挂上DAILY_QUERY_OVER_LIMIT当天调用量超出个人账号配额控制台看用量曲线判断是否需要升配额QPS_HAS_EXCEEDED_THE_LIMIT(10014)每秒并发请求超限前端加节流或做请求合并INVALID_USER_DOMAINKey 设了域名白名单当前域名不在名单里本地开发时临时去掉白名单逆地理编码回调返回空数据坐标超出国内范围、坐标系传反、服务未开通先确认经纬度顺序是[lng, lat]再确认 Key 开通了对应服务地图白屏无报错容器高度为 0、Key 未生效、脚本被拦截打开 DevTools 看 Network 里地图请求是否成功关于那个被很多人在搜索的onRegeocodeSearched返回异常的问题我的经验是这类逆地理编码回调里的异常八成都不是 API 本身的问题而是坐标系或者经纬度顺序搞混了。高德的坐标是[经度, 纬度]而很多人从别的地图平台或者 GPS 设备拿到的数据是[纬度, 经度]传进去地球上就跑到南半球去了逆编码自然查不到数据。另外就是坐标系问题WGS84 和 GCJ-02 之间需要转换直接把 GPS 原始坐标丢给高德会有几百米的偏移。这类问题往往不报错只是结果不对比报错更费时间。4.2 域名白名单、配额与打包后的那些坑域名白名单。控制台里给 Key 配置白名单是个好习惯防止别人盗用。但本地开发的localhost和127.0.0.1是两回事白名单里写了前者不代表后者能过。我一般开发环境那个 Key 干脆不设白名单生产环境的 Key 才配上正式域名两个 Key 分而治之。配额。个人开发者的日调用量有限尤其是逆地理编码这类高频接口。如果你的业务里对每个 Marker 都要做一次逆编码几百个点跑下来配额就见底了。我的做法是在后端缓存逆编码结果同一片区域只查一次或者干脆在数据入库时就存好地址字段前端展示时直接读不去实时调用。这一条能省下的配额相当可观。打包后布局异常。这个问题在热词里也出现了我遇到过好几次。典型表现是本地开发正常npm run build部署到服务器后地图容器塌了高度变成 0。原因通常是 CSS 的优先级或者 flex 布局在不同环境下的计算差异。定位方法很简单打开生产环境的控制台用元素检查器看容器的高度是不是 0是的话往上逐层查父元素的height和flex设置。最粗暴也最有效的解法是给地图容器写死一个min-height.map-container { width: 100%; height: 100%; min-height: 400px; /* 兜底防止 flex 计算塌陷 */ }4.3 几条我觉得最值钱的经验第一条Key 和密钥分环境管理。开发、测试、生产三套 Key分别放到不同的环境变量文件里。别看一开始只有一个人开发嫌麻烦等到要查某个环境的调用量、或者某个 Key 泄露要紧急更换时这套分法能救你。第二条地图实例不要跨组件共享但要跨组件传递引用。我的做法是在最外层的地图组件里创建实例然后通过provide/inject或者 props 把实例引用给到子组件子组件只负责往上加覆盖物。这样避免了多个组件各建一个地图实例互相打架销毁的时候也只需要管住最外层那一个。第三条接口调用做一层节流。地图上的拖拽、缩放事件触发非常频繁如果你在地图moveend或者zoomend里直接发请求去拉数据用户拖一下地图可能就发出几十个请求QPS 直接爆。加个 300 毫秒的debounce体验和数据量都能兼顾。第四条先写死数据跑通再接真实接口。我见过太多人一上来就把地图和业务接口耦合在一起写结果接口没返回、或者字段对不上地图也一起白屏根本分不清是哪一环出的问题。正确的顺序是写死几个坐标把地图和标记跑通确认 Key、密钥、容器、插件全都没问题再去替换成接口数据。最后分享一个我个人的小习惯在开发环境里给地图容器加一个浅色边框和半透明背景这样哪块区域是地图容器一目了然容器塌陷的时候能立刻看出来比对着白屏盲猜高效得多。等上线前再把这段样式去掉就行。
返回列表