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 = cb
    searchBluetooth();
}

//第一步(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-WEIXIN
        uni.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-ALIPAY
        my.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].uuid
                    getBluetoothCharacteristics()
            }
        },
        fail(err) {
            console.log('第五步获取蓝牙设备所有服务失败err', err);
            callBack && callBack(false)
        }
    })
}

//第六步(uni.getBLEDeviceCharacteristics(OBJECT),获取蓝牙设备某个服务中所有特征值(characteristic)。)
function getBluetoothCharacteristics() {
    // #ifdef MP-WEIXIN
    uni.getBLEDeviceCharacteristics({
        deviceId: curDeviceId,
        serviceId: curServiceId,
        success: (res) => {
            console.log('第六步获取蓝牙设备某个服务中所有特征值成功res', res);
            curCharacteristicWrite = res.characteristics.filter(item => item && item.uuid.includes('0002'))[
                    0].uuid
            curCharacteristicRead = res.characteristics.filter(item => item && item.uuid.includes('0003'))[
                    0].uuid
            notifyBluetoothCharacteristicValueChange()
        },
        fail: (err) => {
            console.log('第六步获取蓝牙设备某个服务中所有特征值失败err', err);
            callBack && callBack(false)
        }
    });
    // #endif
    // #ifdef MP-ALIPAY
    my.getBLEDeviceCharacteristics({
        deviceId: curDeviceId,
        serviceId: curServiceId,
        success: (res) => {
            console.log('第六步获取蓝牙设备某个服务中所有特征值成功res', 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
            notifyBluetoothCharacteristicValueChange()
        },
        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);
                // #endif
                if (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-WEIXIN
        curArr.map(i => writeBLECharacter(hexStringToArrayBuffer(i)))
        // #endif

        // #ifdef MP-ALIPAY
        curArr.map(i => writeBLECharacter(i))
        // #endif	
    } else {
        // #ifdef MP-WEIXIN
        writeBLECharacter(hexStringToArrayBuffer(str));
        // #endif

        // #ifdef MP-ALIPAY
        writeBLECharacter(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进制转化为ArrayBuffer
    return 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小姐姐的蓝牙需求了。完结撒花~ 码文不易,还请各位大佬三连鼓励(如发现错别之处,还请联系笔者修正)。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何修改文件的修改时间?

如何修改文件的修改时间&#xff1f;随着当代社会科技的不断进步&#xff0c;我们对信息和数据的依赖程度与日俱增。在这个信息化时代&#xff0c;文件处理已经成为数字化办公中不可或缺的一部分。文件处理的范围非常广&#xff0c;其中有一个比较冷门的操作技巧&#xff0c;那…

C语言从入门到精通之【char类型】

char类型用于储存字符&#xff08;如&#xff0c;字母或标点符号&#xff09;&#xff0c;但是从技术层面看&#xff0c;char是整数类型。因为char类型实际上储存的是整数而不是字符。计算机使用数字编码来处理字符&#xff0c;即用特定的整数表示特定的字符。 char类型占1个字…

Debug知识点解析!超实用教程

一、Debug简介 二、IDEA中的Debug步骤 2.1 步过调试按钮(F8) 2.2 步入调试按钮(F7) 2.3 强制步入调试按钮(Alt Shift EZ) 2.4 步出调试按钮(Shift F8) 2.5 回退断点 2.6 运行到光标处&#xff08;F9&#xff09; 2.7 计算表达式按钮(Alt F8) 三、条件断点 在断点处右…

[Kettle] 记录处理

1.排序记录 排序是对数据中的无序记录&#xff0c;按照自然或客观规律&#xff0c;根据关键字段大小递增或递减的次序&#xff0c;对记录重新排列的过程 数据源 2019年11月月考数学成绩(Kettle数据集3).xlshttps://download.csdn.net/download/Hudas/88521681 2019年11月月考…

Mysql5.7创建远程账号和新建数据库

文章目录 Mysql5.7创建远程账号和新建数据库创建远程账号新建默认数据库默认数据库指定字符集数据库写法一写法二 查看数据库列表查看数据库的定义声明 Mysql5.7创建远程账号和新建数据库 创建远程账号 CREATE USER mm% IDENTIFIED WITH mysql_native_password BY mm1122;Que…

将算力普惠到底阿里云开启金秋云创季: 数百款爆品享专属特惠价(阿里云2023年双十一)

11月9日消息&#xff0c;国内领先云计算厂商阿里云近日在官网推出了“金秋云创季”活动&#xff0c;为开发者和企业带来了多重福利&#xff0c;包括上云必备的2C2G 3M固定带宽云服务器仅需99元/年、上百款核心云产品的专属特惠价、最高5580元的满减券大礼包&#xff0c;不断降低…

绕过PPL机制窃取凭证

Mimikatz窃取凭证攻击 正常权限cmd情况下mimikatz是无法直接提权的,所以窃取密码更是不行的。 但管理员权限下的mimikatz是可以获取到主机登陆密码的。 PPL安全机制 在此之前,用户只需要使用SeDebugPrivilege令牌权限即可获取任意进程的所有访问权限;随后Windows8.1 在此…

Ant Design Vue Select下拉框内容显示不全问题解决

默认情况下&#xff0c;当下拉框的内容长度过长时&#xff0c;会被折叠&#xff0c;这比较影响用户体验&#xff0c;查了下文档&#xff0c;看到有一个属性可以解决这个问题&#xff1a; 没设置的下拉效果&#xff1a; 设置完之后&#xff1a;dropdownMatchSelectWidth{false}&…

pytest自动化测试两种执行环境切换的解决方案

一、痛点分析 在实际企业的项目中&#xff0c;自动化测试的代码往往需要在不同的环境中进行切换&#xff0c;比如多套测试环境、预上线环境、UAT环境、线上环境等等&#xff0c;并且在DevOps理念中&#xff0c;往往自动化都会与Jenkins进行CI/CD&#xff0c;不论是定时执行策略…

Ora2Pg工具迁移Oracle到openGauss

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

2022年12月 Python(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 下面哪个语句正确定义了元组类型数据tuple1?( ) A: tuple1=[“张三”,“李四”,“王五”] B: tuple1=(“张三”;“李四”;“王五”) C: tuple1=(张三,李四,王五) D: tuple1=(“张三…

C语言从入门到精通之【printf和scanf函数】

printf()是输出函数&#xff0c;scanf()是输入函数&#xff0c;但是它们的工作原理几乎相同。两个函数都使用格式字符串和参数列表。 printf()函数的格式 printf( 格式字符串, 待打印项1, 待打印项2,…);待打印项1、待打印项2等都是要打印的项。它们可以是变量、常量&#xff…

【论文阅读】GAIN: Missing Data Imputation using Generative Adversarial Nets

论文地址&#xff1a;[1806.02920] GAIN: Missing Data Imputation using Generative Adversarial Nets (arxiv.org)

应用层——HTTPS协议

文章目录 一.HTTPS协议介绍二.关于加密1.什么是"加密"2.为什么要加密3.常见的加密方式4.数据摘要 && 数据指纹 三.HTTPS的工作过程探究1.方案1 —— 只使用对称加密&#xff08;明文传输不可取&#xff09;2.方案2 —— 只使用非对称加密&#xff08;仅单向安…

【数据分享】2015-2023年我国地级市逐月房价数据(Excel格式/Shp格式)

房价是一个城市发展程度的重要体现&#xff0c;一个城市的房价越高通常代表这个城市越发达&#xff0c;对于人口的吸引力越大&#xff01;因此&#xff0c;房价数据是我们在各项城市研究中都非常常用的数据&#xff01;之前我们分享过我国主要城市2023年房价数据&#xff08;可…

Vue基础必备掌握知识点-Vue的指令系统讲解(二)

Vue指令系统继续讲解 v-for 作用:基于数据进行循环&#xff0c;多次渲染整个元素 数据类型:数组.对象.数字。。。 遍历数组语法&#xff1a;v-for"(item,index)" in 数组 item:表示每一项 index:则是表现下标 注意:v-for中的key值&#xff0c;key属性唯一的…

【算法|动态规划 | 区间dp No.2】AcWing 1068.环形石子合并

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【AcWing算法提高学习专栏】【手撕算法系列专栏】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&a…

深度学习实战59-NLP最核心的模型:transformer的搭建与训练过程详解,手把手搭建与跑通

大家好,我是微学AI,今天给大家介绍一下深度学习实战59-NLP最核心的模型:transformer的搭建与训练过程详解,手把手搭建与跑通。transformer是一种基于自注意力机制的深度学习模型,由Vaswani等人在2017年的论文《Attention is All You Need》中提出。它最初被设计用来处理序…

SpringCloudalibaba2

一、nacos简介 Nacos&#xff08;全称为"Nano Service"&#xff09;是一个用于动态服务发现、配置管理和服务元数据的开源平台。它由阿里巴巴集团于2018年开源&#xff0c;并逐渐成为云原生应用中的重要组件之一。 Nacos提供了以下主要功能&#xff1a; 1. 服务发…