uni-app微信小程序如何开发蓝牙功能

一. 前言。

蓝牙功能在我们日常软件中的使用率还是蛮高的----譬如各类共享单车/电单车。正因此,我们开发中接触蓝牙功能也是日渐增长。对于很多从未开发过蓝牙功能的童鞋来说,当PM小姐姐扔过来一个蓝牙协议要你接入时,简直一头雾水(我是谁?我在哪?)。只能一翻度娘和AI,可是网上文章大多水准参差不齐,技术五花八门,没法真正地让你从无到有掌握蓝牙功能/协议对接。

二. 说明。

本文就基于uni-app框架结合微信和支付宝小程序为例,来讲述蓝牙功能在各类型小程序中的整体开发流程和如何“优雅”高效的封装蓝牙功能模块。本文使用到的主要技术栈和环境有:

  • uni-app
  • JavaScript
  • AES加解密
  • 微信小程序
  • 支付宝小程序

三. 蓝牙流程图。

正所谓“知己知彼,百战不殆”,所以在讲述蓝牙模块如何在小程序中开发和封装之前,我们先要了解蓝牙功能模块是如何在小程序中“走向”的,各API是如何交互通讯的。为了让大家看得清楚,学的明白----这里简明扼要地梳理了一份蓝牙核心API流程图(去除了非必要的逻辑走向,只展示了实际开发中最重要的步骤和交互)。

  • uni-app: 蓝牙API
  • 微信小程序:蓝牙API
  • 支付宝小程序:蓝牙API
  • 核心API流程图(注:每家厂商的小程序API大同小异,uni-app的基本通用,具体明细详见各厂商开发文档):

四. 蓝牙协议。

了解完开发所需的API后,就需要根据实际开发场景中所对接的硬件和其厂家提供的蓝牙对接协议来结合上述的API来编写代码了。每家厂商的蓝牙协议是不一样的,不过“万变不离其宗”。只要知道其中的规则,真正看懂一家,那换其他家的也是可以看懂的。本文以下述协议(蓝牙寻车+蓝牙开锁)为例解释下。

1. 寻车:

  • 协议内容:

  • 解读:

根据上述图文的描述,我们可以知道想要开启蓝牙锁,那么必须先通过寻车蓝牙指令(7B5B01610060 或 7B5B01610160)写入,然后根据蓝牙响应的信息功能体和错误码判断响应是否正确,如正确,那么就拿到此时的随机数,后根据协议规定对该随机数做相应的处理,最后将处理后得到的结果用于组装开锁的蓝牙写入指令。

  • 案例代码:

 2. 开锁:

  • 协议内容:

  • 解读:

根据上述图文的描述,我们可以知道开锁的写入指令是需要自己组装的,组装规则为:7B5B(数据头) 1B(信息体长度) 62(信息功能) 00(秘钥索引)018106053735(补1位0的电话号码)4B大端的时间戳 寻车拿到的随机码补8位0后经AES加密组合得到的16B数据 00(校验码);所以开锁写入的数据就是这种(案例:7B5B1B6200018106053735XXXXXXXXXXXXXXXXXXXX)。响应的话,也是根据信息功能体和错误码来判断开锁失败(9201)还是成功(9200)。

  • 案例代码:

五.代码编写。

这里为了提高蓝牙模块的代码耦合度,我们会把业务层和蓝牙模块层分离出来----也就是会把蓝牙整体流程交互封装成一个蓝牙模块js,然后根据业务形态,在各个业务层面上通过传参的形式来区分每个组件的蓝牙功能。

1. 业务层:

  • 核心代码:
//引入封装好的蓝牙功能JS模块核心方法函数
import { operateBluetoothYws } from '@/utils/bluetoothYws.js';//调用蓝牙功能
blueTooth() {//初始化蓝牙模块,所有的蓝牙API都需要在此步成功后才能调用uni.openBluetoothAdapter({success(res) {console.log('初始化蓝牙成功res', res);let mac = 'FF8956DEDA29';let key = 'oYQMt8LFavXZR6sB';operateBluetoothYws('open', mac, key, flag => {if (flag) {console.log('flag存在回调函数--蓝牙成功,可以执行后续步骤了', flag);} else {console.log('flag不存在回调函数--蓝牙成功,可以执行后续步骤了', flag);}})},fail(err) {console.log('初始化蓝牙失败err', err);}})
},
  • 解读:

这里是我们具体业务层需要的写法,一开始就是引入我们封装好的蓝牙JS模块核心方法函数(operateBluetoothYws),然后启用uni.openBluetoothAdapter这个蓝牙功能启动前提,成功后在其success内执行operateBluetoothYws方法,此时的参数根据实际开发业务和相对应的蓝牙协议而定(这里以指令参数、设备编号和AES加密秘钥为例),实际中每个mac和key是数据库一一匹配的,我们按后端童鞋提供的接口获取即可(这里为了直观直接写死)。

2. 蓝牙模块层:

  • 核心代码:
let CryptoJS = require('./crypto-js.min.js'); //引入AES加密
let callBack = null; //回调函数,用于与业务层交互
let curOrder; //指令(开锁还是关锁后取锁的状态)
let curMac; //当前扫码的车辆编码对应的设备mac
let curKey; //当前扫码的车辆编码对应的秘钥secret(用于AES加密)
let curDeviceId; //当前扫码的车辆编码对应的设备的 id
let curServiceId; //蓝牙服务 uuid,需要使用 getBLEDeviceServices 获取
let curCharacteristicRead; //当前设备读的uuid值
let curCharacteristicWrite; //当前设备写的uuid值//蓝牙调用核心方法(order: 指令;mac:车辆编码;key:秘钥secret;cb:回调)
function operateBluetoothYws(order,mac, key, cb) {curOrder = order;curMac = mac;curKey = key;callBack = cbsearchBluetooth();
}//第一步(uni.startBluetoothDevicesDiscovery(OBJECT),开始搜寻附近的蓝牙外围设备。)
function searchBluetooth() {uni.startBluetoothDevicesDiscovery({services: ['00000001-0000-1000-8000-00805F9B34FB', '00000002-0000-1000-8000-00805F9B34FB'],success(res) {console.log('第一步蓝牙startBluetoothDevicesDiscovery搜索成功res', res)watchBluetoothFound();},fail(err) {console.log('第一步蓝牙startBluetoothDevicesDiscovery搜索失败err', err)callBack && callBack(false)}})
}//第二步(uni.onBluetoothDeviceFound(CALLBACK),监听寻找到新设备的事件。)
function watchBluetoothFound() {uni.onBluetoothDeviceFound(function(res) {curDeviceId = res.devices.filter(i => i.localName.includes(curMac))[0].deviceId;stopSearchBluetooth()connectBluetooth()})
}//第三步(uni.createBLEConnection(OBJECT),连接低功耗蓝牙设备。)
function connectBluetooth() {if (curDeviceId.length > 0) {// #ifdef MP-WEIXINuni.createBLEConnection({deviceId: curDeviceId,timeout: 5000,success: (res) => {console.log('第三步通过deviceId连接蓝牙设备成功res', res);getBluetoothServers()},fail: (err) => {console.log('第三步通过deviceId连接蓝牙设备失败err', err);callBack && callBack(false)}});// #endif// #ifdef MP-ALIPAYmy.connectBLEDevice({deviceId: curDeviceId,timeout: 5000,success: (res) => {console.log('第三步通过deviceId连接蓝牙设备成功res', res);getBluetoothServers()},fail: (err) => {console.log('第三步通过deviceId连接蓝牙设备失败err', err);callBack && callBack(false)}});// #endif}
}//第四步(uni.stopBluetoothDevicesDiscovery(OBJECT),停止搜寻附近的蓝牙外围设备。)
function stopSearchBluetooth() {uni.stopBluetoothDevicesDiscovery({success: (res) => {console.log('第四步停止搜寻附近的蓝牙外围设备成功res', res);},fail: (err) => {console.log('第四步停止搜寻附近的蓝牙外围设备失败err', err);}})
}//第五步(uni.getBLEDeviceServices(OBJECT),获取蓝牙设备所有服务(service)。)
function getBluetoothServers() {uni.getBLEDeviceServices({deviceId: curDeviceId,success(res) {console.log('第五步获取蓝牙设备所有服务成功res', res);//这里取res.services中的哪个,这是硬件产商配置好的,不同产商不同,具体看对接协议if (res.services && res.services.length > 1) {curServiceId = res.services[1].uuidgetBluetoothCharacteristics()}},fail(err) {console.log('第五步获取蓝牙设备所有服务失败err', err);callBack && callBack(false)}})
}//第六步(uni.getBLEDeviceCharacteristics(OBJECT),获取蓝牙设备某个服务中所有特征值(characteristic)。)
function getBluetoothCharacteristics() {// #ifdef MP-WEIXINuni.getBLEDeviceCharacteristics({deviceId: curDeviceId,serviceId: curServiceId,success: (res) => {console.log('第六步获取蓝牙设备某个服务中所有特征值成功res', res);curCharacteristicWrite = res.characteristics.filter(item => item && item.uuid.includes('0002'))[0].uuidcurCharacteristicRead = res.characteristics.filter(item => item && item.uuid.includes('0003'))[0].uuidnotifyBluetoothCharacteristicValueChange()},fail: (err) => {console.log('第六步获取蓝牙设备某个服务中所有特征值失败err', err);callBack && callBack(false)}});// #endif// #ifdef MP-ALIPAYmy.getBLEDeviceCharacteristics({deviceId: curDeviceId,serviceId: curServiceId,success: (res) => {console.log('第六步获取蓝牙设备某个服务中所有特征值成功res', res);curCharacteristicWrite = res.characteristics.filter(item => item && item.characteristicId.includes('0002'))[0].characteristicIdcurCharacteristicRead = res.characteristics.filter(item => item && item.characteristicId.includes('0003'))[0].characteristicIdnotifyBluetoothCharacteristicValueChange()},fail: (err) => {console.log('第六步获取蓝牙设备某个服务中所有特征值失败err', err);callBack && callBack(false)}});// #endif
}//第七步(uni.notifyBLECharacteristicValueChange(OBJECT),启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值。)
function notifyBluetoothCharacteristicValueChange() {uni.notifyBLECharacteristicValueChange({deviceId: curDeviceId,serviceId: curServiceId,characteristicId: curCharacteristicRead,state: true,success(res) {console.log('第七步启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值成功res', res);if(curOrder == 'open'){//寻车指令getRandomCode();}else if(curOrder == 'close'){//查看锁状态指令getLockStatus();}else{}		//第八步(监听)(uni.onBLECharacteristicValueChange(CALLBACK),监听低功耗蓝牙设备的特征值变化事件。),含下发指令后的上行回应接受//这里会一直监听设备上行,所以日志等需清除uni.onBLECharacteristicValueChange((characteristic) => {// #ifdef MP-WEIXIN//完整的蓝牙回应数据let ciphertext = ab2hex(characteristic.value);			//蓝牙回应数据的信息功能体和错误码let curFeature = ab2hex(characteristic.value).slice(6, 10);//蓝牙回应数据的错误码let errCode = ab2hex(characteristic.value).slice(8, 10);// #endif// #ifdef MP-ALIPAY//完整的蓝牙回应数据let ciphertext = characteristic.value;			//蓝牙回应数据的信息功能体和错误码let curFeature = characteristic.value.slice(6, 10);//蓝牙回应数据的错误码let errCode = characteristic.value.slice(8, 10);// #endifif (curFeature.startsWith('91')) { //寻车响应,拿到随机码//用于给开锁的随机码getUnlockData(ciphertext)} else if (curFeature.startsWith('9200')) { //开锁响应(成功)callBack && callBack(true)} else if (curFeature.startsWith('98')) { //关锁后APP主动读取后的响应,查看是否已关锁if (curFeature == '9801') { //关锁成功callBack && callBack(true)} else { //关锁失败callBack && callBack(false)}} else {}})},fail(err) {console.log('第七步启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值失败err', err);callBack && callBack(false)}})
}// ArrayBuffer转16进度字符串示例
function ab2hex(buffer) {const hexArr = Array.prototype.map.call(new Uint8Array(buffer),function (bit) {return ('00' + bit.toString(16)).slice(-2)})return hexArr.join('')
}//寻车指令,用于拿到开锁所需的随机码
function getRandomCode() {let str = '7B5B01610060';writeBLE(str)
}//开锁指令,获取到开锁所需的数据
function getUnlockData(ciphertext) {if (ciphertext.length > 16) { //确保寻车后蓝牙响应内容有用于开锁的随机码//开锁头(固定值)let headData = '7B5B1B6200';//用户手机号let userPhone = '018106053735';//4B大端秒级时间戳let timestamp = convertLettersToUpperCase(decimalToHex(getSecondsTimestamp()));//随机码 + 8个‘0’let randomVal = convertToLower(ciphertext.slice(16, 24)) + '00000000';//AES加密后的前32位密文let aesResult = aesEncrypt(randomVal,curKey).slice(0,32)//校验码let checkCode = '00';//最后用于发指令的内容let result = headData + userPhone + timestamp + aesResult + checkCode;writeBLE(result)} else {getRandomCode();}
}//查看锁状态指令,用于验证用户手工关锁后查询是否真的已关锁
function getLockStatus() {let str = '7B5B006868';writeBLE(str)
}//AES的ECB方式加密,以hex格式(转大写)输出;参数一:明文数据,参数二:秘钥
function aesEncrypt(encryptString, key) {let aeskey = CryptoJS.enc.Utf8.parse(key);let aesData = CryptoJS.enc.Utf8.parse(encryptString);let encrypted = CryptoJS.AES.encrypt(aesData, aeskey, {mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});//将base64格式转为hex格式并转换成大写let password = encrypted.ciphertext.toString().toUpperCase()return password;
}//处理写入数据
function writeBLE(str) {//如果大于20个字节则分包发送if (str.length > 20) {let curArr = splitString(str,20);// #ifdef MP-WEIXINcurArr.map(i => writeBLECharacter(hexStringToArrayBuffer(i)))// #endif// #ifdef MP-ALIPAYcurArr.map(i => writeBLECharacter(i))// #endif	} else {// #ifdef MP-WEIXINwriteBLECharacter(hexStringToArrayBuffer(str));// #endif// #ifdef MP-ALIPAYwriteBLECharacter(str);// #endif}
}//第八步(写入)(uni.writeBLECharacteristicValue(OBJECT),向低功耗蓝牙设备特征值中写入二进制数据。)
function writeBLECharacter(bufferValue){uni.writeBLECharacteristicValue({deviceId: curDeviceId,serviceId: curServiceId,characteristicId: curCharacteristicWrite,value: bufferValue,success(res) {console.log('第八步(写入)向低功耗蓝牙设备特征值中写入二进制数据成功res', res);},fail(err) {console.log('第八步(写入)向低功耗蓝牙设备特征值中写入二进制数据失败err', err);callBack && callBack(false)}})
}//将字符串以每length位分割为数组
function splitString(str, length) {var result = [];var index = 0;while (index < str.length) {result.push(str.substring(index, index + length));index += length;}return result;
}//字符转ArrayBuffer
function hexStringToArrayBuffer(str) {// 将16进制转化为ArrayBufferreturn new Uint8Array(str.match(/[\da-f]{2}/gi).map(function(h) {return parseInt(h, 16)})).buffer
}//对字符串中的英文大写转小写
function convertToLower(str) {var result = '';for (var i = 0; i < str.length; i++) {if (/[a-zA-Z]/.test(str[i])) {result += str[i].toLowerCase();} else {result += str[i];}}return result;
}//对字符串中的英文小写转大写
function convertLettersToUpperCase(str) {var result = str.toUpperCase(); // 将字符串中的字母转换为大写return result;
}//获取秒级时间戳(十进制)
function getSecondsTimestamp() {var timestamp = Math.floor(Date.now() / 1000); // 获取当前时间戳(单位为秒)return timestamp;
}//将十进制时间戳转成十六进制
function decimalToHex(timestamp) {var hex = timestamp.toString(16); // 将十进制时间戳转换为十六进制字符串return hex;
}//抛出蓝牙核心方法
module.exports = {operateBluetoothYws
};
  • 解读:

这里的步骤和上面流程图中的步骤走向是一样的,不过里面的详情,笔者还是想每一步都拆开来对着实际案例讲述为好,详见下文(这里主要是为了照顾小白,大佬勿怪)。

六. 蓝牙模块层各步骤详解。

  1. 蓝牙功能调用核心方法的定义和导出(operateBluetoothYws)

operateBluetoothYws 这里没啥好特别的,就是将业务层传进来的参数做个中转处理,为后续步骤的api所调用,详见上文代码及其注释。

  1. 第一步(uni.startBluetoothDevicesDiscovery(OBJECT))

uni.startBluetoothDevicesDiscovery 这里主要注意的是services这个参数,这个参数会由硬件厂家提供,一般在其提供的蓝牙协议文档中会标注,作用是要搜索的蓝牙设备主 service 的 uuid 列表。某些蓝牙设备会广播自己的主 service 的 uuid。如果设置此参数,则只搜索广播包有对应 uuid 的主服务的蓝牙设备。建议主要通过该参数过滤掉周边不需要处理的其他蓝牙设备。

  1. 第二步(uni.onBluetoothDeviceFound(CALLBACK))

uni.onBluetoothDeviceFound 这一步用来确定目标设备id,即后续步骤所需的参数deviceId。 这里主要注意的是其回调函数的devices结果,我们要根据厂家或其提供的蓝牙对接协议规定和我们业务层传进来的mac来匹配筛选目标设备(因为这里会监听到第一步同样的uuid的每一台设备)(这里我就一台设备测试,所以回调函数的devices结果数组中内容就一个;然后之所以用localName.includes(curMac) 来匹配目标设备,这是根据厂商协议文档来做的,每家厂商和每种设备不一样,这里要按实际情况处理,不过万变不离其宗)。

  1. 第三步(uni.createBLEConnection(OBJECT))

uni.createBLEConnection 这里没啥特别的,主要就是用到第二步中得到的deviceId去连接低功耗蓝牙目标设备。需要注意的是这里支付宝小程序的API不一致,为my.connectBLEDevice

  1. 第四步(uni.stopBluetoothDevicesDiscovery(OBJECT))

uni.stopBluetoothDevicesDiscovery 这一步主要是为了节省电量和资源,在第三步连接目标设备成功后给停止搜寻附近的蓝牙外围设备。

  1. 第五步(uni.getBLEDeviceServices(OBJECT))

uni.getBLEDeviceServices 这里通过第二步中得到的deviceId用来获取蓝牙目标设备的所有服务并确定后续步骤所需用的蓝牙服务uuid(serviceId)。这里取res.services中的哪个,这是硬件厂商定好的,不同厂商不同,具体看对接协议(案例中的是固定放在第2个,所以是通过curServiceId = res.services[1].uuid得到)。

  1. 第六步(uni.getBLEDeviceCharacteristics(OBJECT))

uni.getBLEDeviceCharacteristics 这里通过第二步获取的目标设备IddeviceId和第五步获取的蓝牙服务IdserviceId来得到目标设备的写的uuid读的uuid。这里取characteristics的哪一个也是要根据厂商和其提供的蓝牙协议文档来决定的(案例以笔者这的协议文档为主,所以是这样获取的:curCharacteristicWrite = res.characteristics.filter(item => item && item.uuid.includes('0002'))[0].uuid 和 curCharacteristicRead = res.characteristics.filter(item => item && item.uuid.includes('0003'))[0].uuid)。需要注意的是这里支付宝小程序的API不一致,为my.getBLEDeviceCharacteristics,其res返回值也不一样,curCharacteristicWrite = res.characteristics.filter(item => item && item.characteristicId.includes('0002'))[0].characteristicId 和 curCharacteristicRead = res.characteristics.filter(item => item && item.characteristicId.includes('0003'))[0].characteristicId。

  1. 第七步(uni.notifyBLECharacteristicValueChange(OBJECT))

uni.notifyBLECharacteristicValueChange 这里就是开启低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值。可以在其的success内执行一些写入操作执行uni.onBLECharacteristicValueChange(CALLBACK)来监听低功耗蓝牙设备的特征值变化事件了。

9.第八步(写入)(uni.writeBLECharacteristicValue(OBJECT))

uni.writeBLECharacteristicValue 这里特别要注意的是参数value必须为二进制值(这里需用注意的是支付宝小程序的参数value可以不为二进制值,可直接传入,详见支付宝小程序开发文档);并且单次写入不得超过20字节,超过了需分段写入

  1. 第八步(监听)(uni.onBLECharacteristicValueChange(CALLBACK))

uni.onBLECharacteristicValueChange 这里需根据实际开发的业务场景对CALLBACK 返回参数转16进度字符串后自行处理(支付宝小程序如果写入时未转换,那么这里读取时也不需要转换)(本文以寻车--开锁--检测锁状态为例)。

七. 总结。

以上就是本文的所有内容,主要分为2部分----业务层蓝牙模块层(封装)。业务层只需要关注目标设备和其对应的密钥(不同厂家和设备不同);蓝牙模块层主要是按蓝牙各API拿到以下四要素并按流程图一步步执行即可。

  1. 蓝牙设备Id:deviceId
  2. 蓝牙服务uuid:serviceId
  3. 蓝牙写操作的uuid
  4. 蓝牙读操作的uuid

至此,如何在小程序中优雅地封装蓝牙模块并高效使用就已经完结了,当然本文只是以最简而易学的案例来讲述蓝牙模块开发,大多只处理了success的后续,至于fail后续可以根据大家实际业务处理。相信看到这,你已经对小程序开发蓝牙功能,对接各种蓝牙协议已经有一定的认识了,再也不虚PM小姐姐的蓝牙需求了。完结撒花~ 码文不易,还请各位大佬三连鼓励(如发现错别之处,还请联系笔者修正)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/176093.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法:穷举,暴搜,深搜,回溯,剪枝

文章目录 算法基本思路例题全排列子集全排列II电话号码和字母组合括号生成组合目标和组合总和优美的排列N皇后有效的数独解数独单词搜索黄金矿工不同路径III 总结 算法基本思路 穷举–枚举 画出决策树设计代码 在设计代码的过程中&#xff0c;重点要关心到全局变量&#xff…

一分钟搞懂什么是this指针(未涉及静态成员和函数)

前言 我们在学习类的过程中&#xff0c;一定听说过this指针&#xff0c;但是并不知道它跟谁相似&#xff0c;又有什么用途&#xff0c;所以接下来&#xff0c;让我们一起去学习this指针吧&#xff01; 一、this指针的引入 我们先来看下面两段代码&#xff0c;它们输出的是什么&…

IDEA 2022创建Spring Boot项目

首先点击New Project 接下来&#xff1a; (1). 我们点击Spring Initializr来创建。 (2). 填写项目名称 (3). 选择路径 (4). 选择JDK------这里笔者选用jdk17。 (5). java选择对应版本即可。 (6). 其余选项如无特殊需求保持默认即可。 然后点击Next。 稍等一会&#xff0c…

【python后端】- 初识Django框架

Django入门 &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f31d;分享学习心得&#xff0c;欢迎指正&#xff0c;大家一起学习成长&#xff01; 文章目录 Django入门…

Programming Abstractions in C阅读笔记:p196

《Programming Abstractions in C》学习第63天&#xff0c;p196总结。涉及到编程之外的知识&#xff0c;依然是读起来很费劲&#xff0c;需要了解作者在书中提到的人物(Edouard Lucas)、地点(Benares)、神话传说(Brahma)等等。虽然深知自己做不到对人文知识&#xff0c;历史知识…

从0到0.01入门React | 009.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

19 - 如何用协程来优化多线程业务?

近几年&#xff0c;国内很多互联网公司开始使用或转型 Go 语言&#xff0c;其中一个很重要的原因就是 Go 语言优越的性能表现&#xff0c;而这个优势与 Go 实现的轻量级线程 Goroutines&#xff08;协程 Coroutine&#xff09;不无关系。那么 Go 协程的实现与 Java 线程的实现有…

vue离线地图(瓦片)

最近公司要弄一个这样的离线地图&#xff0c;要求在图上打点画线之类的。折腾了几天&#xff0c;学习了三种方式&#xff1a; 1.拿到各省市区的经纬度json&#xff0c;通过echarts来制作&#xff0c;再套一个卫星图的地图背景 2.下载地图瓦片&#xff0c;再通过百度/高德的离线…

OpenGL_Learn11(光照)

目录 1. 光照 2. 环境光照 3. 漫反射光照 4. 代码实战 1. 光照 在OpenGL中主要分以下几个光照类型 环境光照(Ambient Lighting)&#xff1a;即使在黑暗的情况下&#xff0c;世界上通常也仍然有一些光亮&#xff08;月亮、远处的光&#xff09;&#xff0c;所以物体几乎永远不…

mac homebrew.mxcl.php@5.6.plist

今天启动php5.6时 遇到了一个问题 servers % brew services start php5.6 Bootstrap failed: 5: Input/output error Try re-running the command as root for richer errors. Error: Failure while executing; /bin/launchctl bootstrap gui/501 /Users/ssh/Library/LaunchAge…

部分背包问题【贪心算法】

部分背包问题是一种经典的贪心问题&#xff0c;物品可以取一部分&#xff0c;也就是可以随意拆分的物品。 算法思路&#xff1a; 用列表保存每个物品的价值及总重量、平均价值&#xff08;性价比&#xff09;。输入数据同时计算每种物品的平均价值。使用自定义的compare函数以…

【数据结构】堆(Heap):堆的实现、堆排序、TOP-K问题

目录 堆的概念及结构 ​编辑 堆的实现 实现堆的接口 堆的初始化 堆的打印 堆的销毁 获取最顶的根数据 交换 堆的插入&#xff08;插入最后&#xff09; 向上调整&#xff08;这次用的是小堆&#xff09; 堆的删除&#xff08;删除根&#xff09; 向下调整&#xff08;这次用的…