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

WebSocket 实现指南

WebSocket 实现指南

目录

1. 依赖安装

1.1 安装必要的包

# 安装 gorilla/websocket
go get github.com/gorilla/websocket# 安装 gin 框架
go get github.com/gin-gonic/gin

1.2 更新 go.mod

require (github.com/gin-gonic/gin v1.9.1github.com/gorilla/websocket v1.5.3
)

1.3 配置文件

1.3.1Config.go
package configimport ("os""gopkg.in/yaml.v3"
)type Config struct {Server    ServerConfig    `yaml:"server"`MySQL     MySQLConfig     `yaml:"mysql"`Redis     RedisConfig     `yaml:"redis"`JWT       JWTConfig       `yaml:"jwt"`WebSocket WebSocketConfig `yaml:"websocket"`
}type ServerConfig struct {Port int    `yaml:"port"`Mode string `yaml:"mode"`
}type MySQLConfig struct {Host     string `yaml:"host"`Port     int    `yaml:"port"`User     string `yaml:"user"`Password string `yaml:"password"`DBName   string `yaml:"dbname"`
}type RedisConfig struct {Host     string `yaml:"host"`Port     int    `yaml:"port"`Password string `yaml:"password"`DB       int    `yaml:"db"`
}type JWTConfig struct {Secret string `yaml:"secret"`Expire int    `yaml:"expire"` // token过期时间(小时)
}type WebSocketConfig struct {ReadBufferSize  int   `yaml:"read_buffer_size"`WriteBufferSize int   `yaml:"write_buffer_size"`MaxMessageSize  int64 `yaml:"max_message_size"`WriteWait       int   `yaml:"write_wait"`PongWait        int   `yaml:"pong_wait"`PingPeriod      int   `yaml:"ping_period"`MaxConnections  int   `yaml:"max_connections"`
}var GlobalConfig Configfunc Init() error {// 确保日志目录存在os.MkdirAll("logs", 0755)file, err := os.ReadFile("config/config.yaml")if err != nil {return err}return yaml.Unmarshal(file, &GlobalConfig)
}
1.3.2 config.yaml
server:port: 8080mode: debug  # debug or releasewebsocket:read_buffer_size: 1024write_buffer_size: 1024max_message_size: 512write_wait: 10     # secondspong_wait: 60      # secondsping_period: 54    # secondsmax_connections: 5000mysql:host: localhostport: 3306user: rootpassword: "123456"dbname: my_kingdomredis:host: localhostport: 6379password: "123456"db: 0jwt:secret: "XueZhimin"expire: 24  # hours

2. 代码实现

2.1 WebSocket管理器 (manager.go)

package websocketimport ("encoding/json""fmt""mykingdom/config""net/http""sync""github.com/gin-gonic/gin""github.com/gorilla/websocket"
)// Manager WebSocket管理器
type Manager struct {clients   sync.Map    // 所有客户端连接broadcast chan []byte // 广播消息通道config    *config.WebSocketConfigmessages  chan Message // 新增:消息处理通道
}// Message 定义消息结构
type Message struct {Type     string      `json:"type"`Data     interface{} `json:"data"`ClientID string      `json:"-"` // 发送者的连接ID
}// 配置websocket upgrader
var upgrader = websocket.Upgrader{ReadBufferSize:  1024,WriteBufferSize: 1024,CheckOrigin: func(r *http.Request) bool {return true // 允许所有跨域请求},
}// NewManager 创建WebSocket管理器
func NewManager(config *config.WebSocketConfig) *Manager {return &Manager{broadcast: make(chan []byte),messages:  make(chan Message, 256), // 新增:初始化消息通道config:    config,}
}// HandleWebSocket 处理WebSocket连接
func (m *Manager) HandleWebSocket() gin.HandlerFunc {return func(c *gin.Context) {// 检查连接数限制count := 0m.clients.Range(func(_, _ interface{}) bool {count++return true})if count >= m.config.MaxConnections {c.JSON(http.StatusServiceUnavailable, gin.H{"message": "达到最大连接数限制",})return}conn, done := Upgrade(c)if done {return}// 创建新的客户端连接client := &Client{conn:    conn,manager: m,send:    make(chan []byte, 256),}// 存储客户端连接m.clients.Store(client.conn.RemoteAddr().String(), client)// 启动读写协程go client.readPump()go client.writePump()}
}// Upgrade 升级HTTP连接为WebSocket连接
func Upgrade(c *gin.Context) (*websocket.Conn, bool) {conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)if err != nil {return nil, true}return conn, false
}// Broadcast 广播消息给所有客户端
func (m *Manager) Broadcast(message []byte) {m.clients.Range(func(_, value interface{}) bool {if client, ok := value.(*Client); ok {select {case client.send <- message:default:m.clients.Delete(client.conn.RemoteAddr().String())close(client.send)}}return true})
}// SendToClient 发送消息给指定客户端
func (m *Manager) SendToClient(clientAddr string, message []byte) bool {if value, ok := m.clients.Load(clientAddr); ok {if client, ok := value.(*Client); ok {client.send <- messagereturn true}}return false
}// SendMessage 发送任意类型的消息
func (m *Manager) SendMessage(messageType string, data interface{}) error {message := Message{Type: messageType,Data: data,}// 将消息转换为JSONjsonData, err := json.Marshal(message)if err != nil {return fmt.Errorf("marshal message failed: %v", err)}// 广播消息m.Broadcast(jsonData)return nil
}// SendMessageToClient 发送消息给指定客户端
func (m *Manager) SendMessageToClient(clientAddr string, messageType string, data interface{}) error {message := Message{Type: messageType,Data: data,}// 将消息转换为JSONjsonData, err := json.Marshal(message)if err != nil {return fmt.Errorf("marshal message failed: %v", err)}// 发送给指定客户端if !m.SendToClient(clientAddr, jsonData) {return fmt.Errorf("client not found: %s", clientAddr)}return nil
}// GetMessages 获取消息通道,用于读取消息
func (m *Manager) GetMessages() <-chan Message {return m.messages
}// ReadMessage 读取消息
func ReadMessage(conn *websocket.Conn) ([]byte, error) {_, message, err := conn.ReadMessage()if err != nil {return nil, fmt.Errorf("read message failed: %v", err)}return message, nil
}// WriteMessage 发送消息
func WriteMessage(conn *websocket.Conn, message []byte) error {err := conn.WriteMessage(websocket.TextMessage, message)if err != nil {return fmt.Errorf("write message failed: %v", err)}return nil
}// WriteJSON 发送JSON消息
func WriteJSON(conn *websocket.Conn, v interface{}) error {err := conn.WriteJSON(v)if err != nil {return fmt.Errorf("write json failed: %v", err)}return nil
}

2.2 客户端实现 (client.go)

package websocketimport ("encoding/json""log""time""github.com/gorilla/websocket"
)const (writeWait  = 10 * time.SecondpongWait   = 60 * time.SecondpingPeriod = (pongWait * 9) / 10
)// Client WebSocket客户端
type Client struct {conn    *websocket.Connmanager *Managersend    chan []byte
}// readPump 从WebSocket连接读取消息
func (c *Client) readPump() {defer func() {c.manager.clients.Delete(c.conn.RemoteAddr().String())c.conn.Close()}()c.conn.SetReadDeadline(time.Now().Add(pongWait))c.conn.SetPongHandler(func(string) error {c.conn.SetReadDeadline(time.Now().Add(pongWait))return nil})for {_, message, err := c.conn.ReadMessage()if err != nil {if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {log.Printf("error: %v", err)}break}// 尝试解析消息为Message结构var msg Messageif err := json.Unmarshal(message, &msg); err != nil {log.Printf("unmarshal message failed: %v", err)continue}// 设置发送者IDmsg.ClientID = c.conn.RemoteAddr().String()// 将消息发送到消息通道c.manager.messages <- msg// 广播消息给所有客户端c.manager.Broadcast(message)}
}// writePump 向WebSocket连接写入消息
func (c *Client) writePump() {ticker := time.NewTicker(pingPeriod)defer func() {ticker.Stop()c.conn.Close()}()for {select {case message, ok := <-c.send:c.conn.SetWriteDeadline(time.Now().Add(writeWait))if !ok {c.conn.WriteMessage(websocket.CloseMessage, []byte{})return}w, err := c.conn.NextWriter(websocket.TextMessage)if err != nil {return}w.Write(message)if err := w.Close(); err != nil {return}case <-ticker.C:c.conn.SetWriteDeadline(time.Now().Add(writeWait))if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {return}}}
}

3. 配置说明

3.1 WebSocket配置 (config.yaml)

websocket:read_buffer_size: 1024    # 读缓冲区大小write_buffer_size: 1024   # 写缓冲区大小max_connections: 5000     # 最大连接数

3.2 配置结构体 (config.go)

type WebSocketConfig struct {ReadBufferSize  int   `yaml:"read_buffer_size"`WriteBufferSize int   `yaml:"write_buffer_size"`MaxConnections  int   `yaml:"max_connections"`
}

4. 使用示例

4.1 服务端示例

func main() {r := gin.Default()// 创建WebSocket管理器wsManager := websocket.NewManager(&config.GlobalConfig.WebSocket)// WebSocket连接r.GET("/ws", wsManager.HandleWebSocket())// 广播消息r.POST("/broadcast", func(c *gin.Context) {message := c.PostForm("message")wsManager.Broadcast([]byte(message))c.JSON(200, gin.H{"message": "broadcast sent"})})r.Run(":8080")
}

4.2 前端示例

// 连接WebSocket
const ws = new WebSocket('ws://localhost:8080/ws')// 连接成功
ws.onopen = () => {console.log('连接成功')
}// 接收消息
ws.onmessage = (event) => {console.log('收到消息:', event.data)
}// 发送消息
ws.send('Hello, World!')

4.3 Vue组件示例

<template><div><div>连接状态: {{ isConnected ? '已连接' : '未连接' }}</div><input v-model="message" @keyup.enter="sendMessage"><button @click="sendMessage">发送</button></div>
</template><script setup>
import { ref, onMounted, onUnmounted } from 'vue'const ws = ref(null)
const isConnected = ref(false)
const message = ref('')const connect = () => {ws.value = new WebSocket('ws://localhost:8080/ws')ws.value.onopen = () => isConnected.value = truews.value.onclose = () => isConnected.value = falsews.value.onmessage = (event) => {console.log('收到消息:', event.data)}
}const sendMessage = () => {if (isConnected.value && message.value) {ws.value.send(message.value)message.value = ''}
}onMounted(() => connect())
onUnmounted(() => ws.value?.close())
</script>

4.4 消息收发示例

4.4.1 消息结构
// Message 定义消息结构
type Message struct {Type     string      `json:"type"`    // 消息类型Data     interface{} `json:"data"`    // 消息数据ClientID string      `json:"-"`       // 发送者的连接ID
}
4.4.2 发送消息
// 1. 广播消息给所有客户端
err := wsManager.SendMessage("chat", map[string]interface{}{"user": "系统","content": "欢迎新用户加入",
})// 2. 发送消息给指定客户端
err := wsManager.SendMessageToClient(clientAddr, "private", map[string]interface{}{"content": "这是一条私信","time": time.Now(),
})// 3. 发送游戏动作
err := wsManager.SendMessage("game_action", map[string]interface{}{"action": "move","position": map[string]int{"x": 100,"y": 200,},
})
4.4.3 接收和处理消息
// 启动消息处理协程
go func() {// 获取消息通道msgChan := wsManager.GetMessages()// 循环处理消息for msg := range msgChan {// 根据消息类型处理switch msg.Type {case "chat":handleChatMessage(msg)case "game_action":handleGameAction(msg)case "private":handlePrivateMessage(msg)default:log.Printf("未知消息类型: %s", msg.Type)}}
}()// 处理聊天消息
func handleChatMessage(msg websocket.Message) {data, ok := msg.Data.(map[string]interface{})if !ok {return}log.Printf("来自 %s 的聊天消息: %v", msg.ClientID, data["content"])
}// 处理游戏动作
func handleGameAction(msg websocket.Message) {data, ok := msg.Data.(map[string]interface{})if !ok {return}log.Printf("来自 %s 的游戏动作: %v", msg.ClientID, data["action"])
}
4.4.4 前端发送消息示例
// 1. 发送聊天消息
ws.send(JSON.stringify({type: 'chat',data: {content: '大家好!'}
}))// 2. 发送游戏动作
ws.send(JSON.stringify({type: 'game_action',data: {action: 'move',position: {x: 100,y: 200}}
}))
4.4.5 前端接收消息示例
ws.onmessage = (event) => {try {const message = JSON.parse(event.data)// 根据消息类型处理switch (message.type) {case 'chat':handleChat(message.data)breakcase 'game_action':handleGameAction(message.data)breakcase 'private':handlePrivateMessage(message.data)breakdefault:console.log('未知消息类型:', message.type)}} catch (error) {console.error('解析消息失败:', error)}
}// 处理聊天消息
function handleChat(data) {console.log(`${data.user}: ${data.content}`)
}// 处理游戏动作
function handleGameAction(data) {console.log('游戏动作:', data.action)updateGameState(data.position)
}// 处理私信
function handlePrivateMessage(data) {console.log('收到私信:', data.content)
}

4.5 错误处理示例

// 发送消息时的错误处理
if err := wsManager.SendMessage("chat", data); err != nil {log.Printf("发送消息失败: %v", err)
}// 发送私信时的错误处理
if err := wsManager.SendMessageToClient(clientAddr, "private", data); err != nil {if strings.Contains(err.Error(), "client not found") {log.Printf("用户已离线: %s", clientAddr)} else {log.Printf("发送私信失败: %v", err)}
}

4.6 消息类型建议

const (// 系统消息MessageTypeSystem     = "system"      // 系统通知MessageTypeError      = "error"       // 错误消息// 聊天消息MessageTypeChat       = "chat"        // 公共聊天MessageTypePrivate    = "private"     // 私聊消息// 游戏消息MessageTypeGameAction = "game_action" // 游戏动作MessageTypeGameState  = "game_state"  // 游戏状态MessageTypeMatch      = "match"       // 匹配相关
)

5. 框架整合

5.1 与Gin框架整合

// 中间件:验证WebSocket连接
func AuthWebSocket() gin.HandlerFunc {return func(c *gin.Context) {// 验证tokentoken := c.Query("token")if !validateToken(token) {c.AbortWithStatus(http.StatusUnauthorized)return}c.Next()}
}// 使用中间件
r.GET("/ws", AuthWebSocket(), wsManager.HandleWebSocket())

5.2 与Nginx整合

# nginx.conf
http {map $http_upgrade $connection_upgrade {default upgrade;'' close;}upstream websocket {server 127.0.0.1:8080;}server {listen 80;server_name example.com;# WebSocket代理location /ws {proxy_pass http://websocket;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection $connection_upgrade;proxy_set_header Host $host;}# 其他HTTP请求location / {proxy_pass http://127.0.0.1:8080;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;}}
}

6. 部署说明

6.1 服务器要求

  • 支持WebSocket的现代浏览器
  • Go 1.16+
  • Nginx 1.16+(如果使用)

6.2 部署步骤

  1. 编译Go程序
go build -o server cmd/main.go
  1. 配置Nginx(如果使用)
# 复制nginx配置
sudo cp nginx.conf /etc/nginx/conf.d/websocket.conf# 重启Nginx
sudo systemctl restart nginx
  1. 运行服务
./server

6.3 注意事项

  1. 连接管理

    • 定期清理断开的连接
    • 实现重连机制
    • 控制连接数量
  2. 安全性

    • 添加连接认证
    • 限制消息大小
    • 添加消息验证
  3. 性能优化

    • 使用消息队列
    • 控制广播频率
    • 添加负载均衡
  4. 监控

    • 记录连接数
    • 监控消息流量
    • 错误日志记录

相关文章:

WebSocket 实现指南

WebSocket 实现指南 目录 1. 依赖安装 1.1 安装必要的包 # 安装 gorilla/websocket go get github.com/gorilla/websocket# 安装 gin 框架 go get github.com/gin-gonic/gin1.2 更新 go.mod require (github.com/gin-gonic/gin v1.9.1github.com/gorilla/websocket v1.5.3…...

TRELLIS - 生成 3D 作品的开源模型

TRELLIS 是一个大型 3D 资产生成模型。它接收文本或图像提示&#xff0c;并生成各种格式的高质量 3D 资产&#xff0c;例如 Radiance Fields、3D Gaussians 和网格。TRELLIS 的基石是统一的结构化 LATent &#xff08;SLAT&#xff09; 表示&#xff0c;它允许解码为不同的输出…...

uni-app图文列表到详情页面切换

需求&#xff1a;参考若依框架后&#xff0c;想实现首页浏览文章列表&#xff0c;没有合适的样式参考&#xff0c;所以需要有效果做到“图文列表到详情页面切换”&#xff0c;查阅了一下案例 发现有相应的案例&#xff0c;在导航栏“模板”中找到了 DCloud 插件市场 PC电脑端访…...

ros2-3.4话题通信最佳实践

3.4.1 工程架构设计 需求背景&#xff1a; 第一&#xff0c;通过这个小工具可以看到系统的实时状态信息包括记录信息的时间、主机名称、CPU使用率、内存使用率、内存总大小、剩余内存、网络接收数据量和网络发送数据量; 第二&#xff0c;要有一个简单的界面&#xff0c;可以将…...

Vmware安装centos

用来记录自己安装的过程 一、创建虚拟机安装centos镜像 点击完成后&#xff0c;等待一会会进入centos的系统初始化界面 二、centos初始化配置 三、配置网络 1、虚拟网络编辑器&#xff0c;开启VMnet1、VMnet8的DHCP vmware左上角工具栏&#xff0c;点击【编辑】->【虚拟网…...

51单片机——按键实验

由于机械点的弹性作用&#xff0c;按键开关在闭合时不会马上稳定的接通&#xff0c;在断开时也不会一下子断开&#xff0c;因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的&#xff0c;一般为 5ms 到 10ms&#xff0c;为了确保 CPU 对按键的…...

QT c++ 自定义按钮类 加载图片 美化按钮

如果你有需要利用图片美化按钮的情况&#xff0c;本文能帮助你。 鼠标左键按下按钮和松开&#xff0c;按钮显示不同的图片。 1.按钮类 //因为此类比较简单&#xff0c;1个头文件搞定&#xff0c;没有cpp文件 #ifndef CUSTOMBUTTON_H #define CUSTOMBUTTON_H #include <Q…...

Django:构建高效Web应用的强大框架

在当今快速发展的Web开发领域&#xff0c;选择一个合适的框架对于项目的成功至关重要。Django&#xff0c;作为Python编程语言中最受欢迎的Web框架之一&#xff0c;凭借其强大的功能、高度的可扩展性和简洁的语法&#xff0c;成为了众多开发者心中的首选。本文将深入探讨Django…...

代码随想录算法【Day11】

150. 逆波兰表达式求值 class Solution { public:int evalRPN(vector<string>& tokens) {// 力扣修改了后台测试数据&#xff0c;需要用longlongstack<long long> st; for (int i 0; i < tokens.size(); i) {if (tokens[i] "" || tokens[i] &…...

[SeaTunnel] [MySql CDC] Generate Splits for table db.table error

在使用 SeaTunnel 的 MySQL CDC 时报错&#xff1a; Caused by: org.apache.seatunnel.engine.common.exception.SeaTunnelEngineException: java.lang.RuntimeException: Generate Splits for table db.table error SeaTunnel 版本为 2.3.8 在 GitHub 上找到一种解决方法&am…...

Spring Boot | 基于MinIO实现文件上传和下载

关注&#xff1a;CodingTechWork 介绍 在现代的 web 应用中&#xff0c;文件上传和下载是常见的需求。MinIO 是一个开源的高性能分布式对象存储服务&#xff0c;可以用来存储和管理大量的非结构化数据&#xff0c;如图片、视频、日志文件等。本文将介绍如何在 Spring Boot 应用…...

企业手机号搜索API接口

每日免费每次消耗&#xff1a;按量每日限制&#xff1a;10 次每次请求积分消耗&#xff1a;50 积分 / 次总次数限制&#xff1a;10000 次每次请求间隔&#xff1a;0 秒&#xff0c;并发&#xff1a;50 请求地址 http(s)://api.aiqimao.com/index/apiphoneget/ 调试 请求方法…...

VirtualBox Main API 学习笔记

1. Philosophy 1.1 对于Python&#xff0c;推荐使用"WEBSERVICE"连接方式 Gemini 2.0 Flash Experimental: 对于 Java 和 Python&#xff1a; 文档建议您首先使用"WEBSERVICE"&#xff0c;因为它提供了一种更直观的方式来使用 API。 2. Configuration pi…...

[Linux]Mysql9.0.1服务端脱机安装配置教程(redhat)

前言 本教程适用于在yum源不可用的LInux主机上安装Mysql的场景。 以redhat系主机做操作示例&#xff0c;debian系主机可参照步骤&#xff0c;将对应的rpm -ivh命令换成dpkg -i。 1. 官网下载安装包 https://dev.mysql.com/downloads/mysql/ 1.1 版本分类 MySQL Enterprise…...

uniapp--HBuilder开发

提示&#xff1a;本文为学习内容&#xff0c;若有错误&#xff0c;请联系作者&#xff0c;谦虚受教。 文章目录 前言一、下载HBuilder二、添加modbus相关库1.下载nodejs2.下载modbus库3.项目添加modbus库 三、HBuilder相关功能语句1.文件夹说明2.消息信息框3.开关按钮4.选中按钮…...

计算机毕业设计学习项目-P10080 基于springboot+vue的社团管理系统的设计与实现

项目说明 本号所发布的项目均由我部署运行验证&#xff0c;可保证项目系统正常运行&#xff0c;以及提供完整源码。 如需要远程部署/定制/讲解系统&#xff0c;可以联系我。定制项目未经同意不会上传&#xff01; 项目源码获取方式放在文章末尾处 注&#xff1a;项目仅供学…...

with as提高sql的执行效率

实战sql with cte(UNIT_ID, UNIT_NAME, PARENT_UNIT_ID, UNIT_CODE ) as (select UNIT_ID, UNIT_NAME, PARENT_UNIT_ID , UNIT_CODEfrom HPFM_UNITunion allselect t.UNIT_ID, t.UNIT_NAME, t.PARENT_UNIT_ID, t.UNIT_CODEfrom HPFM_UNIT tjoin cte on t.PARENT_UNIT_ID cte.U…...

【银河麒麟高级服务器操作系统实例】tcp半链接数溢出分析及处理全过程

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://document.kylinos.cn 服务器环境以及配置 系统环境 物理机/虚拟机/云…...

计算机毕业设计Python中华古诗词知识图谱可视化 古诗词智能问答系统 古诗词数据分析 古诗词情感分析模型 自然语言处理NLP 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

分布式ID生成-雪花算法实现无状态

雪花算法这里不再赘述&#xff0c;其缺点是有状态&#xff08;多副本隔离时&#xff0c;依赖手动配置workId和datacenterId&#xff09;&#xff0c;代码如下&#xff1a; /*** 雪花算法ID生成器*/ public class SnowflakeIdWorker {/*** 开始时间截 (2017-01-01)*/private st…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案

在移动互联网营销竞争白热化的当下&#xff0c;推客小程序系统凭借其裂变传播、精准营销等特性&#xff0c;成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径&#xff0c;助力开发者打造具有市场竞争力的营销工具。​ 一、系统核心功能架构&…...