WebSocket 实战案例:从设计到部署
在前六篇文章中,我们深入探讨了 WebSocket 的基础原理、服务端开发、客户端实现、安全实践、性能优化和测试调试。今天,让我们通过一个实战案例,看看如何将这些知识应用到实际项目中。我曾在一个大型在线教育平台中,通过 WebSocket 实现了实时互动课堂,支持了数万名师生的同时在线。
项目背景
我们要实现一个实时互动课堂系统,主要功能包括:
- 实时音视频
- 课堂互动
- 共享白板
- 实时聊天
- 课堂管理
让我们从系统设计开始。
系统架构
实现系统架构:
// app.js
const express = require('express')
const https = require('https')
const fs = require('fs')
const path = require('path')
const WebSocket = require('ws')
const Redis = require('ioredis')
const { ClusterManager } = require('./cluster-manager')
const { ConnectionPool } = require('./connection-pool')
const { MessageQueue } = require('./message-queue')
const { RoomManager } = require('./room-manager')
const { UserManager } = require('./user-manager')
const { MediaServer } = require('./media-server')class ClassroomServer {constructor(options = {}) {this.options = {port: 8080,sslPort: 8443,redisUrl: 'redis://localhost:6379',mediaServer: 'localhost:8000',...options}// 初始化组件this.cluster = new ClusterManager()this.pool = new ConnectionPool()this.queue = new MessageQueue()this.rooms = new RoomManager()this.users = new UserManager()this.media = new MediaServer(this.options.mediaServer)this.initialize()}// 初始化服务器async initialize() {// 创建 Express 应用this.app = express()this.setupExpress()// 创建 HTTPS 服务器this.server = https.createServer({key: fs.readFileSync('server.key'),cert: fs.readFileSync('server.cert')}, this.app)// 创建 WebSocket 服务器this.wss = new WebSocket.Server({server: this.server,path: '/ws'})// 连接 Redisthis.redis = new Redis(this.options.redisUrl)// 设置事件处理器this.setupEventHandlers()// 启动服务器await this.start()}// 设置 ExpresssetupExpress() {// 静态文件this.app.use(express.static('public'))// API 路���this.app.use('/api', require('./routes/api'))// 错误处理this.app.use((err, req, res, next) => {console.error('Express error:', err)res.status(500).json({ error: 'Internal server error' })})}// 设置事件处理器setupEventHandlers() {// WebSocket 连接this.wss.on('connection', (ws, req) => {this.handleConnection(ws, req)})// Redis 订阅this.redis.on('message', (channel, message) => {this.handleRedisMessage(channel, message)})// 进程消息process.on('message', (message) => {this.handleProcessMessage(message)})}// 处理 WebSocket 连接async handleConnection(ws, req) {try {// 验证用户const user = await this.users.authenticate(req)// 创建连接const connection = this.pool.createConnection(ws, user)// 加入房间const roomId = req.query.roomIdif (roomId) {await this.rooms.joinRoom(roomId, connection)}// 设置消息处理器ws.on('message', (message) => {this.handleMessage(connection, message)})// 设置关闭处理器ws.on('close', () => {this.handleClose(connection)})// 发送欢迎消息connection.send({type: 'welcome',data: {user: user.toJSON(),room: roomId ? await this.rooms.getRoomInfo(roomId) : null}})} catch (error) {console.error('Connection error:', error)ws.close()}}// 处理消息async handleMessage(connection, message) {try {const data = JSON.parse(message)// 验证消息if (!this.validateMessage(data)) {throw new Error('Invalid message format')}// 处理不同类型的消息switch (data.type) {case 'chat':await this.handleChatMessage(connection, data)breakcase 'whiteboard':await this.handleWhiteboardMessage(connection, data)breakcase 'media':await this.handleMediaMessage(connection, data)breakcase 'control':await this.handleControlMessage(connection, data)breakdefault:throw new Error('Unknown message type')}} catch (error) {console.error('Message error:', error)connection.send({type: 'error',error: error.message})}}// 处理聊天消息async handleChatMessage(connection, message) {const { roomId, content } = message.data// 验证权限if (!await this.rooms.canChat(connection.user, roomId)) {throw new Error('No permission to chat')}// 创建聊天消息const chat = {id: generateId(),roomId,userId: connection.user.id,content,timestamp: Date.now()}// 保存到数据库await this.rooms.saveChatMessage(chat)// 广播到房间await this.rooms.broadcast(roomId, {type: 'chat',data: chat})}// 处理白板消息async handleWhiteboardMessage(connection, message) {const { roomId, action } = message.data// 验证权限if (!await this.rooms.canDraw(connection.user, roomId)) {throw new Error('No permission to draw')}// 处理白板动作const result = await this.rooms.handleWhiteboardAction(roomId, action)// 广播到房间await this.rooms.broadcast(roomId, {type: 'whiteboard',data: {action,result}})}// 处理媒体消息async handleMediaMessage(connection, message) {const { roomId, stream } = message.data// 验证权限if (!await this.rooms.canPublish(connection.user, roomId)) {throw new Error('No permission to publish')}// 处理媒体流const result = await this.media.handleStream(roomId, stream)// 广播到房间await this.rooms.broadcast(roomId, {type: 'media',data: {stream,result}})}// 处理控制消息async handleControlMessage(connection, message) {const { roomId, action } = message.data// 验证权限if (!await this.rooms.canControl(connection.user, roomId)) {throw new Error('No permission to control')}// 处理控制命令const result = await this.rooms.handleControlAction(roomId, action)// 广播到房间await this.rooms.broadcast(roomId, {type: 'control',data: {action,result}})}// 处理连接关闭async handleClose(connection) {try {// 离开房间const roomId = connection.roomIdif (roomId) {await this.rooms.leaveRoom(roomId, connection)}// 清理连接this.pool.removeConnection(connection)// 广播离开消息if (roomId) {await this.rooms.broadcast(roomId, {type: 'user_left',data: {userId: connection.user.id}})}} catch (error) {console.error('Close error:', error)}}// 处理 Redis 消息handleRedisMessage(channel, message) {try {const data = JSON.parse(message)// 处理不同类型的消息switch (channel) {case 'room_update':this.handleRoomUpdate(data)breakcase 'user_update':this.handleUserUpdate(data)breakcase 'system_update':this.handleSystemUpdate(data)break}} catch (error) {console.error('Redis message error:', error)}}// 处理进程消息handleProcessMessage(message) {try {// 处理不同类型的消息switch (message.type) {case 'status':this.handleStatusUpdate(message.data)breakcase 'reload':this.handleReload(message.data)breakcase 'shutdown':this.handleShutdown(message.data)break}} catch (error) {console.error('Process message error:', error)}}// 启动服务器async start() {// 启动 HTTP 服务器this.server.listen(this.options.sslPort, () => {console.log(`HTTPS server running on port ${this.options.sslPort}`)})// 启动 HTTP 重定向const redirectServer = express().use((req, res) => {res.redirect(`https://${req.headers.host}${req.url}`)}).listen(this.options.port, () => {console.log(`HTTP redirect server running on port ${this.options.port}`)})}// 关闭服务器async shutdown() {console.log('Shutting down classroom server...')// 关闭 WebSocket 服务器this.wss.close()// 关闭 HTTP 服务器this.server.close()// 关闭 Redis 连接await this.redis.quit()// 清理资源await this.pool.shutdown()await this.queue.shutdown()await this.rooms.shutdown()await this.media.shutdown()console.log('Classroom server shutdown complete')}
}
房间管理
实现房间管理:
// room-manager.js
class RoomManager {constructor(options = {}) {this.options = {maxRooms: 1000,maxUsersPerRoom: 100,...options}this.rooms = new Map()this.stats = new Stats()this.initialize()}// 初始化房间管理器initialize() {// 监控房间数this.stats.gauge('rooms.total', () => this.rooms.size)this.stats.gauge('rooms.active', () => this.getActiveRooms().size)}// 创建房间async createRoom(options) {// 检查房间数限制if (this.rooms.size >= this.options.maxRooms) {throw new Error('Room limit reached')}// 创建房间const room = {id: generateId(),name: options.name,type: options.type,createdAt: Date.now(),users: new Map(),state: {whiteboard: [],chat: [],media: []},...options}this.rooms.set(room.id, room)this.stats.increment('rooms.created')return room}// 加入房间async joinRoom(roomId, connection) {const room = this.rooms.get(roomId)if (!room) {throw new Error('Room not found')}// 检查人数限制if (room.users.size >= this.options.maxUsersPerRoom) {throw new Error('Room is full')}// 添加用户room.users.set(connection.user.id, {connection,joinedAt: Date.now(),state: {}})// 更新连接connection.roomId = roomIdthis.stats.increment('room.users.joined')// 广播加入消息await this.broadcast(roomId, {type: 'user_joined',data: {user: connection.user.toJSON()}})return room}// 离开房间async leaveRoom(roomId, connection) {const room = this.rooms.get(roomId)if (!room) return// 移除用户room.users.delete(connection.user.id)// 更新连接delete connection.roomIdthis.stats.increment('room.users.left')// 如果房间为空,清理房间if (room.users.size === 0) {await this.cleanupRoom(roomId)}}// 广播消息async broadcast(roomId, message, excludeId = null) {const room = this.rooms.get(roomId)if (!room) return 0let count = 0room.users.forEach((user, userId) => {if (userId !== excludeId) {try {user.connection.send(message)count++} catch (error) {console.error('Broadcast error:', error)}}})this.stats.increment('room.messages.broadcast', count)return count}// 获取房间信息async getRoomInfo(roomId) {const room = this.rooms.get(roomId)if (!room) {throw new Error('Room not found')}return {id: room.id,name: room.name,type: room.type,users: Array.from(room.users.values()).map(user => ({id: user.connection.user.id,name: user.connection.user.name,role: user.connection.user.role,joinedAt: user.joinedAt})),state: room.state}}// 更新房间状态async updateRoomState(roomId, update) {const room = this.rooms.get(roomId)if (!room) {throw new Error('Room not found')}// 更新状态room.state = {...room.state,...update}// 广播更新await this.broadcast(roomId, {type: 'room_state_updated',data: {state: room.state}})return room.state}// 清理房间async cleanupRoom(roomId) {const room = this.rooms.get(roomId)if (!room) return// 保存房间数据await this.saveRoomData(room)// 删除房间this.rooms.delete(roomId)this.stats.increment('rooms.cleaned')}// 保存房间数据async saveRoomData(room) {// 实现数据持久化逻辑}// 获取活跃房间getActiveRooms() {const activeRooms = new Map()this.rooms.forEach((room, id) => {if (room.users.size > 0) {activeRooms.set(id, room)}})return activeRooms}// 获取统计信息getStats() {return {rooms: {total: this.rooms.size,active: this.getActiveRooms().size},...this.stats.getAll()}}// 关闭管理器async shutdown() {// 保存所有房间数据for (const room of this.rooms.values()) {await this.saveRoomData(room)}// 清理资源this.rooms.clear()}
}
用户管理
实现用户管理:
// user-manager.js
class UserManager {constructor(options = {}) {this.options = {sessionTimeout: 3600000, // 1 小时...options}this.users = new Map()this.sessions = new Map()this.stats = new Stats()this.initialize()}// 初始化用户管理器initialize() {// 启动会话清理setInterval(() => {this.cleanupSessions()}, 300000) // 5 分钟// 监控用户数this.stats.gauge('users.total', () => this.users.size)this.stats.gauge('users.online', () => this.getOnlineUsers().size)}// 认证用户async authenticate(req) {const token = this.extractToken(req)if (!token) {throw new Error('No token provided')}// 验证会话const session = this.sessions.get(token)if (!session) {throw new Error('Invalid session')}// 更新会话session.lastActivity = Date.now()return session.user}// 创建会话async createSession(user) {const token = generateToken()this.sessions.set(token, {user,createdAt: Date.now(),lastActivity: Date.now()})this.stats.increment('sessions.created')return token}// 清理会话cleanupSessions() {const now = Date.now()let cleaned = 0this.sessions.forEach((session, token) => {if (now - session.lastActivity > this.options.sessionTimeout) {this.sessions.delete(token)cleaned++}})if (cleaned > 0) {this.stats.increment('sessions.cleaned', cleaned)}}// 获取在线用户getOnlineUsers() {const onlineUsers = new Map()this.sessions.forEach(session => {onlineUsers.set(session.user.id, session.user)})return onlineUsers}// 获取用户信息async getUserInfo(userId) {const user = this.users.get(userId)if (!user) {throw new Error('User not found')}return {id: user.id,name: user.name,role: user.role,online: this.isUserOnline(userId)}}// 检查用户是否在线isUserOnline(userId) {return Array.from(this.sessions.values()).some(session => session.user.id === userId)}// 获取统计信息getStats() {return {users: {total: this.users.size,online: this.getOnlineUsers().size},sessions: {total: this.sessions.size},...this.stats.getAll()}}
}
媒体服务器
实现媒体服务器:
// media-server.js
class MediaServer {constructor(url, options = {}) {this.url = urlthis.options = {maxStreams: 1000,...options}this.streams = new Map()this.stats = new Stats()this.initialize()}// 初始化媒体服务器initialize() {// 监控���数量this.stats.gauge('streams.total', () => this.streams.size)this.stats.gauge('streams.active', () => this.getActiveStreams().size)}// 处理媒体流async handleStream(roomId, stream) {// 检查流数量限制if (this.streams.size >= this.options.maxStreams) {throw new Error('Stream limit reached')}// 创建流const mediaStream = {id: generateId(),roomId,type: stream.type,createdAt: Date.now(),state: 'new'}// 处理不同类型的流switch (stream.type) {case 'video':await this.handleVideoStream(mediaStream, stream)breakcase 'audio':await this.handleAudioStream(mediaStream, stream)breakcase 'screen':await this.handleScreenStream(mediaStream, stream)break}this.streams.set(mediaStream.id, mediaStream)this.stats.increment('streams.created')return mediaStream}// 处理视频流async handleVideoStream(mediaStream, stream) {// 实现视频流处理逻辑}// 处理音频流async handleAudioStream(mediaStream, stream) {// 实现音频流处理逻辑}// 处理屏幕共享流async handleScreenStream(mediaStream, stream) {// 实现屏幕共享处理逻辑}// 停止流async stopStream(streamId) {const stream = this.streams.get(streamId)if (!stream) return// 停止流stream.state = 'stopped'// 清理资源this.streams.delete(streamId)this.stats.increment('streams.stopped')}// 获取活跃流getActiveStreams() {const activeStreams = new Map()this.streams.forEach((stream, id) => {if (stream.state === 'active') {activeStreams.set(id, stream)}})return activeStreams}// 获取统计信息getStats() {return {streams: {total: this.streams.size,active: this.getActiveStreams().size},...this.stats.getAll()}}// 关闭服务器async shutdown() {// 停止所有流for (const stream of this.streams.values()) {await this.stopStream(stream.id)}}
}
部署配置
实现部署配置:
// config.js
module.exports = {// 服务器配置server: {port: process.env.PORT || 8080,sslPort: process.env.SSL_PORT || 8443,host: process.env.HOST || 'localhost'},// Redis 配置redis: {url: process.env.REDIS_URL || 'redis://localhost:6379',options: {retryStrategy: (times) => {return Math.min(times * 50, 2000)}}},// 媒体服务器配置media: {url: process.env.MEDIA_SERVER || 'localhost:8000',options: {maxStreams: 1000}},// 集群配置cluster: {workers: process.env.WORKERS || require('os').cpus().length,restartDelay: 1000},// 安全配置security: {ssl: {key: process.env.SSL_KEY || 'server.key',cert: process.env.SSL_CERT || 'server.cert'},cors: {origin: process.env.CORS_ORIGIN || '*'}},// 房间配置room: {maxRooms: 1000,maxUsersPerRoom: 100},// 用户配置user: {sessionTimeout: 3600000},// 监控配置monitor: {enabled: true,interval: 1000,historySize: 3600},// 日志配置log: {level: process.env.LOG_LEVEL || 'info',file: process.env.LOG_FILE || 'classroom.log'}
}
最佳实践
系统设计
- 模块化架构
- 可扩展设计
- 高可用配置
功能实现
- 实时通讯
- 媒体处理
- 状态同步
性能优化
- 连接池管理
- 消息队列
- 集群部署
运维支持
- 监控系统
- 日志记录
- 故障恢复
安全保障
- 身份认证
- 数据加密
- 权限控制
写在最后
通过这个实战案例,我们深入探讨了如何构建一个完整的 WebSocket 应用。从系统设计到具体实现,从功能开发到性能优化,我们不仅关注了技术细节,更注重了实际应用中的各种挑战。
记住,一个优秀的实时应用需要在功能、性能、安全等多个方面取得平衡。在实际开发中,我们要根据具体需求选择合适的实现方案,确保应用能够稳定高效地运行。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍
相关文章:
WebSocket 实战案例:从设计到部署
在前六篇文章中,我们深入探讨了 WebSocket 的基础原理、服务端开发、客户端实现、安全实践、性能优化和测试调试。今天,让我们通过一个实战案例,看看如何将这些知识应用到实际项目中。我曾在一个大型在线教育平台中,通过 WebSocket 实现了实时互动课堂,支持了数万名师生的同时在…...
selenium合集
环境搭建步骤 安装selenium pip install selenium 安装浏览器 安装浏览器驱动 谷歌浏览器:chromdriver.exe ie浏览器:ieserverdriver.exe FireFox浏览器:geckodriver.exe 特别注意⚠️:下载驱动版本必须与浏览器版本一致 下载地址 淘宝镜像࿱…...
JVM生产环境常用参数配置及调优建议
一、生产常用参数配置 JAVA_OPTS"-server -Xms3000m -Xmx3000m -Xmn1500m -XX:UseG1GC -XX:ConcGCThreads8 -XX:PrintGCDetails -XX:PrintGCTimeStamps -Xloggc:./g1-gc.log -XX:MaxMetaspaceSize256m -XX:-UseGCOverheadLimit -XX:UseCompressedOops -XX:HeapDumpOnOu…...
Spring Boot 3 实现 MySQL 主从数据库之间的数据同步
✅ Spring Boot 3 实现 MySQL 主从数据库之间的数据同步 在实际项目中,为了提高 系统的读性能 和 数据的可用性,通常会使用 主从数据库架构。Spring Boot 提供了对 多数据源 的良好支持,可以轻松配置 主从数据库 的数据同步,实现…...

【小程序开发】- 小程序版本迭代指南(版本发布教程)
一,版本号 版本号是小程序版本的标识,通常由一系列数字组成,如 1.0.0、1.1.0 等。版本号的格式通常是 主版本号.次版本号.修订号 主版本号:当小程序有重大更新或不兼容的更改时,主版本号会增加。 次版本号:…...
MySQL 间隙锁避免“可重复读”出现“幻读”
在数据库事务处理中,可重复读(Repeatable Read)是一个常用的隔离级别,但其默认行为可能导致幻读现象。然而,在 MySQL 的实现中,通过 **间隙锁(Gap Lock)**机制,能够避免幻…...

揭秘区块链隐私黑科技:零知识证明如何改变未来
文章目录 1. 引言:什么是零知识证明?2. 零知识证明的核心概念与三大属性2.1 完备性(Completeness)2.2 可靠性(Soundness)2.3 零知识性(Zero-Knowledge) 3. 零知识证明的工作原理4. 零…...
JavaWeb开发:从入门到精通
近年来,JavaWeb开发已经成为了互联网开发领域的重要技术之一。无论是大型企业还是个人项目,都离不开JavaWeb开发。本文将为您介绍JavaWeb开发的基本概念、常用技术和开发流程,帮助您快速入门并掌握这一技术。 一、JavaWeb开发的基本概念 Jav…...

2025年01月07日Github流行趋势
项目名称:khoj 项目地址url:https://github.com/khoj-ai/khoj项目语言:Python历史star数:20105今日star数:363项目维护者:debanjum, sabaimran, MythicalCow, aam-at, shantanuSakpal项目简介:你…...

c#集成npoi根据excel模板导出excel
NuGet中安装npoi 创建excel模板,替换其中的内容生成新的excel文件。 例子中主要写了这四种情况: 1、替换单个单元格内容; 2、替换横向多个单元格; 3、替换表格; 4、单元格中插入图片; using System.IO; …...

Vue2移动端(H5项目)项目封装switch组件支持动态设置开启关闭背景色、值及组件内显示文字描述、禁用、switch 的宽度
前言 近期产品需求:Vue2移动端项目需要在switch开关内显示文字,看Vantui没有对应功能,因此自己手撸写了这个组件。 一、最终效果 二、参数配置 1、代码示例: <t-switch v-model"check"/>2、配置参数(…...
MATLAB语言的语法糖
MATLAB语言的语法糖 引言 在编程语言的发展历程中,语法糖(Syntactic Sugar)被广泛提及。它指的是一种编程语言的语法特性,旨在使代码更易读、更易写,虽然这些特性并不增加语言的表达能力,但能使程序员的生…...

数字IC设计高频面试题
在数字IC设计领域,面试是评估候选人技术能力和问题解决能力的重要环节。数字IC设计的复杂性和要求在不断提高。面试官通常会提出一系列面试题,以考察应聘者在数字设计、验证、时钟管理、功耗优化等方面的专业知识和实践经验。 这些题目不仅涉及理论知识…...
OpenCV 4.5至4.10版本更新概述
OpenCV 4.5至4.10版本更新概述 OpenCV 从 4.5 到 4.10 版本的更迭中,每个版本都引入了新功能、优化和修复。以下是主要版本的更新内容概述: OpenCV 4.5.x 系列 4.5.0 (2020年10月) 新增对 YOLOv4 的支持。引入 DNN 模块的改进,包括对 ONNX …...
OSPF - LSA对照表
LSA的三要素,如何唯一表示一条LSA Type:表示是几类的LSA Link-id:这个比较特殊,不同的LSA的Link-ID不同 Advertising router:谁产生的LSA 常用的就是1、2、3、4、5、7型LSA 点击蓝字跳转LSA详细介绍(持续更新中…...

游戏引擎学习第77天
仓库: https://gitee.com/mrxiao_com/2d_game 回顾昨天的 bug 今天我们继续开发进度,进行调试昨天代码的问题,主要是关于如何跟踪玩家和敌人在世界中的高度位置。虽然我们做的是一款 2D 游戏,但我们希望能够处理多层的房间,玩家…...

【项目实战1】五子棋游戏
目录 C语言编程实现五子棋:: game.h game.c 1.打印菜单 2.打印棋盘 3.玩家下棋 4.判断五子连珠 5.判断输赢 6.游戏运行 game.c完整源代码展示 test.c C语言编程实现五子棋:: game.h #pragma once #include<stdio.h> …...
HTML5 动画效果:淡入淡出(Fade In/Out)详解
HTML5 动画效果:淡入淡出(Fade In/Out)详解 淡入淡出(Fade In/Out)是一种常见的动画效果,使元素逐渐显现或消失,增强用户体验。以下是淡入淡出的详细介绍及实现示例。 1. 淡入淡出的特点 平滑…...
Conmi的正确答案——Cordova使用“src-cordova/config.xml”编辑“Android平台”的“uses-permission”
Cordova版本:12.0.0 (cordova-lib12.0.1) 1、配置例程: <platform name"android"><config-file target"AndroidManifest.xml" parent"/manifest"><uses-permission android:name"android.permission…...

在Mysql环境下对数据进行增删改查
一、插入数据: insert into 表名 [(字段名)] values (字段对应的值1,字段对应的值2,…)[,(字段对应的值1,字段对应的值2,…)]; insert into students (id,name,age,height,gender,cls_id,is_delete) values (0,小明,18,180.00,2,1,0)在学生表中插入“小明”数据的…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...