当前位置: 首页 > news >正文

SpringBoot+VUE2完成WebSocket聊天(数据入库)

下载依赖

        <!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- MybatisPlus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.0</version></dependency>

数据库sql

CREATE TABLE `interrogation`  (`ID` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',`INITIATOR_ID` int(0) NULL DEFAULT NULL COMMENT '发起人的ID()',`DOCTOR` int(0) NULL DEFAULT NULL COMMENT '接受人ID',`STATUS` int(0) NULL DEFAULT NULL COMMENT '状态(1.开始聊天和2.结束)',PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '聊天列表' ROW_FORMAT = Dynamic;CREATE TABLE `interrogation_chat`  (`ID` int(0) NOT NULL AUTO_INCREMENT COMMENT 'ID',`INTERROGATION_ID` int(0) NULL DEFAULT NULL COMMENT '聊天表的id',`CONTENT` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '内容',`INITIATOR_ID` int(0) NULL DEFAULT NULL COMMENT '发送人',`CREATE_TIME` datetime(0) NULL DEFAULT NULL COMMENT '发送时间',PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 130 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '聊天记录表' ROW_FORMAT = Dynamic;

实体类 

后续业务采用mybatils-plus写的,如果不会使用手写sql也是可以的

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;import java.util.Date;/*** @description:  聊天记录表* @author: * @date: 2024/10/27 9:37**/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class InterrogationChat {/*** ID**/@TableId(value = "ID", type = IdType.AUTO)private Integer id;/*** 聊天表的id**/@TableField("INTERROGATION_ID")private Integer interrogationId;/*** 内容**/@TableField("CONTENT")private String content;/*** 发送人**/@TableField("INITIATOR_ID")private Integer initiatorId;/*** 发送时间**/@TableField("CREATE_TIME")@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date createTime;
}

websocker配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @author * @date 2024/10/23* @apiNote*/@Configuration
public class WebSocketConfig {/*** 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;import javax.websocket.server.ServerEndpointConfig;/*** @author * @date 2024/10/30* @apiNote*/public class MyEndpointConfigure extends ServerEndpointConfig.Configurator implements ApplicationContextAware {private static volatile BeanFactory context;@Overridepublic <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException{return context.getBean(clazz);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException{System.out.println("auto load"+this.hashCode());MyEndpointConfigure.context = applicationContext;}
}import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author * @date 2024/10/30* @apiNote*/@Configuration
public class MyConfigure {@Beanpublic MyEndpointConfigure newConfigure(){return new MyEndpointConfigure();}}import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.*;import java.io.Serializable;/*** @description: 聊天列表* @author: * @date: 2024/10/27 9:30**/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class Interrogation implements Serializable {/*** ID**/@TableId(value = "ID", type = IdType.AUTO)private Integer id;/*** 发起人ID**/@TableField("INITIATOR_ID")private Integer initiatorId;/*** 收到人ID**/@TableField("DOCTOR")private Integer doctor;/*** 状态(1.开始聊天和2.结束)**/@TableField("STATUS")private Integer status;}

websocket业务代码

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ruoyi.dai.config.MyEndpointConfigure;
import com.ruoyi.system.RemoteSendService;
import com.ruoyi.system.domain.InterrogationChat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @author * @date 2024/10/23* @apiNote*/@ServerEndpoint(value = "/imserver/{username}", configurator = MyEndpointConfigure.class)
@Component
public class WebSocketServer {@Autowiredprivate RemoteSendService remoteSendService;private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);/*** 记录当前在线连接数*/public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("username") String username) {sessionMap.put(username, session);log.info("有新用户加入,username={}, 当前在线人数为:{}", username, sessionMap.size());JSONObject result = new JSONObject();JSONArray array = new JSONArray();result.set("users", array);for (Object key : sessionMap.keySet()) {JSONObject jsonObject = new JSONObject();jsonObject.set("username", key);array.add(jsonObject);}sendAllMessage(JSONUtil.toJsonStr(result));}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(Session session, @PathParam("username") String username) {sessionMap.remove(username);log.info("有一连接关闭,移除username={}的用户session, 当前在线人数为:{}", username, sessionMap.size());}/*** 收到客户端消息后调用的方法* 后台收到客户端发送过来的消息* onMessage 是一个消息的中转站* 接受 浏览器端 socket.send 发送过来的 json数据** @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message, Session session, @PathParam("username") String username) {log.info("服务端收到用户username={}的消息:{}", username, message);JSONObject obj = JSONUtil.parseObj(message);String toUsername = obj.getStr("to");String text = obj.getStr("content"); // 注意这里修改成了 "content"//非初始化消息转发和缓存if (!text.equals("初始化连接")){Session toSession = sessionMap.get(toUsername);if (toSession != null) {JSONObject jsonObject = new JSONObject();jsonObject.set("id",obj.getLong("id"));jsonObject.set("interrogationId",obj.getInt("interrogationId"));jsonObject.set("content",text); // 注意这里修改成了 "content"jsonObject.set("createTime",obj.getDate("createTime")); // 注意这里修改成了 "createTime"jsonObject.set("initiatorId",obj.getInt("initiatorId")); // 注意这里修改成了 "initiatorId"this.sendMessage(jsonObject.toString(), toSession);// 数据库记录聊天记录InterrogationChat interrogationChat = new InterrogationChat();interrogationChat.setInterrogationId(obj.getInt("interrogationId"));interrogationChat.setContent(text); // 注意这里修改成了 "content"interrogationChat.setCreateTime(obj.getDate("createTime")); // 注意这里修改成了 "createTime"interrogationChat.setInitiatorId(obj.getInt("initiatorId")); // 注意这里修改成了 "initiatorId"remoteSendService.sendMessage(interrogationChat);log.info("发送给用户username={},消息:{}", toUsername, jsonObject.toString());} else {log.info("发送失败,未找到用户username={}的session", toUsername);}}}@OnErrorpublic void onError(Session session, Throwable error) {log.error("发生错误");error.printStackTrace();}/*** 服务端发送消息给客户端*/private void sendMessage(String message, Session toSession) {try {log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);toSession.getBasicRemote().sendText(message);} catch (Exception e) {log.error("服务端发送消息给客户端失败", e);}}/*** 服务端发送消息给所有客户端*/private void sendAllMessage(String message) {try {for (Session session : sessionMap.values()) {log.info("服务端给客户端[{}]发送消息{}", session.getId(), message);session.getBasicRemote().sendText(message);}} catch (Exception e) {log.error("服务端发送消息给客户端失败", e);}}
}

聊天业务代码

controller

@RestController
@RequestMapping("/send")
public class SendController {@Resourceprivate SendService sendService;/*** 聊天消息入库**/@PostMapping("/sendMessage")public R<String> sendMessage(@RequestBody InterrogationChat message) {return R.ok(sendService.sendMessage(message));}/*** 发起一个聊天**/@PostMapping("/launchInterrogation")public R<String> launchInterrogation(@RequestBody Interrogation interrogation ){return R.ok(sendService.launchInterrogation(interrogation));}
}

service

public interface SendService {String sendMessage(InterrogationChat message);String launchInterrogation(Interrogation interrogation);
}

impl

@Service
public class SendServiceImpl implements SendService {@Resourceprivate InterrogationChatMapper interrogationChatMapper;@Resourceprivate InterrogationMapper interrogationMapper;@Overridepublic String sendMessage(InterrogationChat message) {try {interrogationChatMapper.insert(message);return "发送成功";}catch (Exception e){e.printStackTrace();return "发送失败";}}@Overridepublic String launchInterrogation(Interrogation interrogation) {// 有没有Interrogation ones = interrogationMapper.selectOne(new QueryWrapper<Interrogation>().eq("INITIATOR_ID",interrogation.getInitiatorId()).eq("DOCTOR",interrogation.getDoctor()).eq("STATUS",1));// 为空创建if (StringUtils.isNull(ones)){interrogation.setStatus(1);int  insert = interrogationMapper.insert(interrogation);return  interrogation.getId().toString();}// 有就直接返回return ones.getId().toString();}
}

mapper

@Mapper
public interface InterrogationMapper extends BaseMapper<Interrogation> {
}@Mapper
public interface InterrogationChatMapper extends BaseMapper<InterrogationChat> {
}

前端VUE代码

index.vue  第一个页面

<template><div><h2>欢迎来到聊天室</h2><div v-for="message in messages" :key="message.id" class="message-container"><div v-if="message.initiatorId !== id" class="message-received"><span class="message-text">{{ message.content }}</span></div><div v-else class="message-sent"><span class="message-text">{{ message.content }}</span><span class="time">{{ message.createTime }}</span></div></div><input type="text" v-model="newMessage" placeholder="输入消息" /><button @click="sendMessage">发送</button></div>
</template><script>
import { launchInterrogation, historyInterrogation } from '@/api/interrogation/interrogationType';export default {components: {},props: {},data() {return {interrogation: {id: null,initiatorId: 101,doctor: 1},messages: [],newMessage: '',ws: null,id: 1,isConnected: false,interrogationId: null,};},computed: {},watch: {},methods: {launchInterrogation() {launchInterrogation(this.interrogation).then(res => {this.interrogationId = res.data;historyInterrogation(this.interrogationId).then(res => {console.log("查询到的历史记录:", res.data.interrogationChats);if (res.data != null) {this.messages = res.data.interrogationChats || [];}});});},connectToWebSocket() {const wsUrl = `ws://ip:port/imserver/${this.id}`;this.ws = new WebSocket(wsUrl);this.ws.onopen = () => {console.log('WebSocket 连接已打开');this.isConnected = true;// 在 WebSocket 连接完全建立后再发送初始化消息this.sendMessageIfConnected();};this.ws.onclose = () => {console.log('WebSocket 连接已关闭');this.isConnected = false;};this.ws.onerror = (error) => {console.error('WebSocket 错误', error);this.isConnected = false;};this.ws.onmessage = (event) => {const message = JSON.parse(event.data);if (message.content !== undefined) {console.log("获取到的回调数据:", message);this.messages.push(message);} else {console.warn('接收到的数据无效或格式错误:', event.data);}};},sendMessage() {if (this.newMessage.trim() === '') return;const now = new Date();const formattedTime = now.toISOString().slice(0, 19).replace('T', ' ');const message = {interrogationId: this.interrogationId,id: Date.now(),content: this.newMessage,createTime: formattedTime,initiatorId: this.id,to: '101',};if (this.isConnected) {this.messages.push(message);this.ws.send(JSON.stringify(message));this.newMessage = '';} else {console.warn('WebSocket 连接尚未建立,消息未发送');}},sendMessageIfConnected() {if (this.isConnected) {const initialMessage = {interrogationId: this.interrogationId,id: Date.now(),content: '初始化连接',initiatorId: this.id,createTime: new Date().toISOString().slice(0, 19).replace('T', ' '),to: '101',};this.ws.send(JSON.stringify(initialMessage));}}},created() {this.connectToWebSocket();this.launchInterrogation();},mounted() {},beforeCreate() {},beforeMount() {},beforeUpdate() {},updated() {},beforeDestroy() {if (this.ws && this.ws.readyState === WebSocket.OPEN) {this.ws.close();}},destroyed() {},activated() {}
}
</script><style scoped>
.message-container {display: flex;align-items: flex-start;margin-bottom: 10px;
}.message-received {margin-right: auto;background-color: #ddd;border-radius: 10px;padding: 10px;max-width: 70%;word-wrap: break-word;
}.message-sent {margin-left: auto;background-color: #00bfff;border-radius: 10px;padding: 10px;max-width: 70%;word-wrap: break-word;display: flex;flex-direction: column;align-items: flex-end;
}.message-text {display: block;color: #333;font-size: 1em;
}.time {font-size: 0.8em;color: #999;margin-top: 5px;
}input {width: 200px;
}
button {margin-left: 10px;
}
</style>

inde1.vue  第二个页面

<template><div><h2>欢迎来到聊天室</h2><div v-for="message in messages" :key="message.id" class="message-container"><div v-if="message.initiatorId !== id" class="message-received"><span class="message-text">{{ message.content }}</span></div><div v-else class="message-sent"><span class="message-text">{{ message.content }}</span><span class="time">{{ message.createTime }}</span></div></div><input type="text" v-model="newMessage" placeholder="输入消息" /><button @click="sendMessage">发送</button></div>
</template><script>
import { launchInterrogation, historyInterrogation } from '@/api/interrogation/interrogationType';export default {components: {},props: {},data() {return {interrogation: {id: null,initiatorId: 101,doctor: 1},messages: [],newMessage: '',ws: null,id: 101,isConnected: false,interrogationId: null,};},computed: {},watch: {},methods: {launchInterrogation() {launchInterrogation(this.interrogation).then(res => {this.interrogationId = res.data;historyInterrogation(this.interrogationId).then(res => {console.log("查询到的历史记录:", res.data.interrogationChats);if (res.data != null) {this.messages = res.data.interrogationChats || [];}});});},connectToWebSocket() {const wsUrl = `ws://ip:port/imserver/${this.id}`;this.ws = new WebSocket(wsUrl);this.ws.onopen = () => {console.log('WebSocket 连接已打开');this.isConnected = true;this.sendMessageIfConnected();};this.ws.onclose = () => {console.log('WebSocket 连接已关闭');this.isConnected = false;};this.ws.onerror = (error) => {console.error('WebSocket 错误', error);this.isConnected = false;};this.ws.onmessage = (event) => {const message = JSON.parse(event.data);if (message.content !== undefined) {console.log("获取到的回调数据:", message);this.messages.push(message);} else {console.warn('接收到的数据无效或格式错误:', event.data);}};},sendMessage() {if (this.newMessage.trim() === '') return;const now = new Date();const formattedTime = now.toISOString().slice(0, 19).replace('T', ' ');const message = {//问诊记录idinterrogationId: this.interrogationId,id: Date.now(),content: this.newMessage,createTime: formattedTime,initiatorId: this.id,to: '1',};if (this.isConnected) {this.messages.push(message);this.ws.send(JSON.stringify(message));this.newMessage = '';} else {console.warn('WebSocket 连接尚未建立,消息未发送');}},sendMessageIfConnected() {if (this.isConnected) {const initialMessage = {//问诊记录idinterrogationId: this.interrogationId,id: Date.now(),content: '初始化连接',initiatorId: this.id,createTime: new Date().toISOString().slice(0, 19).replace('T', ' '),to: '1',};this.ws.send(JSON.stringify(initialMessage));}}},created() {this.connectToWebSocket();this.launchInterrogation();},mounted() {},beforeCreate() {},beforeMount() {},beforeUpdate() {},updated() {},beforeDestroy() {if (this.ws && this.ws.readyState === WebSocket.OPEN) {this.ws.close();}},destroyed() {},activated() {}
}
</script><style scoped>
.message-container {display: flex;align-items: flex-start;margin-bottom: 10px;
}.message-received {margin-right: auto;background-color: #ddd;border-radius: 10px;padding: 10px;max-width: 70%;word-wrap: break-word;
}.message-sent {margin-left: auto;background-color: #00bfff;border-radius: 10px;padding: 10px;max-width: 70%;word-wrap: break-word;display: flex;flex-direction: column;align-items: flex-end;
}.message-text {display: block;color: #333;font-size: 1em;
}.time {font-size: 0.8em;color: #999;margin-top: 5px;
}input {width: 200px;
}
button {margin-left: 10px;
}
</style>

js方法代码

import request from '@/utils/request'export function launchInterrogation(data) {return request({url: '/send/launchInterrogation',method: 'post',data})
}export function historyInterrogation(param){return request({url: '/send/historyInterrogation?id='+param,method: 'get',})
}

效果

 

刷新聊天记录不会消失

注意:因为服务器时间比系统时间慢八个小时,注意同步一下,不然可能发送完消息刷新页面,从数据库读取到的消息发送时间会慢八个小时

相关文章:

SpringBoot+VUE2完成WebSocket聊天(数据入库)

下载依赖 <!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- MybatisPlus --><dependency><groupId>com.ba…...

理解 CSS 中的绝对定位与 Flex 布局混用

理解 CSS 中的绝对定位与 Flex 布局混用 在现代网页设计中&#xff0c;CSS 布局技术如 flex 和绝对定位被广泛使用。然而&#xff0c;这两者结合使用时&#xff0c;可能会导致一些意想不到的布局问题。本文将探讨如何正确使用绝对定位元素&#xff0c;避免它们受到 flex 布局的…...

Redis 事务 问题

前言 相关系列 《Redis & 目录》《Redis & 事务 & 源码》《Redis & 事务 & 总结》《Redis & 事务 & 问题》 参考文献 《Redis事务详解》 Redis事务是什么&#xff1f; 标准的事务是指执行时具备原子性/一致性/隔离性/持久性的一系列操作。…...

Cpp学习手册-进阶学习

C标准库和C20新特性 C标准库概览&#xff1a; 核心库组件介绍&#xff1a; 容器&#xff1a; C 标准库提供了多种容器&#xff0c;它们各有特点&#xff0c;适用于不同的应用场景。 std::vector&#xff1a; vector&#xff1a;动态数组&#xff0c;支持快速随机访问。 #in…...

代码随想录-字符串-反转字符串中的单词

题目 题解 法一:纯粹为了做出本题&#xff0c;暴力解 没有技巧全是感情 class Solution {public String reverseWords(String s) {//首先去除首尾空格s s.trim();String[] strs s.split("\\s");StringBuilder sb new StringBuilder();//定义一个公共的字符反转…...

勒索软件通过易受攻击的 Cyber​​Panel 实例攻击网络托管服务器

一个威胁行为者&#xff08;或可能多个&#xff09;使用 PSAUX 和其他勒索软件攻击了大约 22,000 个易受攻击的 Cyber​​Panel 实例以及运行该实例的服务器上的加密文件。 PSAUX 赎金记录&#xff08;来源&#xff1a;LeakIX&#xff09; Cyber​​Panel 漏洞 Cyber​​Pane…...

Open WebUI + openai API / vllm API ,实战部署教程

介绍Open WebUI + Ollama 的使用: https://www.dong-blog.fun/post/1796 介绍vllm 的使用:https://www.dong-blog.fun/post/1781 介绍 Ollama 的使用: https://www.dong-blog.fun/post/1797 本篇博客玩个花的,Open WebUI 本身可以兼容openai 的api, 那来尝试一下。 仅供…...

InsuranceclaimsController

目录 1、 InsuranceclaimsController 1.1、 保险理赔结算 1.2、 生成预约单号 1.3、 保存索赔表 InsuranceclaimsController using QXQPS.Models; using QXQPS.Vo; using System; using System.Collections; using System.Collections.Generic; using System.Li…...

如何成为开源代码库Dify的contributor:解决issue并提交PR

前言 Dify 是一个开源的大语言模型&#xff08;LLM&#xff09;应用开发平台&#xff0c;它融合了后端即服务&#xff08;Backend as Service&#xff09;和LLMOps的理念&#xff0c;旨在简化和加速生成式AI应用的创建和部署。Dify提供了一个用户友好的界面和一系列强大的工具…...

SQL进阶技巧:巧用异或运算解决经典换座位问题

目录 0 问题描述 1 数据准备 2 问题分析 2.1 什么是异或 2.2异或有什么特性? 2.3 异或应用 2.4 本问题采用异或SQL解决方案 3 小结 0 问题描述 表 seat中有2个字段id和student id 是该表的主键(唯一值)列,student表示学生姓名。 该表的每一行都表示学生的姓名和 ID。…...

【MySQL】 运维篇—数据库监控:使用MySQL内置工具(如SHOW命令、INFORMATION_SCHEMA)进行监控

随着应用程序的增长&#xff0c;数据库的性能和稳定性变得至关重要。监控数据库的状态和性能可以帮助数据库管理员&#xff08;DBA&#xff09;及时发现问题&#xff0c;进行故障排查&#xff0c;并优化数据库的运行效率。通过监控工具&#xff0c;DBA可以获取实时的性能指标、…...

【温酒笔记】DMA

参考文档&#xff1a;野火STM32F103 1. Direct Memory Access-直接内存访问 DMA控制器独立于内核 是一个单独的外设 DMA1有7个通道DMA2有5个通道DMA有四个等级&#xff0c;非常高&#xff0c;高&#xff0c;中&#xff0c;低四个优先级如果优先等级相同&#xff0c;通道编号越…...

力扣判断字符是否唯一(位运算)

文章目录 给一个数n,判断它的二进制位中第x位是0还是1(从0开始计数)将一个数n的二进制位第X位修改为1(从0开始计数)将一个数n的二进制第x位修改为0(从0开始计数)提取一个数n二进制中最右侧的1去掉一个数n二进制表示中最右侧的1 今天我们通过判断字符是否唯一这个题来了解位运算…...

GPU和CPU区别?为什么挖矿、大模型都用GPU?

GPU(图形处理单元)和CPU(中央处理单元)是计算机中两种不同类型的处理器&#xff0c;它们在设计和功能上有很大的区别。 CPU是计算机的大脑&#xff0c;专门用于执行各种通用任务&#xff0c;如操作系统管理、数据处理、多任务处理等。它的架构设计旨在适应多种任务&#xff0c…...

新兴斗篷cloak技术,你了解吗?

随着互联网技术的飞速发展&#xff0c;网络营销领域也经历了翻天覆地的变革。 从最早的网络横幅广告到如今主流的搜索引擎和社交媒体营销&#xff0c;广告形式变得越来越多样。 其中&#xff0c;搜索引擎广告一直以其精准投放而备受青睐&#xff0c;但近年来&#xff0c;一项名…...

【抽代复习笔记】34-群(二十八):不变子群的几道例题

例1&#xff1a;证明&#xff0c;交换群的任何子群都是不变子群。 证&#xff1a;设(G,o)是交换群&#xff0c;H≤G&#xff0c; 对任意的a∈G&#xff0c;显然都有aH {a o h|h∈H} {h o a|h∈H} Ha。 所以H⊿G。 【注&#xff1a;规范的不变子群符号是一个顶角指向左边…...

Chrome和Firefox如何保护用户的浏览数据

在当今数字化时代&#xff0c;保护用户的浏览数据变得尤为重要。浏览器作为我们日常上网的主要工具&#xff0c;其安全性直接关系到个人信息的保密性。本文将详细介绍Chrome和Firefox这两款主流浏览器如何通过一系列功能来保护用户的浏览数据。&#xff08;本文由https://chrom…...

CentOS 7镜像下载

新版本系统镜像下载&#xff08;当前最新是CentOS 7.4版本&#xff09; CentOS官网 官网地址 http://isoredirect.centos.org/centos/7.4.1708/isos/x86_64/ http://mirror.centos.org/centos/7/isos/ 国内的华为云&#xff0c;超级快&#xff1a;https://mirrors.huaweiclou…...

opencv-windows-cmake-Mingw-w64,编译opencv源码

Windows_MinGW_64_OpenCV在线编译动态库,并使用在C项目: (mingw-w64 cmakegithub actions方案) 修改版opencv在线编译: 加入opencv-contrib库, 一起编译生成动态库,在线编译好的opencv动态库,可以下载使用.验证opencv动态库是否可用的模板项目,测试opencv动态库是否可用的模板…...

Puppeteer点击系统:解锁百度流量点击率提升的解决案例

在数字营销领域&#xff0c;流量和搜索引擎优化&#xff08;SEO&#xff09;是提升网站可见性的关键。我开发了一个基于Puppeteer的点击系统&#xff0c;旨在自动化地提升百度流量点击率。本文将介绍这个系统如何通过模拟真实用户行为&#xff0c;优化关键词排名&#xff0c;并…...

Kyber原理解析

Kyber是一种IND-CCA2安全的密钥封装机制。Kyber的安全性基于在模格&#xff08;MLWE问题&#xff09;中解决LWE问题的难度。Kyber的构造采⽤两阶段⽅法&#xff1a;⾸先介绍⼀种⽤来加密固定32字节⻓度的消息原⽂的IND-CPA安全性的公钥加密⽅案&#xff0c;我们称之为 CPAPKE&a…...

2024 CCF CSP-J/S 2024 第二轮认证 真题试卷

2024年信息学奥赛CSP-J2入门级复赛真题试卷 题目总数&#xff1a;4 总分数&#xff1a;400 编程题 第 1 题 问答题 扑克牌(poker) 【题目描述】 小 P 从同学小 Q 那儿借来一副 n 张牌的扑克牌。 本题中我们不考虑大小王&#xff0c;此时每张牌具有两个属性:花色和…...

Android 无障碍服务常见问题梳理

android 无障碍服务本意是为了帮助盲人操作手机而设计&#xff0c;但是现在也有人利用这个做自动化操作。 本片文章讲述的主要用作自动化方面。 官方文档 关于配置方法和接口列表&#xff0c;参考 无障碍 比较常用的接口&#xff1a; 1. 执行点击操作 2. 触摸屏幕&#xf…...

Milvus 与 Faiss:选择合适的向量数据库

向量数据库 Milvus 和 Faiss 都是处理大规模向量数据的工具&#xff0c;尤其适用于需要相似性搜索的场景&#xff0c;比如推荐系统、图像检索和自然语言处理等。但它们各自的设计初衷和功能有所不同&#xff0c;适用于不同的使用场景。下面&#xff0c;我们从性能、功能特性、部…...

2024最全CTF入门指南、CTF夺旗赛及刷题网站(建议收藏!)

文章目录 一、赛事介绍二、竞赛模式三、CTF各大题型简介四、赛题情况分析CTF 工具集合Web | Web 安全&#x1f578; MISC | 杂项❆ 基础工具❆ 解题工具❆ 开源脚本&#x1f511; Crypto | 密码学 &#x1f4ab; Reverse | 逆向基础工具&#x1f4a5; PWN | 二进制 &#x1f44…...

【论文阅读】ESRGAN+

学习资料 论文题目&#xff1a;进一步改进增强型超分辨率生成对抗网络&#xff08;ESRGAN : FURTHER IMPROVING ENHANCED SUPER-RESOLUTION GENERATIVE ADVERSARIAL NETWORK&#xff09;论文地址&#xff1a;2001.08073代码&#xff1a;ncarraz/ESRGANplus&#xff1a; ICASSP …...

北京市首发教育领域人工智能应用指南,力推个性化教育新篇章

近年来&#xff0c;人工智能在全球教育领域的应用呈现蓬勃发展之势&#xff0c;各国都在探索如何将其更好的融入教育体系&#xff0c;在这一背景下&#xff0c;北京市于10月26日发布《北京市教育领域人工智能应用指南》&#xff08;以下简称《指南》&#xff09;&#xff0c;推…...

【Java并发编程】信号量Semaphore详解

一、简介 Semaphore&#xff08;信号量&#xff09;&#xff1a;是用来控制同时访问特定资源的线程数量&#xff0c;它通过协调各个线程&#xff0c;以保证合理的使用公共资源。 Semaphore 一般用于流量的控制&#xff0c;特别是公共资源有限的应用场景。例如数据库的连接&am…...

window11使用wsl2安装Ubuntu22.04

目录 1、快速了解wsl2 安装子系统linux流程&#xff08;B站视频&#xff09; 2、wsl2常用命令 3、windows与子系统Linux文件访问方法 4、子系统linux使用windows网络代理、网络配置&#xff08;镜像网络&#xff0c;非NAT&#xff09; 5、wsl2 Ubuntu miniconda 安装 6、…...

虚拟滚动 - 从基本实现到 Angular CDK

简介 在大数据列表的处理上&#xff0c;虚拟滚动是一种优化性能的有效方式。本篇文章将详细介绍两种常见的虚拟滚动实现方式&#xff1a;使用 transform 属性和 Intersection Observer。重点讲解如何通过 transform 属性实现高效的虚拟滚动&#xff0c;并对比Angular CDK中的实…...

web网站开发怎么分工/目前最好的引流推广方法

一.ADO.NET简介 1、 访问数据库的技术很多&#xff0c;常见的有以下几种&#xff1a; &#xff08;1&#xff09;开放数据库互联&#xff08;ODBC&#xff09; &#xff08;2&#xff09;数据访问对象&#xff08;DAO&#xff09; &#xff08;3&#xff09;远程数据对象&#…...

建设网站写需求分析报告/新人跑业务怎么找客户

一. 事件委托 事件委托就是利用冒泡的原理&#xff0c;把事件加到父级上&#xff0c;来代替子集执行相应的操作&#xff0c;事件委托首先可以极大减少事件绑定次数&#xff0c;提高性能&#xff1b;其次可以让新加入的子元素也可以拥有相同的操作。 比如有20个<li>&#…...

天津做网站美工/潍坊网站关键词推广

说说concurrenthashmap的底层实现 concurrenthashmap是安全的hashmap&#xff0c;它底层数据结构使用的是数组链表红黑树。通过hashcode函数获取哈希值&#xff0c;然后哈希值与数组容量减一进行按位与算出存放的数组下标&#xff0c;再通过equals来判断是否重复&#xff0c;以…...

网站建设SEO优化哪家好/秦皇岛seo招聘

Java import关键字 参考&#xff1a;http://www.educity.cn/java/500811.html 简单的说package就是为了要区分相同的类名&#xff0c;而import的作用就是帮你把类名前面的package补上&#xff0c;编译器看到没有package的类就回到java.lang里找&#xff0c;所以不用import ja…...

wordpress出现自动投稿/东莞做网站的公司有哪些

冯华亮/Brighton Feng---Communication Infrastructure摘要HyperLink 为两个 KeyStone 架构 DSP之间提供了一种高速&#xff0c;低延迟&#xff0c;引脚数量少的通信接口。HyperLink 的用户手册已经详细的对其进行了描述。本文主要是为 HyperLink 的编程提供了一些额外的补充信…...

江苏做网站公司/平台推广策划方案

1.使用router-link 不会让页面刷新&#xff0c;使用a标签会使页面刷新。2.router-link 里面的to"/路由地址" tag""自定义标签" 默认为a标签,linkActiveClass 可以更改默认类名。3.在 HTML5 history 模式下&#xff0c;router-link 会拦截点击事件&…...