Node 使用 SSE 结合redis 推送数据(echarts 图表实时更新)
1、实时通信有哪些实现方式?
特性 | 轮询(Polling) | WebSocket | SSE (Server-Sent Events) |
---|---|---|---|
通信方向 | 单向(客户端 → 服务端) | 双向(客户端 ↔ 服务端) | 单向(服务端 → 客户端) |
连接方式 | 客户端定时发起HTTP请求 | 持久连接(基于TCP协议) | 持久连接(基于HTTP协议) |
实现复杂度 | 简单 | 较复杂(需处理握手、协议转换等) | 简单 |
实时性 | 低(依赖轮询间隔) | 高(支持实时双向通信) | 高(服务端实时推送) |
性能开销 | 高(频繁建立/关闭连接) | 低(单个持久连接) | 低(单个持久连接) |
示例场景 | 定时获取天气、股票数据 | 在线聊天、多人游戏、实时协作 | 新闻推送、通知、实时股票行情 |
- 轮询:开发简单,但是效率太低了,适合对实时性要求不高的场景。在项目中,基本是不推荐使用,这种做法比较 low。
- WebSocket:开发起来较为复杂。性能好,可以双向实时通信,适合需要交互的场景。推荐使用 Socket.IO 包来开发。
- SSE:开发简单。性能好,但只能单向实时通信,适合服务端向客户端主动推送数据的场景。
另外值得一提的是,现在各个 AI 平台的消息推送
,也都是使用SSE
来实现的。
2、SSE 的基础实现
// 设置正确的响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');// 定时发送数据const intervalId = setInterval(() => {const data = {message: `当前时间是 ${new Date().toLocaleTimeString()}`,};res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 2000);// 处理客户端断开连接
req.on('close', () => {clearInterval(intervalId); // 清除定时器res.end(); // 结束响应
});
- 想要用
SSE来推送数据
,顶部
要按照这个格式来设置响应头
,明确指明为event-stream
。这样才能被识别成SSE。 中间
部分,简单的写了一个定时执行
。每隔两秒钟,计算一下当前的时间。- 然后注意了,这里用的是
res.write
,这是实现SSE的关键代码
。使用res.write可以不断的,分多次发送数据,而不需要一次性发送完整的响应
。 - 还有要注意的是,我们这里
发送数据的格式
,按照SSE的规则
,必须是以data:开头,以两个\n结束
。\n是换行的意思。 - 最下面,如果
连接断开了
,就停止定时器
,并结束响应
。
3、前端部分实现
SSE默认不支持在header中传递数据
,那就直接在URL里传token好了
// 定义管理员令牌,用于身份验证,在实际使用时需将 'xxxx' 替换为真实有效的令牌
const token = 'xxxx';// 创建一个 EventSource 对象,用于建立与服务器的 SSE 连接
// 这里使用模板字符串将 token 拼接到请求的 URL 中,该 URL 指向服务器上处理订单流数据的接口
const eventSource = new EventSource(`http://localhost:3000/admin/charts/stream_order?token=${ token }`);// 当服务器向客户端发送消息时,会触发 onmessage 事件
eventSource.onmessage = function (event) {// 使用 try...catch 块来捕获可能出现的异常,例如数据解析失败的情况try {// 服务器发送的数据存储在 event.data 中,通常是 JSON 格式的字符串// 使用 JSON.parse 方法将其解析为 JavaScript 对象const data = JSON.parse(event.data);// 将解析后的数据打印到控制台,方便调试和查看console.log(data);} catch (error) {// 如果解析数据过程中出现错误,将错误信息打印到控制台console.error('解析数据出错:', error);}
};// 当 SSE 连接出现错误时,会触发 onerror 事件
eventSource.onerror = function (error) {// 将连接错误信息打印到控制台,方便调试和排查问题console.error('SSE 连接出错:', error);
};
4、测试
检查
,选择网络
。然后刷新一下
,就能看到发出的请求
了
注意
:请求的状态是pending
,等待 2 秒钟
后,状态变为200
的时候我们再点进去。这是因为如果点早了
,浏览器还没有将它识别成SSE
。
5、 SSE 小结
- 在
Node
部分,设置好响应头
,并用res.write
不断的发数据出去就好。 前端
部分,用new EventSource
建立连接。并通过onmessage
事件,来接收数据。
6、实践
redis 封装
6.1、后端代码实现
6.1.1、新建 SSE 处理工具类
在根目录
新建streams
文件夹,里面再新建一个sse-handler.js
,用于处理服务器发送事件(Server-Sent Events, SSE)的工具类,代码如下:
const {setKey, getKey} = require('../utils/redis');// 定义一个名为 SSEHandler 的类,用于处理服务器发送事件(SSE)的连接和数据广播
class SSEHandler {// 构造函数,在创建 SSEHandler 实例时会自动调用constructor() {// 使用 ES6 的 Set 数据结构来存储与浏览器建立 SSE 连接的响应对象(res)// Set 可以确保存储的元素唯一,避免重复this.clients = new Set();}/*** 初始化 SSE 数据流,处理新的客户端连接* @param {Object} res - Express 响应对象,用于向客户端发送数据* @param {Object} req - Express 请求对象,包含客户端的请求信息*/initStream(res, req) {// 设置响应头,指定响应内容类型为 text/event-stream,这是 SSE 的标准内容类型res.setHeader('Content-Type', 'text/event-stream');// 设置缓存控制,禁止浏览器缓存响应内容,确保每次请求都能获取最新数据res.setHeader('Cache-Control', 'no-cache');// 设置连接类型为 keep-alive,保持与客户端的长连接res.setHeader('Connection', 'keep-alive');// 刷新响应头,将设置的响应头信息发送给客户端res.flushHeaders();// 将当前客户端的响应对象添加到 clients 集合中,以便后续广播数据时使用this.clients.add(res);// 监听客户端连接关闭事件,当客户端断开连接时触发回调函数req.on('close', () => {// 从 clients 集合中删除当前客户端的响应对象this.clients.delete(res);// 打印日志,提示客户端已断开连接console.log('Client disconnected');});}/*** 向所有连接的客户端广播数据* @param {Object} data - 要广播的数据对象,会被序列化为 JSON 字符串发送给客户端*/async broadcastData(data) {await setKey('sse_broadcast_data', data);// 遍历 clients 集合中的每个客户端响应对象this.clients.forEach((client) => {// 检查客户端响应是否已经结束,如果未结束则继续发送数据if (!client.finished) {// 按照 SSE 的格式,以 "data: " 开头,后面跟上 JSON 序列化后的数据,以两个换行符 "\n\n" 结尾// 并将其写入客户端响应流,发送给客户端client.write(`data: ${JSON.stringify(data)}\n\n`);}});}
}// 将 SSEHandler 类导出,以便其他模块可以引入和使用
module.exports = SSEHandler;
6.1.2、定义和管理统计查询的 SQL 语句·
创建utils/stats-query.js
文件,用于定义和管理统计查询的 SQL 语句:
const statsQueries = {order: "SELECT DATE_FORMAT(`createdAt`, '%Y-%m') AS `month`, COUNT(*) AS `value` FROM `Orders` GROUP BY `month` ORDER BY `month` ASC",user: "SELECT DATE_FORMAT(`createdAt`, '%Y-%m') AS `month`, COUNT(*) AS `value` FROM `Users` GROUP BY `month` ORDER BY `month` ASC",// 可以添加更多类型的统计查询
};function getStatsQuery(type) {return statsQueries[type];
}module.exports = {getStatsQuery
};
6.1.3、 广播服务的实现
创建utils/broadcast-service.js
文件,实现广播服务:
const {sequelize} = require('../models');
const SSEHandler = require('../streams/sse-handler');// 直接在 broadcast-service.js 中定义统计查询配置
const {getStatsQuery} = require('./stats-query');
// 存储不同类型的 SSE 处理程序
const sseHandlers = {};async function broadcastStats(type) {try {if (!getStatsQuery(type)) {console.error(`Invalid stats type: ${type}`);return;}if (!sseHandlers[type]) {sseHandlers[type] = new SSEHandler();}const [results] = await sequelize.query(getStatsQuery(type));const data = {months: results.map(item => item.month),values: results.map(item => item.value)};sseHandlers[type].broadcastData(data);console.log(`${type} stats broadcasted successfully`);} catch (error) {console.error(`Error broadcasting ${type} stats:`, error);}
}function initSSEStream(type, res, req) {if (!getStatsQuery(type)) {console.error(`Invalid stats type: ${type}`);return;}if (!sseHandlers[type]) {sseHandlers[type] = new SSEHandler();}sseHandlers[type].initStream(res, req);
}module.exports = {broadcastStats,initSSEStream
};
6.1.4、新建 charts.js
文件实现 ECharts
路由(仅展示关键代码)
在这一部分,我们将创建一个 charts.js 文件,用于实现与 ECharts 相关的路由功能。该文件主要负责处理不同类型的统计数据请求,并利用 Redis 进行数据缓存,同时支持通过 SSE(Server-Sent Events)进行实时数据推送。
const {getStatsQuery} = require('../../utils/stats-query');
const {initSSEStream} = require('../../utils/broadcast-service');
const {getKey, setKey} = require('../../utils/redis');// 获取统计数据的通用路由
// 此路由根据请求参数中的 type 来获取相应的统计数据
router.get('/:type', async (req, res) => {// 从请求参数中获取统计数据的类型const type = req.params.type;// 根据类型获取对应的统计查询语句const query = getStatsQuery(type);// 如果未找到对应的查询语句,返回错误信息if (!query) {return failure(res, new Error('Invalid stats type'));}try {// 首先尝试从 Redis 中获取缓存的数据let cachedData = await getKey(`stats_data_${type}`);// 如果 Redis 中存在缓存数据,直接返回该数据if (cachedData) {return success(res, 'Stats data fetched successfully', {data: cachedData});}// 若 Redis 中没有缓存数据,执行数据库查询const [results] = await sequelize.query(query);// 对查询结果进行处理,将月份和对应的值分别提取出来const data = {months: results.map(item => item.month),values: results.map(item => item.value)};// 将处理后的数据存储到 Redis 中,以便后续请求可以直接使用缓存数据await setKey(`stats_data_${type}`, data);// 返回查询成功的响应,并将数据发送给客户端success(res, 'Stats data fetched successfully', {data});} catch (error) {// 若出现错误,返回错误响应failure(res, error);}
});/*** SSE 统计不同类型数据* GET /admin/charts/stream/:type* 此路由用于处理 SSE 连接,实时推送不同类型的统计数据*/
router.get('/stream/:type', async (req, res) => {// 从请求参数中获取统计数据的类型const type = req.params.type;// 初始化 SSE 数据流,开始向客户端推送数据initSSEStream(type, res, req);
});
6.1.5、表单新增数据
时 删除redis缓存
以order
为例
// 定义一个处理 POST 请求的路由,路径为根路径('/')
// 当客户端向该路径发送 POST 请求时,此中间件函数会被调用
// req 表示请求对象,包含客户端发送的请求信息
// res 表示响应对象,用于向客户端发送响应
// next 是 Express 中的中间件函数,用于将控制权传递给下一个中间件
router.post('/', async function (req, res, next) {try {// 生成一个唯一的订单号// 使用 uuidv4 函数生成一个通用唯一识别码(UUID)// 然后使用 replace 方法将 UUID 中的连字符(-)替换为空字符串,得到一个无连字符的订单号const outTradeNo = uuidv4().replace(/-/g, '');// 调用 getMembership 函数,根据请求对象 req 获取会员信息// 这个函数可能会从数据库、缓存或其他数据源中获取与当前请求相关的会员信息const membership = await getMembership(req);// 使用 Sequelize 的 create 方法创建一个新的订单记录// 传入一个包含订单信息的对象,这些信息将被插入到数据库的 Order 表中const order = await Order.create({// 订单号,使用前面生成的唯一订单号outTradeNo: outTradeNo,// 用户 ID,从请求对象中获取当前用户的 IDuserId: req.userId,// 订单主题,使用会员的名称subject: membership.name,// 会员时长(月),从会员信息中获取会员的持续月数membershipMonths: membership.durationMonths,// 订单总金额,使用会员的价格totalAmount: membership.price,// 订单状态,初始状态设为 0,通常表示待支付status: 0,});// 删除 Redis 中存储的订单统计数据缓存// 当有新订单创建时,之前的统计数据可能不再准确,所以需要删除缓存// 后续请求统计数据时会重新从数据库获取最新数据await delKey('stats_data_order');// 调用广播服务,将订单统计数据的更新广播出去// 这可能会触发前端页面或其他相关服务更新订单统计信息await broadcastStats('order');// 调用 success 函数,向客户端发送成功响应// 第一个参数是响应对象 res,用于发送响应// 第二个参数是成功消息,告知客户端订单创建成功// 第三个参数是包含订单信息的对象,客户端可以使用这些信息进行后续处理success(res, '订单创建成功。', { order });} catch (error) {// 如果在订单创建过程中出现错误,调用 failure 函数向客户端发送错误响应// 第一个参数是响应对象 res,用于发送响应// 第二个参数是捕获到的错误对象,客户端可以根据错误信息进行相应处理failure(res, error);}
});
6.2、前端代码实现
6.2.1、前端charts
封装
在前端开发中,为了高效管理 ECharts 图表,我们封装了 ChartManager 类,它能处理图表的初始化、数据获取、SSE 连接以及错误处理等操作。以下是详细的代码及说明:
// 配置对象,可根据实际情况灵活修改,用于存储与图表管理相关的配置信息
const config = {// API 请求的基础 URL,用于获取图表数据和建立 SSE 连接API_BASE_URL: 'http://localhost:3000',// 最大重试次数,当 SSE 连接失败时会进行重试,达到该次数后停止重试MAX_RETRIES: 5,// 重试延迟时间(毫秒),每次重试之间的间隔时长RETRY_DELAY: 3000
};/*** ChartManager 类,负责管理 ECharts 图表的初始化、数据获取、SSE 连接以及错误处理等功能*/
class ChartManager {/*** 构造函数,用于初始化图表管理器的基本属性* @param {string} chartId - 图表容器的 ID,用于查找对应的 DOM 元素* @param {string} type - 图表类型,例如 'order' 或 'user',决定获取数据的接口路径* @param {string} token - 用于身份验证的令牌,在请求数据时使用* @param {Object} [option={}] - 可选的 ECharts 配置选项,用于自定义图表样式*/constructor(chartId, type, token, option = {}) {this.chartId = chartId;this.type = type;this.token = token;this.chart = null;this.initialDataFetched = false;this.sseSource = null;this.retryCount = 0;this.maxRetries = config.MAX_RETRIES;this.retryDelay = config.RETRY_DELAY;this.option = {title: {text: `月度${type === 'order' ? '订单' : '用户'}统计`,textStyle: { color: '#333' }},tooltip: {trigger: 'axis',axisPointer: { type: 'shadow' }},grid: {left: '3%',right: '4%',bottom: '3%',containLabel: true},xAxis: {type: 'category',data: [],axisTick: { alignWithLabel: true }},yAxis: {type: 'value'},series: [{name: '数量',type: 'bar',barWidth: '60%',data: []}],...option};}/*** 初始化图表,包含创建 ECharts 实例、获取初始数据以及建立 SSE 连接等操作*/async init() {try {const chartDom = document.getElementById(this.chartId);if (!chartDom) {throw new Error(`未找到指定 ID 的图表容器:${this.chartId}`);}this.chart = echarts.init(chartDom);this.chart.setOption(this.option);await this.fetchInitialData();this.connectSSE();} catch (error) {console.error(`图表 ${this.chartId} 初始化失败:`, error);this.showError('图表初始化失败,请尝试刷新页面重试', error.message);}}/*** 异步获取初始数据*/async fetchInitialData() {try {const url = `${config.API_BASE_URL}/admin/charts/${this.type}?token=${this.token}`;const response = await this.fetchData(url);const { data } = response;if (data.data.months.length === 0) {this.showError('当前暂无数据,10 秒后将尝试重新获取');setTimeout(() => this.fetchInitialData(), 10000);return;}if (data.data.months.length!== data.data.values.length) {this.showError('数据格式存在错误:月份和数量数组长度不匹配');return;}this.option.xAxis.data = data.data.months;this.option.series[0].data = data.data.values;this.initialDataFetched = true;this.chart.setOption(this.option);} catch (error) {this.showError('数据加载失败,5 秒后将尝试重新加载', error.message);setTimeout(() => this.fetchInitialData(), 5000);}}/*** 建立 SSE 连接,实时监听服务器发送的数据*/connectSSE() {if (this.sseSource &&!this.sseSource.closed) {this.sseSource.close();}const url = `${config.API_BASE_URL}/admin/charts/stream/${this.type}?token=${this.token}`;this.sseSource = new EventSource(url);this.sseSource.onmessage = (event) => {try {const responseData = JSON.parse(event.data);const data = responseData.data || responseData;if (!data ||!Array.isArray(data.months) ||!Array.isArray(data.values)) {this.showError('SSE 返回的数据格式有误,请检查后端接口');return;}if (data.months.length === 0) {this.showError('暂无数据,请创建订单后再进行查看');return;}if (data.months.length!== data.values.length) {this.showError('SSE 数据格式错误:月份和数量数组长度不匹配');return;}this.option.xAxis.data = data.months;this.option.series[0].data = data.values;this.chart.setOption(this.option);} catch (parseError) {console.error('解析 SSE 数据时出现错误:', parseError);this.showError('实时数据解析失败,请检查网络连接或尝试刷新页面', parseError.message);}};this.sseSource.onerror = (error) => {console.error('SSE 连接出现错误:', error);this.showError('实时数据连接中断,正在尝试重新连接...', error.message);this.handleSseError();};}/*** 处理 SSE 连接错误,执行重试操作*/handleSseError() {this.retryCount++;if (this.retryCount <= this.maxRetries) {setTimeout(() => this.connectSSE(), this.retryDelay);} else {this.showError('重连失败,请手动刷新页面');this.retryCount = 0;}}/*** 发起网络请求获取数据* @param {string} url - 请求的 URL* @returns {Promise<Object>} - 返回解析后的 JSON 数据*/async fetchData(url) {try {const response = await fetch(url);if (!response.ok) {throw new Error(`HTTP 请求失败,状态码:${response.status}`);}const contentType = response.headers.get('content-type');if (!contentType ||!contentType.includes('application/json')) {throw new Error('响应数据并非 JSON 格式');}return await response.json();} catch (error) {this.showError('网络请求出现问题', error.message);throw error;}}/*** 显示错误信息并更新图表标题* @param {string} message - 错误消息内容* @param {string} [errorCode=null] - 可选的错误代码*/showError(message, errorCode = null) {const errorDiv = document.createElement('div');errorDiv.className = 'error-message';errorDiv.innerHTML = `${message}${errorCode? `<small>错误码: ${errorCode}</small>` : ''}<button onclick="this.parentElement.remove()">关闭</button>`;document.body.prepend(errorDiv);this.option.series[0].data = [];this.chart.setOption(this.option);}/*** 销毁图表和 SSE 连接,释放相关资源*/destroy() {if (this.chart) {this.chart.dispose();this.chart = null;}if (this.sseSource &&!this.sseSource.closed) {this.sseSource.close();}}
}// 将 ChartManager 类暴露到全局作用域,方便在其他地方使用
window.ChartManager = ChartManager;
6.2.1、前端demo
实现
面是一个前端示例,展示了如何使用 ChartManager 类创建实时统计图表:
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><!-- 让页面在移动设备上能正确显示 --><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>实时统计图表</title><!-- 引入 ECharts 库,用于创建各种类型的图表 --><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><!-- 引入自定义的图表管理脚本,包含 ChartManager 类等逻辑 --><script src="charts.js"></script><style>body {/* 设置页面整体字体为 Arial 无衬线字体 */font-family: Arial, sans-serif;/* 使用 Flexbox 布局,让内容水平和垂直居中 */display: flex;justify-content: center;align-items: center;/* 让内容垂直排列 */flex-direction: column;/* 页面四周添加 20px 的外边距 */margin: 20px;}.chart-container {/* 设置图表容器的宽度为 800px */width: 800px;/* 设置图表容器的高度为 500px */height: 500px;/* 图表容器四周添加 20px 的外边距 */margin: 20px;/* 图表容器添加 1px 宽的浅灰色边框 */border: 1px solid #eee;/* 图表容器四个角设置 4px 的圆角 */border-radius: 4px;}.error-message {/* 错误消息文本颜色设置为红色 */color: red;/* 错误消息四周添加 20px 的外边距 */margin: 20px;/* 错误消息内容四周添加 10px 的内边距 */padding: 10px;/* 错误消息背景颜色设置为浅红色 */background-color: #ffebee;/* 错误消息框四个角设置 4px 的圆角 */border-radius: 4px;}</style>
</head><body>
<!-- 订单统计图表的容器,后续 ECharts 图表会渲染到这个容器中 -->
<div class="chart-container" id="orderChart"></div>
<!-- 用户统计图表的容器,后续 ECharts 图表会渲染到这个容器中 -->
<div class="chart-container" id="userChart"></div>
<script>// 当文档的 DOM 内容加载完成后执行以下逻辑document.addEventListener('DOMContentLoaded', async () => {try {// 这里的 token 用于身份验证,实际使用时需要替换为从后端获取的真实 tokenconst token = '';// 自定义的 ECharts 配置选项,可用于覆盖默认配置const customOption = {// 修改图表标题的文本颜色为蓝色title: {textStyle: { color: 'blue' }}};// 创建订单统计图表的管理实例,传入容器 ID、图表类型、token 和自定义配置const orderChart = new ChartManager('orderChart', 'order', token, customOption);// 创建用户统计图表的管理实例,传入容器 ID、图表类型、token 和自定义配置const userChart = new ChartManager('userChart', 'user', token, customOption);// 使用 Promise.all 并行初始化订单图表和用户图表await Promise.all([orderChart.init(), userChart.init()]);// 监听窗口的 beforeunload 事件,当用户关闭或刷新页面时执行以下逻辑window.addEventListener('beforeunload', () => {// 销毁订单图表实例,释放相关资源orderChart.destroy();// 销毁用户图表实例,释放相关资源userChart.destroy();});} catch (error) {// 如果在初始化过程中出现错误,在控制台输出错误信息console.error('图表初始化失败:', error);// 调用 showGlobalError 函数显示全局错误消息showGlobalError('图表初始化失败,请检查浏览器控制台');}});/*** 显示全局错误消息的函数* @param {string} message - 要显示的错误消息内容*/function showGlobalError(message) {// 创建一个新的 div 元素用于显示错误消息const errorDiv = document.createElement('div');// 为错误消息 div 元素添加 error-message 类名,以便应用相应的样式errorDiv.className = 'error-message';// 设置错误消息 div 元素的文本内容为传入的消息errorDiv.textContent = message;// 将错误消息 div 元素添加到页面 body 元素的最前面document.body.prepend(errorDiv);}
</script>
</body></html>
相关文章:
Node 使用 SSE 结合redis 推送数据(echarts 图表实时更新)
1、实时通信有哪些实现方式? 特性轮询(Polling)WebSocketSSE (Server-Sent Events)通信方向单向(客户端 → 服务端)双向(客户端 ↔ 服务端)单向(服务端 → 客户端)连接方…...
提升 Instagram 账号安全性:防止数据泄露的步骤
提升 Instagram 账号安全性:防止数据泄露的步骤 在这个数字化时代,Instagram 不仅是我们分享生活点滴的平台,也是个人信息交换的场所。随之而来的,是数据泄露的风险。保护好自己的 Instagram 账号,防止个人信息外泄&a…...
实现“XXX一张图“进行环境设施设备可视化管理
实现“电网一张图”、“铁路一张图”、“水库一张图”、“森林一张图”等概念,本质上是将某一领域的空间数据、设施设备、运行状态等信息整合到一个统一的数字化平台上,实现全域可视化、智能化管理和协同运营。这种“一张图”模式依赖于地理信息系统(GIS)、物联网(IoT)、…...
RTDETR融合[CVPR2025]ARConv中的自适应矩阵卷积
RT-DETR使用教程: RT-DETR使用教程 RT-DETR改进汇总贴:RT-DETR更新汇总贴 《Adaptive Rectangular Convolution for Remote Sensing Pansharpening》 一、 模块介绍 论文链接:https://arxiv.org/pdf/2503.00467 代码链接:https:/…...
深度解读DeepSeek部署使用安全(48页PPT)(文末有下载方式)
深度解读DeepSeek:部署、使用与安全 详细资料请看本解读文章的最后内容。 引言 DeepSeek作为一款先进的人工智能模型,其部署、使用与安全性是用户最为关注的三大核心问题。本文将从本地化部署、使用方法与技巧、以及安全性三个方面,对Deep…...
微服务无状态服务设计
微服务无状态服务设计是构建高可用、高扩展性系统的核心方法。 一、核心设计原则 请求独立性 每个请求必须携带完整的上下文信息,服务不依赖本地存储的会话或用户数据。例如用户认证通过JWT传递所有必要信息,而非依赖服务端Session。 状态外置化 将会话…...
Android 高版本 DownloadManager 封装工具类,支持 APK 断点续传与自动安装
主要有以下优点 兼容高版本 Android:适配 Android 10 及以上版本的存储权限和安装权限。断点续传:支持从断点继续下载。下载进度监听:实时获取下载进度并回调。错误处理:处理下载失败、网络异常等情况。自动安装 APK:…...
Apache Hudi 性能测试报告
一、测试背景 数据湖作为一个集中化的数据存储仓库,支持结构化、半结构化以及非结构化等多种数据格式,数据来源包含数据库数据、增量数据、日志数据以及数仓上的存量数据等。数据湖能够将这些不同来源、不同格式的数据集中存储和管理在高性价比的分布式存储系统中,对外提供…...
Flask使用Blueprint注册管理路由
在 Flask 中,可以使用 蓝图(Blueprint) 来组织和注册路由,从而让代码更加模块化和易于维护。以下是完整的使用方法: 1. 创建 Flask 项目结构 建议的项目目录结构如下: my_flask_app/ │── app.py …...
LuaJIT 学习(3)—— ffi.* API 函数
文章目录 GlossaryDeclaring and Accessing External Symbolsffi.cdef(def)ffi.Cclib ffi.load(name [,global])例子:ffi.load 函数的使用 Creating cdata Objectscdata ffi.new(ct [,nelem] [,init...]) cdata ctype([nelem,] [init...])例子:匿名 C…...
[资源分享]-web3/区块链/学习路线/资料/找工作方式/水龙头
记录个人学习web3整理的资料 后续如果有 了解/入坑 打算, 提前收藏一下. 1. 学习路线 登链社区-学习路线图 2. 学习资料 国内成系统的资料比较少,我整理的网盘的,关注私信我,资料互相学习 前言 | 区块链技术指南 学习web3-僵尸小游戏 web3.js文档 ethers.js官方文档 Hardhat文…...
Django-ORM-prefetch_related
Django-ORM-prefetch_related 模型定义N1 查询问题示例 使用 prefetch_related 优化查询处理更复杂的查询示例:预取特定条件的书籍示例:预取多个关联字段 性能比较注意事项总结 通过 Author 和 Books 两个模型来理解 Django 的 prefetch_related 方法。 …...
MySQL 批量插入 vs 逐条插
MySQL 插入数据:批量插入 vs 逐条插入,哪个更快? 在 MySQL 中,插入数据有两种常见方式: 批量插入:一条 SQL 插入多条数据。逐条插入:每次插入一条数据。 这两种方式有什么区别?哪…...
Linux centos 7 grub引导故障恢复
CentOS 7误删GRUB2可以通过以下步骤恢复: 进入救援模式 1. 插入CentOS 7安装光盘,重启系统。在开机时按BIOS设置对应的按键(通常是F2等),将启动顺序调整为CD - ROM优先。 2. 系统从光盘启动后,选择“…...
要在Unreal Engine 5(UE5)中实现角色打击怪物并让怪物做出受击反应,
UE5系列文章目录 文章目录 UE5系列文章目录前言一、实现思路二、最终效果 前言 ue5角色受击没有播放受击动画,主角达到怪物身上没有反应 一、实现思路 要在Unreal Engine 5(UE5)中实现角色打击怪物并让怪物做出受击反应,你需要…...
Navicat for Snowflake 震撼首发,激活数据仓库管理全新动能
近日,Navicat 家族迎来了一位全新成员 — Navicat for Snowflake。Snowflake 是一款基于云架构的现代数据仓库解决方案,以其弹性扩展、高性能和易用性著称。这次首发的Navicat for Snowflake 专为简化 Snowflake 数据库管理任务而精心打造。它凭借其直观…...
【redis】发布订阅
Redis的发布订阅(Pub/Sub)是一种基于消息多播的通信机制,它允许消息的**发布者(Publisher)向特定频道发送消息,而订阅者(Subscriber)**通过订阅频道或模式来接收消息。 其核心特点如…...
高级java每日一道面试题-2025年2月26日-框架篇[Mybatis篇]-Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式 ?
如果有遗漏,评论区告诉我进行补充 面试官: Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式 ? 我回答: 在Java高级面试中讨论MyBatis如何将SQL执行结果封装为目标对象并返回的过程时,我们可以从过程细节和映射形式两个方面来综合解答这个问…...
linux root丢失修改密
在RHEL7下重置密码 第一种方式:光驱进入急救模式 //做之前最好 selinuxdisabled Conntinue 然后chroot /mnt/sysimag 然后编辑/etc/shadow文件 第二种方式: 1:编辑启动菜单按e,找到linux16行,在行尾加入 init/bin/sh,同时在…...
OpenCV中文路径图片读写终极指南(Python实现)
文章目录 OpenCV中文路径图片读写终极指南(Python实现)一、问题深度解析1.1 现象观察1.2 底层原因 二、中文路径读取方案2.1 终极解决方案(推荐)2.2 快速修复 三、中文路径保存方案3.1 通用保存函数3.2 使用示例 四、技术原理详解…...
linux 时间同步(阿里云ntp服务器)
1、安装ntp服务 rootlocalhost ~]# yum -y install ntp 已加载插件:fastestmirror, langpacks Loading mirror speeds from cached hostfile* base: mirrors.nju.edu.cn* centos-sclo-rh: mirrors.nju.edu.cn* centos-sclo-sclo: mirrors.huaweicloud.com* epel: m…...
Go vs Rust vs C++ vs Python vs Java:谁主后端沉浮
一、核心性能对比(基于TechEmpower基准测试) 语言单核QPS延迟(ms)内存消耗适用场景Rust650,0000.1245MB高频交易/区块链C++720,0000.0932MB游戏服务器/实时渲染Go230,0000.45110MB微服务/API网关Java180,0001.2450MB企业ERP/银行系统Python12,0008.5220MBAI接口/快速原型技术…...
5 分钟搭建 Prometheus + Grafana 监控
一.安装 Prometheus cd /usr/local/ wget https://github.com/prometheus/prometheus/releases/download/v2.38.0/prometheus-2.38.0.linux-amd64.tar.gz tar xvf prometheus-2.38.0.linux-amd64.tar.gz ln -s prometheus-2.38.0.linux-amd64 prometheus二.安装 node_exporter…...
【机器人-基础知识】标定 - 相机内参求解原理(单应性矩阵、内参约束方程)
1. 求解目标:内参 从世界坐标系到像素坐标系的齐次坐标形式: s [ u v 1 ] K [ R t ] [ X w Y w Z w 1 ] s \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} K \, [\, R \quad t \,] \begin{bmatrix} X_w \\ Y_w \\ Z_w \\ 1 \end{bmatrix} s uv1 K…...
【QT】-一文说清楚QT定时器怎么用
在 Qt 中,定时器(QTimer)是用来定时执行某些任务的非常有用的类。它可以帮助你在指定的时间间隔后重复执行某个函数。常见的用法是启动一个定时器,每过一段时间自动执行某个操作,比如更新 UI、检查状态或发送数据等。 …...
QT编程之JSON处理
一、核心类库及功能 Qt 提供了一套完整的 JSON 处理类库(位于 QtCore 模块),支持解析和生成 JSON 数据: QJsonDocument:表示完整的 JSON 文档,支持从 QJsonObject 或 QJsonArray 初始化。QJsonOb…...
优选算法系列(1.双指针_下)
目录 五. 有效三角形的个数(medium) 题目链接:有效三角形的个数 解法: 代码: 六:和为 s 的两个数字(easy) 题目链接:和为 s 的两个数字 解法: 代码; 七…...
江科大51单片机笔记【15】直流电机驱动(PWM)
写在前言 此为博主自学江科大51单片机(B站)的笔记,方便后续重温知识 在后面的章节中,为了防止篇幅过长和易于查找,我把一个小节分成两部分来发,上章节主要是关于本节课的硬件介绍、电路图、原理图等理论…...
MS51FB9AE单片机解密实践与解析
MS51FB9AE 单片机解密实践与解析 在电子技术领域,MS51FB9AE 作为一款基于 8051 内核的单片机,凭借其强大功能与良好性能,在家用电器、智能仪表等诸多领域广泛应用,深受市场青睐。然而,厂家所采用的独特加密技术&#x…...
java 手搓一个http工具类请求传body
import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets;public class HttpUtil {/*** JSON请求发起*/public static String httpJsonRequest(String requestUrl, String requestJson) {String responseJson &…...
从零搭建微服务项目Pro(第2-2章——JSR303自定义文件校验+整合至微服务公共模块)
前言: JSR 303,即 Bean Validation,是 Java EE 6 中的一项子规范,旨在为 Java Bean 提供一种标准化的数据验证机制。它通过注解的方式,允许开发者在 Java 类的字段或方法上直接定义验证规则,从而将验证逻辑…...
Python 实现大文件的高并发下载
项目背景 基于一个 scrapy-redis 搭建的分布式系统,所有item都通过重写 pipeline 存储到 redis 的 list 中。这里我通过代码演示如何基于线程池 协程实现对 item 的中文件下载。 Item 结构 目的是为了下载 item 中 attachments 保存的附件内容。 {"crawl_tim…...
【论文笔记】Contrastive Learning for Compact Single Image Dehazing(AECR-Net)
文章目录 问题创新网络主要贡献Autoencoder-like Dehazing NetworkAdaptive Mixup for Feature PreservingDynamic Feature Enhancement1. 可变形卷积的使用2. 扩展感受野3. 减少网格伪影4. 融合空间结构信息 Contrastive Regularization1. 核心思想2. 正样本对和负样本对的构建…...
Java vs Go:SaaS 系统架构选型解析与最佳实践
在构建 SaaS(Software as a Service)系统时,选用合适的技术栈至关重要。Java 和 Go 是当今最受欢迎的后端开发语言之一,各自有其优势和适用场景。那么,SaaS 系统开发应该选择 Java 还是 Go?本文将从多个维度…...
OpenEuler-22.03-LTS上利用Ansible轻松部署MySQL 5.7
一、需求 使用ansible自动化部署mysql二进制部署mysql部署mysql并创建JDBC用户 二、环境信息 本文涉及的代码,配置文件地址: 链接:百度网盘 请输入提取码 提取码:1g6y 软件名称版本备注Ansible2.9.27All modules — Ansible Doc…...
vscode编译器的一些使用问题
目录 解决pip不可用问题 检查VSCode的终端配置 解决pip不可用问题 eg: C:\Users\student>pip pip 不是内部或外部命令,也不是可运行的程序或批处理文件。 先找到系统环境变量 高级->环境变量 系统属性->Path 变量名随意,自己后续知道…...
解决Windows版Redis无法远程连接的问题
🌟 解决Windows版Redis无法远程连接的问题 在Windows系统下使用Redis时,很多用户会遇到无法远程连接的问题。尤其是在配置了Redis并尝试通过工具如RedisDesktopManager连接时,可能会报错“Cannot connect to ‘redisconnection’”。今天&am…...
MFC中使用Create或CreateDialog创建对话框失败,GetLastError错误码为1813(找不到映像文件中指定的资源类型)
文章目录 创建对话框失败示例、原因分析及解决方案示例代码错误原因解决方案 AFX_MANAGE_STATE(AfxGetStaticModuleState())作用一、功能1. 模块状态切换2. 自动状态恢复 二、为什么要用该函数?三、必须使用该宏的典型场景1. MFC 扩展 DLL(Extension DLL…...
std::invoke详解
基础介绍 c17版本引入了std::invoke特性,这是一个通用的调用包装器,可以统一调用: 普通函数成员函数函数对象Lambda表达式指向成员的指针 它的主要作用是提供一个统一的方式来调用各种可调用对象。 std::invoke依赖的头文件:#…...
【Rust】枚举和模式匹配——Rust语言基础14
文章目录 1. 枚举类型1.2. Option 枚举 2. match 控制流结构2.1. match 对绑定值的匹配2.2. Option<T> 的匹配2.3. 通配模式以及 _ 占位符 3. if let 控制流4. 小测试 1. 枚举类型 枚举(enumerations),也被称作 enums。枚举允许你通过…...
视频理解之Actionclip(论文宏观解读)
配合解读代码解读 1.研究背景 1. 视频行为识别的重要性 视频行为识别是视频理解领域的核心任务之一,旨在通过分析视频内容来识别和分类其中的人物行为或活动。这一任务在多个领域具有重要的应用价值,例如智能监控、人机交互、自动驾驶、医疗健康等。随…...
【论文精读】Deformable DETR:用于端到端目标检测可变形 Transformer
论文:DEFORMABLE DETR: DEFORMABLE TRANSFORMERS FOR END-TO-END OBJECT DETECTION 代码:Deformable-DETR 摘要 DETR 最近被提出用于消除目标检测中许多手工设计组件的需求,同时展示了良好的性能。然而,它存在收敛速度慢和特征空…...
Odoo18 Http鉴权+调用后端接口
最近在调研Odoo18,包括它的前后端原理、源码等。发现官方的开发文档并不十分实用,比如标题这种简单的实用需求,竟然浪费了一点时间,特此记录。 官方文档:External API — Odoo 18.0 documentation 前提:首…...
doris:SQL 方言兼容
提示 从 2.1 版本开始,Doris 可以支持多种 SQL 方言,如 Presto、Trino、Hive、PostgreSQL、Spark、Clickhouse 等等。通过这个功能,用户可以直接使用对应的 SQL 方言查询 Doris 中的数据,方便用户将原先的业务平滑的迁移到 Doris…...
Linux红帽:RHCSA认证知识讲解(六)创建、管理和删除本地用戶和组
Linux红帽:RHCSA认证知识讲解(六)创建、管理和删除本地用戶和组 前言一、用户和组概念用户类型对比表格主要组和补充组对比表格: 二、本地用户账户增删改查三、本地组账户 前言 上篇博客我们详细了解了从红帽和 DNF 软件仓库下载…...
【Repos系列】yum install nginx 是怎么从仓库中下载并安装的?
yum install nginx 是 YUM 包管理工具从配置的软件仓库中下载并安装软件包的核心操作。以下是其完整工作流程的详细步骤(结合缓存机制和依赖处理): 1. 隐式元数据同步(若缓存过期) 检查缓存有效性:…...
《JavaScript高级程序设计(第5版)》学习大纲
《JavaScript高级程序设计(第5版)》学习大纲 《JavaScript高级程序设计(第5版)》是JavaScript领域的经典“红宝书”,它从基础语法讲到高级特性,还包含浏览器环境和前端工程化等内容。本书2024年12月出版&a…...
基于微信小程序开发的宠物领养平台——代码解读
项目前端 一、项目的技术架构概况 一句话概括:该项目是基于微信小程序开发的宠物领养平台,采用原生小程序框架进行用户界面的构建,使用 wx.request 进行 API 请求,并通过 getApp() 和本地存储来管理全局状态和用户信息。 一&am…...
KICK第五课:Mac 系统下安装 Xcode 或 Clang
Mac 系统下安装 Xcode 或 Clang 详细指南 一、安装前的准备 确认系统版本 macOS 10.9 及以上版本支持 Xcode 和 Clang。若版本过低,需先升级系统。 了解工具区别 Xcode:苹果官方 IDE,包含完整开发环境、模拟器、调试工具等,适合…...
PHP语法基础
PHP语法基础 一,变量 在PHP中,变量是存储数据的容器,其灵活性和动态类型系统是PHP的核心特性之一。以下是PHP变量的详细解析,涵盖声明、作用域、类型转换及最佳实践: 1. 变量基础 声明与命名规则 无需显式声明类型&…...