恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

微信小程序蓝牙连接读写实战:从扫描到数据分包完整指南

  • 首页
  • 资讯中心
  • /
  • 微信小程序蓝牙连接读写实战:从扫描到数据分包完整指南

相关资讯

微信小程序蓝牙连接与读写数据:BLE透传链路及避坑实践 2026/10/15 15:16:19
双麦克风语音分离源码实战:从原理到嵌入式复现的避坑指南 2026/10/15 15:16:19
ThreadPoolExecutor核心解析:状态机、Worker复用与拒绝策略 2026/10/15 15:16:19

最新资讯

如何5分钟装好Skyrim Community Shaders:从Nexus下载Address Library到进入游戏设置菜单的完整指南
STM32入门:用STM32CubeMX和HAL库点亮第一颗LED
Blender-Dark Engine资源转换工具链详解
第182篇 系统启动流程:从 init 到 SystemServer
Java面向对象经典练习:从MyPoint到ThreeDPoint的距离计算与类设计
200个实测Simulink电力电子仿真模型:参数可校验、拓扑全覆盖

今日推荐

AI视频生成的时间一致性难题与时空建模演进
基于bbox的PDF文档解析:多栏重排与水印过滤实战
探矿数据进RAG前的清洗全攻略:TXT、Word、PDF、网页四类文档处理实践

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

微信小程序蓝牙连接读写实战:从扫描到数据分包完整指南

发布时间:2026/10/15 15:16:19
微信小程序蓝牙连接读写实战:从扫描到数据分包完整指南 微信小程序蓝牙连接读写数据这个需求这两年越来越多。智能硬件厂商想把控制页面做进小程序节省一个独立 App 的成本做运动健康的团队想让手表、心率带直接连接手机连一些工业巡检设备都开始用小程序做临场调试。但真做起来你就会发现网上大部分教程只写到“能扫到设备”就结束了一旦涉及连接稳定、数据分包、异常重连资料立刻变得稀碎。这篇文章是我踩过不少坑之后整理出来的一套实战流程核心讲清楚小程序端 BLE 的扫描、连接、读写这几个环节以及每个环节最容易翻车的地方适合刚接手小程序外设开发、或者已经被蓝牙问题磨到心态崩掉的同学。先说一个很多新手搞混的点微信小程序并不支持操作所有蓝牙设备它只开放了低功耗蓝牙BLE的能力。这意味着你的外设必须支持 BLE否则小程序这边再怎么写代码都没有用。下面我按实操顺序从设备协议、工程配置、扫描连接、数据读写到排障技巧完整过一遍。1. 实战前的整体认知小程序蓝牙到底怎么工作1.1 小程序蓝牙能做什么不能做什么微信小程序的蓝牙 API 本质上是把手机系统蓝牙栈封装成了 JS 接口你调用wx.openBluetoothAdapter时底层操作的其实是手机系统蓝牙模块。但微信只开放了部分能力不是把整个蓝牙协议栈都给你。当前小程序能做的事包括初始化蓝牙适配器、开始/停止扫描、监听发现设备、建立连接、获取服务和特征值、读取特征值、写入特征值、开启通知。不能做的事包括让手机作为蓝牙外设被其他设备扫描、直接操作经典蓝牙 SPP 通道、修改系统蓝牙底层参数等。所以小程序的角色永远是“中心设备”负责主动扫描和连接而你要控制的硬件通常是“外围设备”负责广播和响应。这个模型很像一个社交场景外设是“贴着二维码的商家”小程序是“路过扫码的顾客”。商家不能反过来加顾客微信顾客必须主动扫码。小程序端所有流程都围绕“主动发现、主动连接、主动读写”展开。1.2 为什么你的设备必须是 BLE而不是普通蓝牙很多硬件项目早期用的是经典蓝牙比如一个单片机通过蓝牙模块和手机 App 做串口透传。这种模块成本低、资料多但微信小程序不支持因为小程序 API 里没有 SPP 通道。你只能在支持 BLE 的从机模式下通过 GATT 服务来通信。BLE 和经典蓝牙最大的区别在于通信模型经典蓝牙建立的是持续连接像打电话一直占用信道BLE 则是“广播-连接-数据交互-休眠”的短连接模型像发微信消息需要时才传输。BLE 功耗更低、连接更快但单次传输数据量也小MTU 默认只有 20 字节左右所以不能像串口透传那样一大段一大段地发。我后来总结了一个类比BLE 外设就像一面公告板板上贴了几张表格服务每张表格里又有几个抽屉特征值。你要读数据就得先找到公告板找到对应表格再打开抽屉看内容。小程序蓝牙开发的全过程就是“找到公告板、找到表格、开抽屉、交换字条”的过程。1.3 典型场景这些项目最适合小程序蓝牙从实际项目看小程序蓝牙最常见的是这几类一是智能家居控制比如灯、锁、窗帘小程序扫码后直接连蓝牙控制省去 App 下载成本二是健康设备比如心率带、体脂秤、血糖仪连接后读取测量数据三是防丢器和标签类设备读广播数据或者触发警报四是工业/巡检类的数据采集终端用小程序做临时配置和离线调试。这些场景有一个共同点数据量不大交互频率不高协议相对固定。如果你的业务需要高频率、大数据量持续传输比如音频流、文件传输那小程序 BLE 就不是最优选择可以考虑其他传输方式。做好这个判断能避免后面白写一大堆代码。2. 准备工作从硬件选型到工程配置漏一步就白折腾2.1 动手写代码前先读懂外设的协议文档这是我吃过大亏的地方。有一次我拿到一款温湿度计模块没看协议文档就开始写扫描代码结果扫到了设备连接也成功但读特征值一直是空数据。后来才发现这个模块的数据根本不是主动读回来的而是要开启 Notify 后由设备主动上报我对着 read 接口折腾了大半天。所以第一步永远是先找外设的协议文档确认这几个信息设备的广播名称叫什么扫描后怎么过滤设备在广播包里有没有暴露 Service UUID有的话可以用这个 UUID 做精确过滤需要操作的目标 Service UUID 和 Characteristic UUID 是哪几个每个特征值的属性是什么只读、可写、支持通知还是支持指示数据格式大端还是小端字段怎么排列有没有校验位命令要不要分包。如果你手头没有任何文档最靠谱的办法是先用手机上的通用蓝牙调试工具手动连一次。这类工具可以直接看到服务列表、特征值属性还能手动读写数据。我把这个过程叫做“协议摸查”相当于学游泳前先在浅水区走一圈后面代码写起来会踏实很多。2.2 工程配置和权限声明Android 和 iOS 有不同的隐形门槛微信小程序调用蓝牙接口本身不需要额外引入 npm 包但工程配置直接影响你能否正常调起蓝牙尤其是 Android。Android 6.0 以上扫描蓝牙设备需要定位权限如果你在 app.json 里没有配置 location 相关的 permission 说明直接调用wx.startBluetoothDevicesDiscovery经常会没有任何回调。我通常会在 app.json 里加上类似这样的配置permission: { scope.userLocation: { desc: 用于扫描附近蓝牙设备 } }同时在微信公众平台后台的“用户隐私保护指引”里把“位置信息”和“蓝牙”的使用目的都填写清楚。这一步看起来只是后台配置但漏掉之后真机调试时很可能报权限错误而且提示还不明确。iOS 端则需要特别注意系统不会给小程序返回蓝牙设备的 MAC 地址设备列表里只有 deviceId 和 localName。所以如果你的过滤逻辑依赖 MAC 地址在 iOS 上一定拿不到。更合理的做法是用 Service UUID 过滤或者让用户在界面上手动选择要连接的设备。2.3 初始化蓝牙适配器不是简单调一次 open 就完事所有蓝牙操作之前必须先调wx.openBluetoothAdapter。很多教程只写了这一句实际上完整流程应该更稳妥先 open再获取适配器状态然后再注册设备监听最后才开始扫描。我的初始化习惯是封装一个initBle()方法内部按顺序执行function initBle() { return new Promise((resolve, reject) { wx.openBluetoothAdapter({ success(res) { // open 成功后再查一下蓝牙是否打开 wx.getBluetoothAdapterState({ success(state) { if (state.available) { resolve(state); } else { reject(new Error(蓝牙未开启)); } }, fail: reject }); }, fail(err) { reject(err); } }); }); }这里有几个容易踩的坑一是wx.openBluetoothAdapter如果蓝牙本身没打开会在 fail 里抛错误二是页面 onLoad 时不适合立刻初始化蓝牙因为 iOS 上有些接口必须在用户点按触发的事件里调用才稳定我习惯用一个“搜索设备”按钮去触发三是 open 成功不代表适配器可用还要看available字段所以一定要做状态判断。还有一点很重要在开始扫描前先用wx.onBluetoothDeviceFound注册监听。如果先startDiscovery再注册监听很可能会漏掉前几次回调导致设备列表少设备。别问我是怎么知道的问就是被坑过。3. 扫描与连接从发现设备到拿到特征值每一步都不简单3.1 扫描过滤条件写得越精确后面越省心调用wx.startBluetoothDevicesDiscovery时如果不传services参数小程序会把周围所有 BLE 设备都扫出来包括耳机、手环、电子秤列表会很乱而且回调非常频繁容易影响主线程性能。我一般会在确认目标设备的 Service UUID 后把 UUID 放到services数组里做精确过滤wx.startBluetoothDevicesDiscovery({ services: [primaryServiceUUID], // 用目标服务的 UUID 过滤 allowDuplicatesKey: false, success(res) { console.log(开始扫描, res); }, fail(err) { console.error(扫描失败, err); } });如果没有拿到 Service UUID那就先空扫描然后在wx.onBluetoothDeviceFound回调里自己过滤。注意这个回调返回的是一个设备数组而且同一个设备可能被重复上报多次不能直接setData覆盖需要按deviceId去重把 RSSI 信号强度存下来再按信号从强到弱排序。扫描完成后连接设备前一定要调用wx.stopBluetoothDevicesDiscovery停止扫描。我以前以为连上设备后系统会自动停止结果发现连接质量很不稳定后来查资料才知道持续扫描和 BLE 连接会互相干扰。所以正确的流程是扫到目标设备 - 停止扫描 - 发起连接。3.2 连接createBLEConnection 的正确打开方式连接用wx.createBLEConnection传设备deviceId就行。但这个接口有几个特别容易翻车的点。第一个点是连接超时。如果你的外设离得太远、或者已经被另一个手机占用连接回调会一直不触发用户体验就是“转圈转半天”。我一般会包一层 Promise同时挂一个 10 秒的 setTimeout超时后主动调wx.closeBLEConnection并提示用户重试。function connectDevice(deviceId, timeout 10000) { return new Promise((resolve, reject) { const timer setTimeout(() { wx.closeBLEConnection({ deviceId }); reject(new Error(连接超时)); }, timeout); wx.createBLEConnection({ deviceId, success(res) { clearTimeout(timer); resolve(res); }, fail(err) { clearTimeout(timer); reject(err); } }); }); }第二个点是连接成功后不要立刻去获取服务。有些蓝牙外设刚连上的瞬间服务列表还没有准备好立刻调getBLEDeviceServices会拿到空数组。我习惯了在连接成功的回调里加一个 100200ms 的短延时然后再去发现服务。这个延时不是标准文档要求但实测对很多低成本 BLE 模块非常有效。第三个点是批量设备的并发连接。如果不做控制用户快速点击多个设备时会同时发起多个createBLEConnection很多外设只支持单连接结果就是互相打断。我之前在页面上加了一个isConnecting标志位连接完成前禁止再次点击。3.3 服务与特征值这一步决定了你到底能不能读写连接成功后需要依次调用wx.getBLEDeviceServices拿到 Service 列表再根据目标 Service 调wx.getBLEDeviceCharacteristics拿到特征值列表。这里面的核心坑是 UUID 的大小写。微信小程序文档明确说UUID 对大小写敏感。Android 端返回的 UUID 常常是小写iOS 端可能是大写而你的外设文档里写的一般是大写。如果你直接用字符串比较很容易因为大小写不一致而找不到目标服务。我的做法是拿到所有 UUID 后统一转成小写再比较比较逻辑也用小写function findService(services, targetUUID) { const target targetUUID.toLowerCase(); return services.find(item item.uuid.toLowerCase() target); }特征值列表里的properties字段会告诉你这个特征值支持哪些操作read支持主动读取write支持写入且对方会回复writeNoResponse支持无回复写入notify支持通知indicate支持指示。很多新手会拿一个只支持 notify 的特征值去调 read结果一直失败。正确做法是先判断 properties再决定走哪条路。比如要收实时数据就得找notify为 true 的特征值要发命令就得找write或writeNoResponse为 true 的特征值。获取这些信息时注意要先拿到 Service 再拿 Characteristic因为getBLEDeviceCharacteristics需要传serviceId。很多人会直接跳过服务列表去调特征值这是常见的顺序错误。4. 数据读写从“连上”到“能通信”的最后一公里4.1 先搞定 ArrayBuffer 和字符串的互相转换BLE 通信传输的是二进制数据小程序 API 里 value 的类型是 ArrayBuffer很多新手直接把字符串传进去会报错。我一般会准备几个工具函数放在公共模块里// ArrayBuffer 转十六进制字符串 function ab2hex(buffer) { const bytes new Uint8Array(buffer); return Array.from(bytes).map(b b.toString(16).padStart(2, 0)).join(); } // 十六进制字符串转 ArrayBuffer function hex2ab(hex) { const clean hex.replace(/\s/g, ); const buffer new ArrayBuffer(clean.length / 2); const view new DataView(buffer); for (let i 0; i clean.length; i 2) { view.setUint8(i / 2, parseInt(clean.substr(i, 2), 16)); } return buffer; } // ArrayBuffer 转字符串兼容 UTF-8 简单场景 function ab2str(buffer) { return new TextDecoder().decode(new Uint8Array(buffer)); } // 字符串转 ArrayBuffer function str2ab(str) { const bytes new TextEncoder().encode(str); return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset bytes.byteLength); }这些函数看着不起眼但实际调试时没有它们你根本不知道设备返回了什么也没法把命令一键拼成代码里能用的格式。建议所有蓝牙页面都复用同一套工具函数避免到处复制粘贴。调试时还有一个习惯很推荐写一个通用日志函数把十六进制数据、ASCII 可显示内容一起打出来。有些设备返回的是 ASCII 码文本有些是纯二进制只打一种格式很难判断问题。4.2 收数据主动读 vs 被动通知两者差别很大接收数据有两个方式主动调wx.readBLECharacteristicValue或者开启 Notify 后被设备推送。主动读适合“问一句答一句”的场景比如点一下界面上的“读取电量”然后去读电量特征值。主动读的结果会在wx.onBLECharacteristicValueChange回调里返回注意不是 read 接口的 success 里返回这个很多人会看懵。实际代码是这样的function readValue(deviceId, serviceId, characteristicId) { return new Promise((resolve, reject) { wx.readBLECharacteristicValue({ deviceId, serviceId, characteristicId, success() { // 真正数据要等 onBLECharacteristicValueChange }, fail: reject }); }); }被动通知适合持续上传的场景比如心率带每秒上报一次心跳或者体脂秤测量完成后主动推结果。开启通知的代码是wx.notifyBLECharacteristicValueChange({ deviceId, serviceId, characteristicId, state: true, // true 开启通知 success(res) { console.log(notify 开启成功); }, fail(err) { console.error(notify 开启失败, err); } });开启 Notify 前一定要确认特征值的properties.notify为 true。如果外设只支持 read你开 notify 会失败。另外开启成功之后也要在wx.onBLECharacteristicValueChange里做分发处理因为所有特征值的数据通知都会走同一个回调。wx.onBLECharacteristicValueChange是全局唯一的所以页面有多个特征值时要在回调里根据characteristicId做分支把数据投递到对应的处理函数。我见过有开发者在这个回调里直接 setData导致多个数据源互相覆盖后来改成 Map 按特征值存数据才解决。4.3 发数据一个 20 字节分包的经典问题写入数据用wx.writeBLECharacteristicValuevalue 必须是 ArrayBuffer参数需要 deviceId、serviceId、characteristicId。这是整条链路里大家报错最多的地方主要集中在这几类。第一类是不判断特征值是否可写。写之前先看 properties 里的write或writeNoResponse如果是 false调了也白调。第二类是数据超过 20 字节。微信小程序没有暴露设置 MTU 的接口所以稳妥做法是默认按 20 字节分包所有超过 20 字节的命令都切成多包发送。第三类是不等上一包返回就发下一包有些外设的接收缓冲区很小发太快会丢包。分包发送的核心思路很简单把原始命令按 20 字节切块每块加一个包序号接收方按序号拼回去。比如要发送一个 60 字节的加密固件升级包没法一次发完我会定义一个发送队列let sendQueue []; let isSending false; function enqueueWrite(deviceId, serviceId, characteristicId, chunks) { chunks.forEach((chunk, index) { sendQueue.push({ chunk, index, total: chunks.length }); }); processSendQueue(deviceId, serviceId, characteristicId); } function processSendQueue(deviceId, serviceId, characteristicId) { if (isSending || sendQueue.length 0) return; const item sendQueue.shift(); isSending true; wx.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: item.chunk, success() { isSending false; // 可以在这里记录发送进度 processSendQueue(deviceId, serviceId, characteristicId); }, fail(err) { isSending false; console.error(发送失败, err, item); // 简单策略把当前包放回队首重试最多重试3次 if (item.retryCount undefined) item.retryCount 0; item.retryCount; if (item.retryCount 3) { sendQueue.unshift(item); } processSendQueue(deviceId, serviceId, characteristicId); } }); }这里的核心是串行发送不能并发。因为很多外设不具备接收乱序包的能力必须等上一包 write 成功回调后再发下一包。发送前我会先跟外设协议核对广播和命令是否有应答机制如果没有我会在业务层加一个超时重传逻辑否则在真实环境中丢包后很难定位。4.4 一个极简的 BLE 管理器封装思路随着项目复杂度上升直接把 API 散在页面里会非常难维护。我最后都会封装一个BleManager类把扫描、连接、发现、读写、关闭统一起来页面只处理业务数据。大概结构如下class BleManager { constructor() { this.deviceId null; this.isReady false; } async init() { await this.openAdapter(); this.registerDiscoveryHandler(); } startScan(filterUUID) { return new Promise((resolve, reject) { wx.startBluetoothDevicesDiscovery({ services: filterUUID ? [filterUUID] : [], allowDuplicatesKey: false, success: resolve, fail: reject }); }); } async connect(deviceId) { this.deviceId deviceId; await this.stopScan(); await this.createConnection(deviceId); await this.discoverServices(deviceId); } stopScan() { return new Promise(resolve { wx.stopBluetoothDevicesDiscovery({ complete: resolve }); }); } createConnection(deviceId) { // 包含超时逻辑 } discoverServices(deviceId) { // 获取服务列表和特征值缓存到 Map } async write(data) { // 分包写入需要指定 serviceId/characteristicId } async read() { // 读取值返回 Promise } async enableNotify(handler) { // 开启指定特征值通知并注册统一回调 } close() { if (this.deviceId) { wx.closeBLEConnection({ deviceId: this.deviceId }); } } } module.exports new BleManager();封装完成后页面里的业务代码就干净很多。比如一个温度计页面只需要在 onLoad 里初始化管理器在按钮点击后扫描连接在 Notify 回调里刷新 UI 即可。后面如果换外设协议只需要改管理器和协议解析层页面几乎不用动。5. 真机调试与问题排查把我踩过的坑整理给你5.1 调试流程先用通用工具摸清协议再写代码小程序蓝牙最大的痛点是开发者工具基本不能用于真机蓝牙调试必须真机跑。而且你没法像 PC 端那样抓完整的蓝牙日志所以在写业务代码前用通用蓝牙调试工具把读写流程跑通是很值得的投资。我的标准流程是这样先把设备用某种调试软件连接上手动扫描服务、查看特征值然后手动执行 read、write、notify 三组操作确认哪个特征值能读、哪个能写、哪个能通知。同时把收到的原始数据截图保存字段含义逐条标注。这一步做完我对协议的理解才真正完整。否则我对着文档猜测 writed 的格式很可能写错了都不知道。调试工具里能看到成功写入的字节数、返回的错误码这些信息比在小程序里猜要直观得多。小程序真机调试时我会打开 vConsole 看 console 日志。其实 vConsole 不是专门给蓝牙用的但能帮我在手机上直接看到 Promise 是否 reject、错误码是什么省去一遍遍蓝牙传日志给电脑的麻烦。当然更严谨的做法是在代码里把关键日志上传到远程日志服务不过大部分项目前期用 vConsole 就够了。5.2 高频问题速查表我把实际项目中遇到最多的问题整理成了一张表方便你们对照排查。问题现象大概率原因解决思路扫描不到设备Android 没开定位权限没有停止上次扫描服务 UUID 过滤太严检查 app.json 权限声明先空白扫描确认设备存在确认真机调试而非工具iOS 能扫到但连不上服务 UUID 大小写不一致设备已被占用连接前还在扫描统一转小写比较停止扫描后再 createBLEConnection重启设备连上了但拿不到服务连接后立即 getServices设备服务尚未就绪连接成功后延时 100~200ms 再获取服务写入数据一直失败特征值不支持写value 不是 ArrayBuffer单包超过 20 字节检查 properties用工具函数转 ArrayBuffer按 20 字节分包开启 Notify 失败特征值不支持 notify查看特征值属性改用 read 或找另一个特征值收到数据是乱码大小端不对字段解析错误数据是十六进制但按文本转用 DataView 指定字节序读取按协议文档逐字段解析连接后频繁断开连接和扫描冲突外设功耗策略主动断开距离太远连接后停止扫描增加重连逻辑检查外设连接间隔设置这张表覆盖了我遇到过的大部分问题。如果你看到的现象不在表里最有效的排查方法是把三层日志全部拉出来小程序 API 回调的错误码、调试工具里的协议交互记录、以及外设端的日志。三层一对比问题基本能定位到某一层。5.3 断线重连与日志策略关键时刻能救命蓝牙连接在真实环境中非常容易断尤其是用户走远、外设休眠、或者系统资源紧张时。小程序提供的wx.onBLEConnectionStateChange可以监听连接状态变化一旦状态变为 disconnected要主动清理资源并提示用户重连。我的重连策略不复杂收到断开回调后先把当前连接的 deviceId 保存起来关闭所有通知监听调用wx.closeBLEConnection确保连接资源释放然后走重新初始化流程。重连不是无限循环最多重试 3 次每次间隔 1 秒、2 秒、4 秒递增超过次数就通知用户手动操作。日志方面我会给每个蓝牙操作加一个带时间戳的日志前缀比如[BLE][startScan] 12:00:01。排查问题时这些日志能帮你还原操作顺序。蓝牙问题有个特点很多故障是时序问题而不是逻辑问题。比如“先扫描再连接”和“先连接再扫描”对结果影响很大只有完整的日志才能帮你看出时序。最后再分享一个我后来一直坚持的小习惯每接到一个新外设第一时间用调试工具跑通用读写然后把原始报文截图存到项目文档里。这个动作花不了十分钟但能让你在隔了几个月后重新接这块板子的时候不需要再翻旧聊天记录找协议。蓝牙开发大多数时候不是难在智商而是难在细节把这些顺序、大小写、分包、超时的功课做扎实了你就能少熬好几个夜。如果你现在正卡在某个蓝牙问题上不妨按这个顺序重新捋一遍链路大部分问题都在这个链路里。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号