SpringBoot中集成eclipse.paho.client.mqttv3实现mqtt客户端并支持断线重连、线程池高并发改造、存储入库mqsql和redis示例业务流程,附资源下载

场景

SpringBoot整合MQTT服务器实现消息的发送与订阅(推送消息与接收推送):

SpringBoot整合MQTT服务器实现消息的发送与订阅(推送消息与接收推送)_服务端接收mqtt消息-CSDN博客

上面SpringBoot集成MQTT使用的是spring-integration-mqtt依赖,也是经常使用的方式。

其底层也是有对org.eclipse.paho.client.mqttv3的封装和引用。

下面直接对org.eclipse.paho.client.mqttv3进行集成和简单的业务示例梳理。

系统中需要对接第三方的硬件比如摄像头报警系统,触发报警时会通过mqtt协议发送json格式的报警数据。

项目启动后需要连接配置的mqtt的broker的地址,并订阅指定主题,当收到mqtt消息时进行解析并存储进mysql和redis中。

Mqtt的Broker的搭建使用如下方式:

Windows上Mqtt的Broker/服务端的搭建-使用mosquitto:

Windows上Mqtt的Broker/服务端的搭建-使用mosquitto-CSDN博客

注:

博客:
霸道流氓气质-CSDN博客

实现

新建SpringBoot项目并添加项目依赖

mqtt所需的依赖

        <!-- mqtt --><dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.client.mqttv3</artifactId><version>1.2.4</version></dependency>

线程池使用guava依赖

        <!-- guava --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>32.1.2-jre</version><scope>provided</scope></dependency>

当然也可使用Java自带的依赖,关于guava的使用参考如下

Java工具库Guava并发相关工具类的使用示例:

Java工具库Guava并发相关工具类的使用示例_moreexecutors.listeningdecorator-CSDN博客

Json解析相关的依赖

        <!-- 阿里JSON解析器 --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.75</version></dependency>

Lombok工具集依赖

        <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>

其它连接mysql、mybatis、redis的依赖省略,见文末示例代码。

yml文件添加mqtt配置

application.yml文件中添加mqtt连接相关的ip、端口、用户名、密码等需要配置的信息。

#mqtt数据配置
badao-mqtt:host: 127.0.0.1port: 1883user: adminpassword: 123456

然后新建配置类,用于获取yml中配置的内容

package com.badao.demo.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** Class Name: mqttConfig* Description: 类功能说明*/
@Component("MqttConfig")
@ConfigurationProperties(prefix = "badao-mqtt")
public class MqttConfig {/*** mqqt地址*/private static String host;/*** mqtt端口*/private static String port;/*** mqtt用户*/private static String user;/*** mqtt密码*/private  static String password;public String getHost() {return host;}public void setHost(String host) {MqttConfig.host = host;}public String getPort() {return port;}public void setPort(String port) {MqttConfig.port = port;}public String getUser() {return user;}public void setUser(String user) {MqttConfig.user = user;}public String getPassword() {return password;}public void setPassword(String password) {MqttConfig.password = password;}
}

注意这里注解的prefix要与yml中的前缀一致。

这里@Component("MqttConfig")注解指定了名称,后面使用。

MQTT相关连接、回调方法实现

完整项目目录参考如下,新建三个mqtt相关的三个类

第一个类MqttReceiver,用来连接代理服务器,订阅主题

package com.badao.demo.mqtt;import com.badao.demo.config.MqttConfig;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.websocket.ClientEndpoint;
import java.util.Random;/*** @ClassName: MqttReceiver* @Description: mqtt连接代理服务器,订阅主题*/
@Component
@ClientEndpoint
@DependsOn("MqttConfig")
public class MqttReceiver {// mqtt配置public  String host;public  String port;public  String user;public  String password;public static MqttClient client;public static MqttService videoMqttService;public static String clientId = "badao_" + new Random().nextInt(999999999);public void start() {try {// broker为代理端,clientId即的客户端id,MemoryPersistence设置clientId的保存形式,默认为以内存保存String broker = "tcp://"+this.host+":"+this.port;client = new MqttClient(broker, clientId, new MemoryPersistence());videoMqttService = new MqttService();// MQTT连接设置MqttConnectOptions connOpts = new MqttConnectOptions();// 修复过多发布bugconnOpts.setMaxInflight(1000);// 设置用户名和密码connOpts.setUserName(this.user);connOpts.setPassword(this.password.toCharArray());// 设置心跳时间间隔,服务端实时了解客户端是否与其保持连接的情况connOpts.setKeepAliveInterval(5);connOpts.setMaxReconnectDelay(10);connOpts.setConnectionTimeout(10);connOpts.setAutomaticReconnect(true);// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,// 这里设置为true表示每次连接到服务器都以新的身份连接connOpts.setCleanSession(true);// 设置回调client.setCallback(new MqttMessageCallback());// 建立连接System.out.println("连接到mqtt服务器: " + broker);client.connect(connOpts);// 订阅主题videoMqttService.subscribe(client);} catch (MqttException me) {me.printStackTrace();}}public void setHost(String host) {this.host = host;}public void setPort(String port) {this.port = port;}public void setUser(String user) {this.user = user;}public void setPassword(String password) {this.password = password;}/*** @Description: Spring实例化该Bean之后马上执行此方法* @param:* @Return: void* @Exception:*/@PostConstructvoid init() {// 实例化mqtt对象MqttReceiver client = new MqttReceiver();// 从yml文件设置参数// 实例化mqtt配置对象MqttConfig mqttConfig =new MqttConfig();client.setHost(mqttConfig.getHost());client.setPort(mqttConfig.getPort());client.setUser(mqttConfig.getUser());client.setPassword(mqttConfig.getPassword());// 启动client.start();}
}

第二个类MqttService用来配置接收到订阅消息后,用来对消息的处理

package com.badao.demo.mqtt;import com.alibaba.fastjson.JSON;
import com.badao.demo.entity.TVideoMsg;
import com.badao.demo.entity.VideoAlarm;
import com.badao.demo.service.serviceImpl.TVideoMsgServiceImpl;
import com.badao.demo.utils.RedisCache;
import com.badao.demo.utils.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import java.util.Date;/*** @ClassName: MqttService* @Description: mqtt订阅消息处理*/
@Slf4j
public class MqttService {private final static String MQTT_VIDEO_TOPIC = "badao";private TVideoMsgServiceImpl tVideoMsgService= SpringUtils.getBean(TVideoMsgServiceImpl.class);private RedisCache redisCache= SpringUtils.getBean("redisCache");/*** @Description: mqtt接受到订阅消息后,对消息的处理* @param: topic* @param: message* @Return: void* @Exception:*/public void getMqttMessage(String topic, MqttMessage message) {try {System.out.println("received topic:"+topic+",message:"+message.toString());if (MqttTopic.isMatched(MQTT_VIDEO_TOPIC, topic)) {String msg = new String(message.getPayload());//序列化json数据到实体VideoAlarm videoAlarm = JSON.parseObject(msg, VideoAlarm.class);//根据自己业务进行数据转换为需要入库的数据实体TVideoMsg tVideoMsgDTO= new TVideoMsg();tVideoMsgDTO.setTimestamp(videoAlarm.getTimestamp());tVideoMsgDTO.setAlgId(videoAlarm.getAlg_id());tVideoMsgDTO.setCameraId(videoAlarm.getCamera_id());tVideoMsgDTO.setImagePath(videoAlarm.getImage_path());tVideoMsgDTO.setAlarmDate(new Date());//mysql入库存储逻辑tVideoMsgService.insertTVideoMsg(tVideoMsgDTO);//redis缓存入库逻辑String reKeyTemp="badaoTest:" + videoAlarm.getCamera_id();redisCache.setCacheObject(reKeyTemp,tVideoMsgDTO);}} catch (Exception e) {log.error(e.getMessage(), e);}}/*** @Description: 订阅主题* @param: client* @Return: void* @Exception:*/public void subscribe(MqttClient client) {try {client.subscribe(MQTT_VIDEO_TOPIC);} catch (Exception e) {e.printStackTrace();}}
}

上面这个类为业务处理关键类,配置的主题为badao,然后这里需要在非Spring管理环境中获取bean,比如需要获取redis

private RedisCache redisCache= SpringUtils.getBean("redisCache");

以及获取插入到mysql中的service

后面附SpringUtiles的具体实现以及service相关实现。

getMqttMessage用来对收到对应主题消息的处理,首先进行json序列化。

然后使用一个对应json字段的实体对象接收,后面模仿业务层面的处理,转换为需要入库的DTO,

分别进行存储mysql数据库和reids操作。

第三个类是MqttMessageCallback用来对建立连接、断开连接、收到消息、自动重连的回调处理。

package com.badao.demo.mqtt;import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*** @ClassName: MqttMessageCallback* @Description: mqtt回调函数*/
@Slf4j
public class MqttMessageCallback implements MqttCallbackExtended {public static MqttService videoMqttService;ExecutorService newFixedThreadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),Runtime.getRuntime().availableProcessors(), 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(500), new ThreadFactoryBuilder().setNameFormat("mqtt-%d").build(), new ThreadPoolExecutor.AbortPolicy());/*** @Description: 服务器断开与客户机的连接时客户机将调用此方法* @param: cause* @Return: void* @Exception:*/@Overridepublic void connectionLost(Throwable cause) {log.error("MQTT丢失连接");try {MqttReceiver.client.reconnect();} catch (MqttException e) {e.printStackTrace();}}/*** @Description: 当客户机的与预订主题相匹配的预订到达时,就会调此方法* @param: topic* @param: message* @Return: void* @Exception:*/@Overridepublic void messageArrived(String topic, MqttMessage message) {videoMqttService = new MqttService();try {newFixedThreadPool.execute(() -> videoMqttService.getMqttMessage(topic, message));} catch (Exception e) {e.printStackTrace();}}/*** @Description: 由mqtt客户机调用以将传递令牌传回客户机应用程序* @param: token* @Return: void* @Exception:*/@Overridepublic void deliveryComplete(IMqttDeliveryToken token) {}@Overridepublic void connectComplete(boolean b, String s) {log.error("MQTT触发自动重连");try {videoMqttService = new MqttService();videoMqttService.subscribe(MqttReceiver.client);if (MqttReceiver.client.isConnected()) {log.error("MQTT重新连接成功!");} else {log.error("MQTT重连失败!");}} catch (Exception e) {e.printStackTrace();}}
}

注意这里在收到消息的回调messageArrived中,使用线程池支持高并发的处理。

实现mqtt消息入库、缓存相关的类

上面收到mqtt消息的处理中SpringUtils的代码实现

package com.badao.demo.utils;import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;/*** spring工具类 方便在非spring管理环境中获取bean**/
@Component
public final class SpringUtils implements BeanFactoryPostProcessor
{/** Spring应用上下文环境 */private static ConfigurableListableBeanFactory beanFactory;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{SpringUtils.beanFactory = beanFactory;}/*** 获取对象** @param name* @return Object 一个以所给名字注册的bean的实例* @throws BeansException**/@SuppressWarnings("unchecked")public static <T> T getBean(String name) throws BeansException{return (T) beanFactory.getBean(name);}/*** 获取类型为requiredType的对象** @param clz* @return* @throws BeansException**/public static <T> T getBean(Class<T> clz) throws BeansException{T result = (T) beanFactory.getBean(clz);return result;}/*** 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true** @param name* @return boolean*/public static boolean containsBean(String name){return beanFactory.containsBean(name);}/*** 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)** @param name* @return boolean* @throws NoSuchBeanDefinitionException**/public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException{return beanFactory.isSingleton(name);}/*** @param name* @return Class 注册对象的类型* @throws NoSuchBeanDefinitionException**/public static Class<?> getType(String name) throws NoSuchBeanDefinitionException{return beanFactory.getType(name);}/*** 如果给定的bean名字在bean定义中有别名,则返回这些别名** @param name* @return* @throws NoSuchBeanDefinitionException**/public static String[] getAliases(String name) throws NoSuchBeanDefinitionException{return beanFactory.getAliases(name);}/*** 获取aop代理对象** @param invoker* @return*/@SuppressWarnings("unchecked")public static <T> T getAopProxy(T invoker){return (T) AopContext.currentProxy();}
}

首先建立一个mysql的表

然后上面收到mqtt消息对应的实体类TVideoMsg

package com.badao.demo.entity;
import java.util.Date;public class TVideoMsg
{private static final long serialVersionUID = 1L;private Long id;private Long timestamp;private Long algId;private Long cameraId;private String algName;private String confirm;private String path;private String imagePath;private String alarmRule;private String status;private String handlePeople;private Date handleDate;private Date alarmDate;private String handleContent;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public Long getTimestamp() {return timestamp;}public void setTimestamp(Long timestamp) {this.timestamp = timestamp;}public Long getAlgId() {return algId;}public void setAlgId(Long algId) {this.algId = algId;}public Long getCameraId() {return cameraId;}public void setCameraId(Long cameraId) {this.cameraId = cameraId;}public String getAlgName() {return algName;}public void setAlgName(String algName) {this.algName = algName;}public String getConfirm() {return confirm;}public void setConfirm(String confirm) {this.confirm = confirm;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}public String getImagePath() {return imagePath;}public void setImagePath(String imagePath) {this.imagePath = imagePath;}public String getAlarmRule() {return alarmRule;}public void setAlarmRule(String alarmRule) {this.alarmRule = alarmRule;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public String getHandlePeople() {return handlePeople;}public void setHandlePeople(String handlePeople) {this.handlePeople = handlePeople;}public Date getHandleDate() {return handleDate;}public void setHandleDate(Date handleDate) {this.handleDate = handleDate;}public Date getAlarmDate() {return alarmDate;}public void setAlarmDate(Date alarmDate) {this.alarmDate = alarmDate;}public String getHandleContent() {return handleContent;}public void setHandleContent(String handleContent) {this.handleContent = handleContent;}
}

消息数据转换后的DTO类VideoAlarm

import lombok.Data;@Data
public class VideoAlarm {private Long id;private Long timestamp;private Long alg_id;private Long camera_id;private String alg_name;private Integer confirm;private String path;private String image_path;private String alarm_rule;private boolean status = false;}

其它mapper层、service层代码等可自行根据表使用代码生成工具等生成。

测试mqtt连接、断线重连、接受json数据解析入库存储流程

启动并连接mqtt服务端

测试断连重连

入库存储测试

全部代码资源、mosquitto、sql文件资源打包下载

下载地址:

https://download.csdn.net/download/BADAO_LIUMANG_QIZHI/90283310

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

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

相关文章

【考研408数据结构-08】 图论基础:存储结构与遍历算法

&#x1f4da; 【考研408数据结构-08】 图论基础&#xff1a;存储结构与遍历算法 &#x1f3af; 考频&#xff1a;⭐⭐⭐⭐⭐ | 题型&#xff1a;选择题、综合应用题、算法设计题 | 分值&#xff1a;约8-15分 引言 想象你正在规划一次跨省自驾游&#xff0c;面前摊开一张复杂的…

SQL查询语句的执行顺序

好的&#xff0c;我们来详细讲解一下 SQL 查询语句的执行顺序。 很多人会误以为 SQL 的执行顺序就是我们写的顺序&#xff08;SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY&#xff09;&#xff0c;但实际上&#xff0c;数据库引擎在底层处理查询…

【Android】OKHttp网络请求原理和弱网优化

【Android】OKHttp网络请求原理和弱网优化 1. OkHttp 网络请求原理 OkHttp 的请求过程可以分为 四个关键阶段&#xff1a; &#xff08;假设你是通过 OkHttpClient.newCall(request).enqueue(callback) 发的请求&#xff09; OkHttpClient│▼ Dispatcher (调度器)│▼ RealC…

概率论基础教程第4章 随机变量(四)

4.7 泊松随机变量 定义 泊松随机变量&#xff1a;如果一个取值于 $ 0, 1, 2, \ldots $ 的随机变量对某一个 $ \lambda > 0 $&#xff0c;其分布列为&#xff1a; p(i)P{Xi}e−λλii!i0,1,2,⋯(7.1) \boxed{p(i) P\{X i\} e^{-\lambda} \frac{\lambda^i}{i!} \qquad i 0…

Unity高级开发:反射原理深入解析与实践指南 C#

Unity高级开发&#xff1a;反射原理深入解析与实践指南 在Unity游戏开发中&#xff0c;反射&#xff08;Reflection&#xff09; 是一项强大的元编程技术&#xff0c;它允许程序在运行时动态地获取类型信息、创建对象和调用方法。根据Unity官方统计&#xff0c;超过78%的商业游…

任务五 推荐页面功能开发

一、推荐页面需求分析 由推荐页面效果图,可以看出,推荐页面主要由顶部轮播图和歌单列表页面组成 二、推荐页面轮播图组件封装 由于轮播图,可能在项目多个地方用到,因此可以将轮播图抽调成一个组件,然后各个页面调用这个组件。 在开发轮播图组件时,需要安装better-scro…

【工具使用-Docker容器】构建自己的镜像和容器

1. 镜像和容器介绍 镜像&#xff08;Image&#xff09;是一个只读的模板&#xff0c;包含了运行某个应用所需的全部内容&#xff0c;比如&#xff1a; 操作系统&#xff08;比如 Ubuntu&#xff09;应用程序代码运行环境&#xff08;如 Python、Java、Node.js 等&#xff09;库…

Apache Shiro550 漏洞(CVE-2016-4437):原理剖析与实战 SOP

在 Web 安全领域&#xff0c;反序列化漏洞一直是威胁等级极高的存在&#xff0c;而 Apache Shiro 框架中的 Shiro550 漏洞&#xff08;CVE-2016-4437&#xff09;&#xff0c;更是因利用门槛低、影响范围广&#xff0c;成为渗透测试中频繁遇到的经典漏洞。本文将从 “原理拆解”…

安卓开发者自学鸿蒙开发3持久化/数据与UI绑定

AppStorage,PersistentStorage与StorageLink AppStorage是应用全局状态管理器,数据存储于内存中,常见的如全局的黑暗模式,StorageLink是用来绑定AppStorage的键到ui上的工具,省去了用户手写代码的无聊过程,PersistentStorage可以绑定AppStorage的键,自动持久化到磁盘,同时支持多…

GitHub宕机生存指南:从应急协作到高可用架构设计

GitHub宕机生存指南&#xff1a;从应急协作到高可用架构设计 摘要&#xff1a; GitHub作为全球开发者的协作中心&#xff0c;其服务稳定性至关重要。然而&#xff0c;任何在线服务都无法保证100%的可用性。本文深入探讨了当GitHub意外宕机时&#xff0c;开发团队应如何应对。我…

机器学习算法篇(十三)------词向量转化的算法思想详解与基于词向量转换的文本数据处理的好评差评分类实战(NPL基础实战)

目录 一、词向量原理介绍 (1). 词向量的核心概念 (2). 传统文本表示的局限性 1. 独热编码&#xff08;One-Hot Encoding&#xff09; 2. 词袋模型&#xff08;Bag of Words&#xff09; 3. TF-IDF (3). 词向量的核心原理 (4). 主流词向量模型 1. Word2Vec&#xff08;20…

JS自定义函数(2)

1. 变量的作用域全局变量定义&#xff1a;在函数外声明的变量作用范围&#xff1a;在整个JS文档中生效生命周期&#xff1a;页面关闭时销毁局部变量定义&#xff1a;在函数内用 var 声明的变量作用范围&#xff1a;只能在函数内部使用生命周期&#xff1a;函数执行完毕时销毁作…

【数据集】Argoverse 数据集:自动驾驶研究的强大基石

Argoverse数据集&#xff1a;自动驾驶研究的强大基石 在自动驾驶技术蓬勃发展的当下&#xff0c;高质量的数据集对于推动相关算法研究和模型训练起着举足轻重的作用。Argoverse 数据集便是其中的佼佼者&#xff0c;它为自动驾驶领域的众多任务提供了丰富且优质的数据资源。 一、…

--- 哈希表和哈希冲突 ---

哈希&#xff08;散列&#xff09;方法是对插入的数据通过哈希函数计算出一个哈希地值&#xff0c;并将这个哈希地址作为储存改数据的地址&#xff0c;这样下次再查找这个数据时&#xff0c;只需要通过哈希函数再获取到该地址然后直接去拿就好这样就做到了不经过任何比较&#…

数学建模-评价类问题-优劣解距离法(TOPSIS)

1-AI带你认识TOPSIS&#x1f4d8; 一、TOPSIS 方法简介1. ​​基本定义&#xff1a;​​​​TOPSIS&#xff08;Technique for Order Preference by Similarity to an Ideal Solution&#xff09;​​&#xff0c;中文通常称为&#xff1a;•​​优劣解距离法​​•​​逼近理想…

Go协程:从汇编视角揭秘实现奥秘

&#x1f680; Go协程&#xff1a;从汇编视角揭秘实现奥秘 #Go语言 #协程原理 #并发编程 #底层实现 引用&#xff1a; 关于 Go 协同程序&#xff08;Coroutines 协程&#xff09;、Go 汇编及一些注意事项。 &#x1f31f; 前言&#xff1a;重新定义并发编程范式 在当今高并发…

MySQL 事务(重点)

MySQL 这个东西注定是可能会被多个用户/客户端来同时访问的&#xff0c;这是肯定的&#xff0c;MySQL 中存放的都是数据&#xff0c;数据可能有一个上层线程在用&#xff0c;也有可能另一个线程也要用...数据是被所有人共享的&#xff0c;所以就注定了 MySQL 这样的服务在一个时…

uniapp:h5链接拉起支付宝支付

场景&#xff1a;APP内点击支付宝支付&#xff0c;后台返回类似链接https://qr.alipay.com/bax***********c3050 通常做法是&#xff0c;使用plus.runtime.openURL(deeplink);先打开浏览器&#xff0c;浏览器会提示打开支付宝&#xff0c;之后是支付流程。现在可以省略跳转h5的…

吴恩达 Machine Learning(Class 3)

Week 11.1 K-means Cluster centroidK-means 是无监督学习中聚类算法的一种&#xff0c;核心在于更新聚类质心&#xff1b;首先将每个点分配给几个聚类质心&#xff0c;取决于那些点离哪个质心更近&#xff1b;然后将几个聚类质心移动到分配给他的所有点的平均值&#xff0c;不…

MyBatis 动态查询语句详解:让 SQL 更灵活可控

MyBatis 动态查询语句详解&#xff1a;让 SQL 更灵活可控 在日常的数据库操作中&#xff0c;我们经常会遇到需要根据不同条件拼接 SQL 语句的场景。比如查询用户时&#xff0c;可能需要根据姓名、年龄、性别等多个条件进行筛选&#xff0c;而这些条件往往是动态变化的 —— 有时…