当前位置: 首页 > 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;并…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

Xcode 16 集成 cocoapods 报错

基于 Xcode 16 新建工程项目&#xff0c;集成 cocoapods 执行 pod init 报错 ### Error RuntimeError - PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup from attributes: {"isa">"PBXFileSystemSynchro…...

Python爬虫实战:研究Restkit库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...

大数据驱动企业决策智能化的路径与实践

&#x1f4dd;个人主页&#x1f339;&#xff1a;慌ZHANG-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 一、引言&#xff1a;数据驱动的企业竞争力重构 在这个瞬息万变的商业时代&#xff0c;“快者胜”的竞争逻辑愈发明显。企业如何在复杂环…...

李沐--动手学深度学习--GRU

1.GRU从零开始实现 #9.1.2GRU从零开始实现 import torch from torch import nn from d2l import torch as d2l#首先读取 8.5节中使用的时间机器数据集 batch_size,num_steps 32,35 train_iter,vocab d2l.load_data_time_machine(batch_size,num_steps) #初始化模型参数 def …...

C++11 constexpr和字面类型:从入门到精通

文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…...