【Node.js 】在Windows 下搭建适配 DPlayer 的轻量(简陋)级弹幕后端服务
一、引言
DPlayer官网:DPlayer
官方弹幕后端服务:DPlayer-node
MoePlayer/DPlayer-node:使用 Docker for DPlayer Node.js 后端(https://github.com/DIYgod/DPlayer)
本来想直接使用官网提供的DPlayer-node直接搭建的,折腾了一天,在Windows上又是装Docker,又是Redis,又是Mongodb,可能是我的环境或者Windows的原因,启动就报错,遂自己做一个简单的弹幕后端服务。
当然,为了让DPlayer能够成功接收自己做的接口,还要看一下DPlayer 的官方文档与DPlayer-node的源码,返回DPplayer能够接受的接口数据。
二、环境
系统:Windowsjs
数据库:Mysql
后端:Node.js
IDE:Vscode
接口测试:postman(可选)
三、核心逻辑
1.视频开始前查询弹幕
- 客户端请求:当用户打开一个视频时,前端(如 DPlayer)会向弹幕服务器发送一个请求,获取与该视频相关的所有弹幕信息。请求中通常会包含视频的唯一标识(如 videoId)。
- 服务器查询:弹幕服务器接收到请求后,会根据视频 ID 从数据库中查询所有相关的弹幕数据。查询结果包括弹幕的文本内容和出现时间(time)等信息。
- 返回弹幕数据:服务器将查询到的弹幕数据返回给客户端,客户端接收到数据后将其存储在内存中。
2.视频播放过程中的弹幕显示
- DPlayer 处理弹幕:在视频播放过程中,DPlayer 会根据每个弹幕的出现时间(time)来控制弹幕的显示。当视频播放时间达到某个弹幕的 time 值时,DPlayer 会在屏幕上显示该弹幕。
- 弹幕的实时显示:DPlayer 会持续监测视频的播放时间,并在合适的时间点显示对应的弹幕,从而实现弹幕的实时滚动效果。
3.添加新弹幕
- 客户端发送请求:当用户发送一条新的弹幕时,前端会向弹幕服务器发送一个 POST 请求,请求体中包含弹幕的文本内容、出现时间、颜色、类型等信息。
- 服务器处理请求:弹幕服务器接收到请求后,会将新的弹幕信息存储到数据库中。
- 更新弹幕数据:新的弹幕数据会被添加到数据库中,以便其他客户端在请求弹幕数据时能够获取到最新的弹幕信息。
4.系统架构图
+-------------------+ +-------------------+ +-------------------+
| | | | | |
| 客户端 (DPlayer) | HTTP | 弹幕服务器 | SQL | 数据库 (MySQL) |
| | <-------> | | <-------> | |
| - 视频播放 | | - 处理请求 | | - 存储弹幕数据 |
| - 显示弹幕 | | - 返回弹幕数据 | | - 查询弹幕数据 |
| | | | | |
+-------------------+ +-------------------+ +-------------------+
四、数据库&接口分析
1.网络请求分析
直接在Vscode新建一个HTML文件,引入DPlayer:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>视频弹幕演示</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.css">
</head><body><div id="dplayer"></div><script src="https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.js"></script><script>const videoId = 'test'; // ❗ 替换为实际视频 ID(如 1028,需与后端一致)const dp = new DPlayer({container: document.getElementById('dplayer'),video: {url: 'video/test.mp4', // 视频路径type: 'mp4',name: '演示视频'},danmaku: {id: videoId, // 必须与后端路由中的 :id 一致api: 'http://127.0.0.1:3000/danmaku/', // 后端弹幕服务根路径addition: [// 外挂弹幕//`http://127.0.0.1:3000/danmaku/list/${videoId}` ]}});// 监听 DPlayer 的弹幕发送事件dp.on('danmaku_send', (danmaku) => {console.log('发送请求')console.log('视频ID:' + videoId)});</script>
</body></html>
视频路径改为本地的视频,里面的api和addition就随便写一个接口,不管能不能请求成功,重点是看DPlayer请求的接口;
- 在Vscode中安装Live Server
- 在新建的HTML页面上按快捷键:Alt+L+O,自动打开浏览器,然后按F12打开开发者工具:
- 按F5或点击刷新按钮刷新本页面,筛选Fetch/XHR,这个就是发送的请求
可以看到这个请求是404,没有请求到数据,因为我还没建这个网络请求
点击会显示请求信息:
http://127.0.0.1:3000/danmaku/v3/?id=test
我写的请求明明是:“api: 'http://127.0.0.1:3000/danmaku/'”;
但浏览器发出的请求是在我写的基础上增加了“v3/?id=test”,说明这个接口被DPlayer.min.js修改了,所以我需要专门提供这个接口;
- 增加弹幕
随便发送一个弹幕,浏览器会捕获到发送弹幕的请求
http://127.0.0.1:3000/danmaku/v3/
和第一个接口相比没有了参数?id,所以我要为这个接口提供get还有post请求,get请求根据id返回数据,post请求插入数据。
- 切换到负载
这里可以看到请求所需的数据:
{"id": "test","author": "DIYgod","time": 21.778242,"text": "dsfsd","color": 16777215,"type": 0
}
这就是数据表所需的数据。
2.源码分析
根据官方提供的后端服务推测所需的数据表结构和接口
DPlayer-node/routes at master · MoePlayer/DPlayer-node
查看根目录下的路由文件:
DPlayer-node/router.js
const Router = require('koa-router');
const router = new Router();router.get('/v3', require('./routes/get'));
router.post('/v3', require('./routes/post'));
router.get('/v3/bilibili', require('./routes/bilibili'));module.exports = router;
该服务一共接收三个请求:post、get、bilibili,关联至DPlayer-node/routes/目录下的三个js文件:
bilibili.js | set cache time | 7 years ago |
get.js | compatible with empty author | 7 years ago |
post.js | fix redis del | 7 years ago |
bilibili.js顾名思义是B站的弹幕接口,不知现在还有没有用,这里也不需要用到B站的弹幕,故跳过;
主要重点是负责get/post请求的这两个js文件,觉得麻烦直接问AI一步到位。
post.js
const logger = require('../utils/logger');module.exports = async (ctx) => {const body = ctx.request.body;const dan = new ctx.mongodb({player: body.id,author: body.author,time: body.time,text: body.text,color: body.color,type: body.type,ip: ctx.ips[0] || ctx.ip,referer: ctx.headers.referer,date: +new Date(),});try {const data = await dan.save();ctx.body = JSON.stringify({code: 0,data,});ctx.redis.del(`danmaku${data.player}`);}catch (err) {logger.error(err);ctx.body = JSON.stringify({code: 1,msg: `Database error: ${err}`,});}
};
-
接收前端发送的弹幕数据。
-
将弹幕数据保存到数据库中。
-
如果保存成功,删除对应的 Redis 缓存并返回成功响应。
-
如果保存失败,记录错误日志并返回错误响应。
get.js
function htmlEncode (str) {return str ? str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/') : '';
}module.exports = async (ctx) => {const { id, limit } = ctx.request.query;let data = await ctx.redis.get(`danmaku${id}`);if (data) {data = JSON.parse(data);if (limit) {data = data.slice(-1 * parseInt(limit));}ctx.response.set('X-Koa-Redis', 'true');} else {data = await ctx.mongodb.find({ player: id }) || [];ctx.redis.set(`danmaku${id}`, JSON.stringify(data));if (limit) {data = data.slice(-1 * parseInt(limit));}ctx.response.set('X-Koa-Mongodb', 'true');}ctx.body = JSON.stringify({code: 0,data: data.map((item) => [item.time || 0, item.type || 0, item.color || 16777215, htmlEncode(item.author) || 'DPlayer', htmlEncode(item.text) || '']),});
};
-
接收前端发送的视频 ID 和弹幕数量限制参数。
-
尝试从 Redis 缓存中获取弹幕数据,如果缓存存在则直接返回。
-
如果缓存不存在,从 MongoDB 数据库中查询弹幕数据,并将结果存入 Redis 缓存。
-
根据
limit
参数对数据进行截取,只返回最近的弹幕数据。 -
对弹幕的作者和文本内容进行 HTML 编码,防止 XSS 攻击。
-
返回格式化后的弹幕数据给客户端。
五、分析结果
1.数据库表结构
表:danmaku(弹幕表)
字段名 | 类型 | 描述 |
---|---|---|
id | INT PRIMARY KEY | 弹幕唯一标识 |
player | VARCHAR(255) | 关联的视频唯一标识 |
author | VARCHAR(255) | 弹幕作者 |
time | DECIMAL(10, 2) | 弹幕出现的时间(秒) |
text | TEXT | 弹幕文本内容 |
color | VARCHAR(7) | 弹幕颜色(十六进制) |
type | INT | 弹幕类型(如滚动、顶部、底部) |
ip | VARCHAR(45) | 发送弹幕的用户IP地址 |
referer | TEXT | 请求来源页面 |
date | TIMESTAMP | 弹幕发送的时间戳 |
2.接口格式
2.1获取弹幕
-
接口地址:
GET /v3
-
请求参数:
-
videoId
:视频唯一标识 -
limit
:限制返回的弹幕数量(可选)
-
-
响应格式:
{"code": 0,"data": [[10.5, // 弹幕出现的时间(秒)1, // 弹幕类型16777215, // 弹幕颜色(十六进制转十进制)"DPlayer", // 弹幕作者(HTML编码)"这是一条测试弹幕" // 弹幕文本内容(HTML编码)]] }
2.2添加弹幕
-
接口地址:
POST /v3
-
请求体:
{"id": "test", // 视频唯一标识"author": "user1", // 弹幕作者"time": 20.0, // 弹幕出现的时间(秒)"text": "这是一条新弹幕", // 弹幕文本内容"color": "#FFFFFF", // 弹幕颜色(十六进制)"type": 1 // 弹幕类型 }
-
响应格式:
{"code": 0,"data": {"id": 2,"player": "test","author": "user1","time": 20.0,"text": "这是一条新弹幕","color": "#FFFFFF","type": 1,"ip": "192.168.1.1","referer": "http://example.com","date": 1714234800000} }
六、创建数据表
-- 创建数据库
CREATE DATABASE danmaku_db;-- 使用创建的数据库
USE danmaku_db;-- 创建弹幕表
CREATE TABLE danmaku (id INT PRIMARY KEY AUTO_INCREMENT,player VARCHAR(255) NOT NULL,author VARCHAR(255) NOT NULL,time DECIMAL(10, 2) NOT NULL,text TEXT NOT NULL,color VARCHAR(20) NOT NULL,type INT NOT NULL,ip VARCHAR(45) NOT NULL,referer TEXT,date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
七、编写接口
1. 环境验证
以下需要Node环境,cmd输入以下命令检查是否含有所需环境:
node -v
npm -v
返回版本号证明安装成功。
2. 创建项目
mkdir danmaku-server
cd danmaku-server
npm init -y
3. 安装依赖
npm install express mysql2 body-parser morgan cors
4. 数据库连接文件
在项目根目录下新建文件夹models,在models中新建danmaku.js文件
const mysql = require('mysql2/promise');const pool = mysql.createPool({host: 'localhost',user: 'root',password: 'your_password',database: 'danmaku_db',waitForConnections: true,connectionLimit: 10,queueLimit: 0
});module.exports = pool;
5. 获取弹幕接口
在项目根目录下新建文件夹routes,在routes中新建get.js文件
const express = require('express');
const router = express.Router(); // 使用 express.Router() 创建路由实例
const pool = require('../models/danmaku');// 处理 GET 请求
router.get('/', async (req, res) => {console.log('GET请求');const { videoId, limit } = req.query; // 从查询参数获取 videoId 和 limit// 参数验证if (!videoId) {return res.status(400).json({ code: 1, msg: 'videoId 参数是必需的' });}try {const [rows] = await pool.query('SELECT time, type, color, author, text FROM danmaku WHERE player = ? ORDER BY time',[videoId]);let data = rows.map(item => [parseFloat(item.time),parseInt(item.type),parseInt(item.color.replace('#', ''), 16),item.author,item.text]);if (limit) {const parsedLimit = parseInt(limit);if (isNaN(parsedLimit) || parsedLimit <= 0) {return res.status(400).json({ code: 1, msg: 'limit 参数必须是正整数' });}data = data.slice(-parsedLimit);}res.json({code: 0,data});} catch (error) {console.error('获取弹幕失败:', error);res.status(500).json({ code: 1, msg: '获取弹幕失败' });}
});module.exports = router;
6. 添加弹幕接口
在routes中新建post.js文件
const express = require('express');
const router = express.Router();
const pool = require('../models/danmaku');
const bodyParser = require('body-parser');router.use(bodyParser.json());router.post('/', async (req, res) => {console.log('POST请求');const { id, author, time, text, color, type } = req.body;const ip = req.ip;const referer = req.headers.referer || '';try {const [result] = await pool.query('INSERT INTO danmaku (player, author, time, text, color, type, ip, referer) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',[id, author, time, text, color, type, ip, referer]);res.json({code: 0,data: {id: result.insertId,player: id,author,time,text,color,type,ip,referer,date: new Date().getTime()}});} catch (error) {console.error('添加弹幕失败:', error);res.status(500).json({ code: 1, msg: '添加弹幕失败' });}
});module.exports = router;
7. 主应用文件
在项目根目录下创建app.js文件
const express = require('express');
const app = express();
const getRouter = require('./routes/get');
const postRouter = require('./routes/post');
const morgan = require('morgan');
const cors = require('cors'); // 引入 cors 中间件// 使用 cors 中间件处理跨域请求
app.use(cors());// 使用 morgan 作为日志中间件,记录更详细的请求信息
app.use(morgan('combined'));app.use(express.json());// 使用 /v3 作为基础路径
app.use('/v3', getRouter);
app.use('/v3', postRouter);// 全局错误处理中间件
app.use((err, req, res, next) => {console.error('发生未捕获的异常:', err);res.status(500).json({ code: 1, msg: '服务器内部错误' });
});const PORT = 3000;
app.listen(PORT, (err) => {if (err) {console.error(`无法启动服务器:`, err);} else {console.log(`弹幕服务器运行在 http://localhost:${PORT}`);}
});
8. 目录结构
9. 运行服务器
在终端内运行命令:
node app.js
出现以下界面说明运行成功
八、接口测试
1. get测试
在浏览器中输入链接:http://localhost:3000/v3?videoId=test&limit=10
出现以上界面说明请求接口成功,获取弹幕失败为数据库问题,把数据库配置改成自己本地的就可以了:
const mysql = require('mysql2/promise');const pool = mysql.createPool({host: '127.0.0.1', //使用localhost可能会连接失败user: 'root',password: '123456',database: 'danmaku_db',waitForConnections: true,connectionLimit: 10,queueLimit: 0
});module.exports = pool;
最终的效果应是这样的:
2.post测试
post测试就不能直接使用浏览器,这里需要用到接口测试软件postman,如果没有就要自己在HTML里面写一个接口请求,比较麻烦,后面会直接写一个DPlayer的引用测试。
打开 Postman:
-
在 Postman 中,选择
POST
方法。 -
在请求 URL 输入框中输入
http://localhost:3000/v3
。
设置请求头:
-
点击
Headers
标签。 -
添加一个请求头,
Content-Type
设置为application/json
。
设置请求体:
-
点击
Body
标签。 -
选择
raw
单选按钮,并从下拉列表中选择JSON
。 -
在文本框中输入以下 JSON 数据:
{"id": "test","author": "user1","time": 20.0,"text": "这是一条新弹幕","color": "#FFFFFF","type": 1 }
发送请求:
-
点击
Send
按钮发送请求。 -
检查响应,确保服务器返回了正确的结果。
检查数据库是否成功插入了数据
九、在DPlayer中使用
1.新建HTML文件
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>视频弹幕演示</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.css">
</head><body><div id="dplayer"></div><script src="https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.js"></script><script>const videoId = 'test'; // ❗ 替换为实际视频 ID(如 1028,需与后端一致)const dp = new DPlayer({container: document.getElementById('dplayer'),video: {url: 'video/test.mp4', // 视频路径type: 'mp4',name: '演示视频'},danmaku: {id: videoId, // 必须与后端路由中的 :id 一致api: 'http://127.0.0.1:3000/', // 后端弹幕服务根路径addition: [// 外挂弹幕//`http://127.0.0.1:3000/` ]}});// 监听 DPlayer 的弹幕发送事件dp.on('danmaku_send', (danmaku) => {console.log('发送请求')console.log('视频ID:' + videoId)});</script>
</body></html>
将视频路径“video/test.mp4”换成你自己项目视频所在的路径,比如我的项目结构如下:
2. 弹幕加载
直接在新建的HTML页面上按快捷键:“Alt+L+O”或右键菜单Open with Live Server,没有该选项需要先安装插件“Live Server”,将自动打开默认浏览器。
打开开发者工具,切换到网络页面,刷新当前网页,网络请求没有变红,并且返回相应数据说明弹幕查询成功;
出现相应的弹幕说明DPlayer解析我们的接口数据成功。
3.发送弹幕测试
- 该请求没有变红,并且返回了相应的数据;
- 数据库也增加了这条数据;
- 视频上也有相应的弹幕;
以上情况都出现了,说明弹幕发送成功。
十、服务器日志
如果其中某个操作没有成功,就需要查看服务器的日志,重复没有成功操作,查看终端是否输出错误信息,根据错误信息定位错误。
比如,我执行发送弹幕的操作没有成功,查看日志输出为:“Data too long for column 'color' at row 1”,插入数据库的长度太长,需要将color字段的长度从varchar(10)改成varchar(20)。
十一、总结
一个非常简陋的Node.js后端服务项目,基本都是用别人写好的东西,感觉还是挺适合初次接触Node.js的同学的。
涉及的技术也很浅:
1.前端:HTML、Javascript;
2.后端:Node.js;
3.接口测试:postman ;
相关文章:
【Node.js 】在Windows 下搭建适配 DPlayer 的轻量(简陋)级弹幕后端服务
一、引言 DPlayer官网:DPlayer 官方弹幕后端服务:DPlayer-node MoePlayer/DPlayer-node:使用 Docker for DPlayer Node.js 后端(https://github.com/DIYgod/DPlayer) 本来想直接使用官网提供的DPlayer-node直接搭建…...
淘宝tb.cn短链接生成
淘宝短链接简介 1. 一键在线生成淘宝短链接tb.cn,m.tb.cn等 2. 支持淘宝优惠券短链接等淘宝系的所有网址 3. 生成的淘宝短链接是官方的,安全稳定有保证 4.适合多种场景下使用,如:网站推广,短信推广 量大提供api接口࿰…...
在web应用后端接入内容审核——以腾讯云音频审核为例(Go语言示例)
腾讯云对象存储数据万象(Cloud Infinite,CI)为用户提供图片、视频、语音、文本等文件的内容安全智能审核服务,帮助用户有效识别涉黄、违法违规和广告审核,规避运营风险。本文以音频审核为例给出go语言示例代码与相应结…...
优化无头浏览器流量:使用Puppeteer进行高效数据抓取的成本降低策略
概述 使用 Puppeteer 进行数据抓取时,流量消耗是一个重要考虑因素。特别是在使用代理服务时,流量成本可能显著增加。为了优化流量使用,我们可以采用以下策略: 资源拦截:通过拦截不必要的资源请求来减少流量消耗。请求…...
【C语言】fprintf与perror对比,两种报错提示的方法
它们的主要区别在于 信息来源 和 自动包含的系统错误详情。 1. fprintf(stderr, "自定义错误信息\n"); 功能: 这是标准库中的一个通用格式化输出函数。你可以用它向任何文件流(包括 stdout 标准输出, stderr 标准错误, 或任何用 fopen 打开的文件&#x…...
C语言复习笔记--内存函数
在复习完字符函数和字符串函数之后,今天让我们复习一下内存函数吧.这一块的东西不太多,并且与之前的字符串函数有一些地方很相似,所以这里应该会比较轻松. memcpy使用和模拟实现 老规矩,先看函数原型 void * memcpy ( void * destination, const void * source, size_t num );…...
前端面试高频算法
前端面试高频算法 1 排序算法;1.1 如何分析一个排序算法1.1.1 执行效率3.1.2 内存消耗1.1.3 稳定性 1.2 冒泡排序(Bubble Sort)1.3 插入排序(Insertion Sort)1.4 选择排序(Selection Sort)1.5 归…...
云原生--核心组件-容器篇-4-认识Dockerfile文件(镜像创建的基础文件和指令介绍)
1、Dockerfile的定义与作用 定义: Dockerfile是一个文本文件,包含一系列Docker指令,用于自动化构建Docker镜像。Docker 在构建镜像时会按照Dockerfile中的指令逐步执行,每一行指令都会生成一个新的镜像层(layer&#x…...
13.组合模式:思考与解读
原文地址:组合模式:思考与解读 更多内容请关注:7.深入思考与解读设计模式 引言 在软件开发中,是否曾经遇到过这样一种情况:你有一个对象,它本身很简单,但是它包含了其他类似的对象。随着系统变得越来越复…...
Pycharm(十七)生成器
一、生成器介绍 1.1 概述 生成器指的是Generator对象,它不再像以往一样,一次性生成所有的数据,而是用一个,再生成一个,基于用户写的规则(条件)来生成数据,如果条件不成立ÿ…...
盛元广通实验材料管理系统-实验室管理系统-LIMS
一、引言 在当下科学研究及各类实验日益频繁的背景下,实验材料管理成为实验室高效运作的核心环节。从“人工低效”到“智能自动化”,盛元广通可覆盖实验材料的采购、存储、使用、追踪等全流程,从功能适配性、技术性能、成本效益、供应商服务…...
检查 NetCDF Fortran的版本
执行 nf-config --all命令后,它会输出一堆信息,大致像这样: This netCDF-Fortran version: 4.6.0 netCDF-Fortran installation dir: /usr/local/netcdf4 Fortran compiler: gfortran Fortran compiler flags: -g -O2 Fortran preprocesso…...
MySQL 存储引擎与服务体系深度解析
一、存储引擎核心概念 基本定义 存储引擎:MySQL服务的核心组件,负责数据的存储、检索和管理版本演进: MySQL 5.0/5.1 默认使用MyISAM引擎MySQL 5.5/5.6+ 默认采用InnoDB引擎关键特性 不同存储引擎采用不同的数据存储结构和处理机制直接影响表的CRUD操作性能和数据安全特性作…...
乐企数电发票分布式发票号码生成重复的问题修复思路分享
文章目录 1.前言2.解决思路2.1错误姿势2.2歪打正着2.3正确姿势 3.总结 1.前言 由于之前接了乐企数电开票,服务上线之后,使用的公司少没有啥问题,后面切换了两家日开票量大的公司上线之后,就发现发票号码生成重复了,后面…...
多级缓存架构设计与实践经验
多级缓存架构设计与实践经验 在互联网大厂Java求职者的面试中,经常会被问到关于多级缓存的架构设计和实践经验。本文通过一个故事场景来展示这些问题的实际解决方案。 第一轮提问 面试官:马架构,欢迎来到我们公司的面试现场。请问您对多级…...
LCD1602液晶显示屏详解(STM32)
目录 一、介绍 二、传感器原理 1.原理图编辑 2.接口说明 三、程序设计 main文件 lcd1602.h文件 lcd1602.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 LCD1602A字符型液晶显示模块是专门用于显示字母、数字元、符号等的点阵型液晶显示模块。分4位和8位数据…...
Golang | 集合求交
文章目录 bitmap求交集2个有序链表多个有序链表跳表 bitmap求交集 2个有序链表 多个有序链表 为什么非最大的所有都要往后移动呢?因为现在已经知道交集即使有,也最小都是这个目前最大的了,其他不是最大的不可能是交集,所有除了最大…...
手机充电进入“秒充“时代:泡面刚下锅,电量已满格
现代人的生活节奏越来越快,手机充电技术也在飞速发展。从最初的"充电一整晚"到如今的"秒充"时代,充电效率的提升正在悄然改变着我们的生活习惯。最新数据显示,目前最快的手机充电技术仅需4分30秒就能充满一部手机的电量&…...
网站字体文件过大 导致字体从默认变成指定字体的时间过长
1.选择字体中只用到的字符集较小的包 只用到了数字,所以使用了 xx-sans.ttf的版本(86kb) 2.转换ttf格式为woff2 转换后26kb 3.使用字体 // 定义字体 font-face {font-family: "myFont";src: url(/assets/fonts/myFont.woff2) format(woff2);font-weigh…...
WPF常用技巧汇总 - Part 2
WPF常用技巧汇总-CSDN博客 主要用于记录工作中发现的一些问题和常见的解决方法。 目录 WPF常用技巧汇总-CSDN博客 1. DataGrid Tooltip - Multiple 2. DataGrid Tooltip - Cell值和ToolTip值一样 3. DataGrid Tooltip - Cell值和ToolTip值不一样 4. DataGrid - Ctrl A /…...
C++中析构函数
析构函数 析构函数(Destructor)是类的一种特殊成员函数,用于在对象的生命周期结束时执行清理操作,他的主要作用是释放对象占用资源,例如动态分配的内存,文件句柄或网络连接等。 特点 名称与类名称相同 单…...
树莓派超全系列教程文档--(44)如何在树莓派上编译树莓派内核
如何在树莓派上编译树莓派内核 构建内核下载内核源代码 本地构建内核构建配置使用 LOCALVERSION 自定义内核版本构建安装内核 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 构建内核 操作系统预装的默认编译器和链接器被配置为构建在该操作系统…...
flask返回文件的同时返回其他参数
参考:flask实现上传文件与下载文件_flask 文件上传和下载-CSDN博客 在 Flask 中,返回文件的同时附加额外参数(如处理时间)可以通过 自定义 HTTP 响应头 或 返回 JSON 数据与文件结合 的方式实现。以下是具体方法和示例: 方法 1:通过 HTTP 响应头 附加参数(推荐) 将参…...
C++23 std::move_only_function:一种仅可移动的可调用包装器 (P0288R9)
文章目录 一、定义与基本概念1.1 定义1.2 基本概念 二、特点2.1 仅可移动性2.2 支持多种限定符2.3 无target_type和target访问器2.4 强前置条件 三、使用场景3.1 处理不可复制的可调用对象3.2 性能优化3.3 资源管理 四、与其他可调用包装器的对比4.1 与std::function的对比4.2 …...
Zookeeper实现分布式锁实战应用
Zookeeper实现分布式锁实战应用示例 1. 分布式锁概述 在分布式系统中,当多个进程或服务需要互斥地访问共享资源时,就需要分布式锁来协调。Zookeeper因其强一致性和临时节点特性,非常适合实现分布式锁。 2. Zookeeper实现分布式锁的核心原理…...
使用 Playwright 构建高效爬虫:原理、实战与最佳实践
随着网站前端技术日益复杂,传统的基于请求解析(如 requests、BeautifulSoup)的爬虫在处理 JavaScript 渲染的网站时变得力不从心。Playwright,作为微软推出的一款强大的自动化浏览器控制框架,不仅适用于自动化测试,也成为了处理现代网站爬取任务的利器。 本篇文章将带你…...
ComfyUI for Windwos与 Stable Diffusion WebUI 模型共享修复
#工作记录 虽然在安装ComfyUI for Windwos时已经配置过extra_model_paths.yaml 文件,但升级ComfyUI for Windwos到最新版本后发现原先的模型配置失效了,排查后发现,原来是 extra_model_paths.yaml 文件在新版本中被移动到了C盘目录下&#x…...
【RabbitMQ消息队列】详解(一)
初识RabbitMQ RabbitMQ 是一个开源的消息代理软件,也被称为消息队列中间件,它遵循 AMQP(高级消息队列协议),并且支持多种其他消息协议。 核心概念 生产者(Producer):创建消息并将其…...
【MySQL数据库入门到精通-08 约束】
文章目录 4、约束4.1 概述4.2 约束演示1. 根据需求,完成表的创建2. SQL数据库3. 结果 4.3 外键约束4.3.1 介绍1. 根据需求,完成表的创建2. SQL数据库3. 结果4.3.2 外键约束建立1. 语法2. SQL语句3. 现象4.3.3 外键删除更新行为1. 知识点2.SQL3.结果 4、约…...
C++笔记-模板进阶和继承(上)
一.模板进阶 1.1非模板类型参数 那之前学过的stack举例,在这之前我们如果要用N,就要用宏来定义,但是宏毕竟有局限性: 如果我要用到两个stack,一个要求10个空间,另一个要求100空间呢? 这时候…...
云计算赋能质检LIMS的价值 质检LIMS系统在云计算企业的创新应用
在云计算技术高速发展的背景下,实验室信息化管理正经历深刻变革。质检LIMS(实验室信息管理系统)作为实验室数字化转型的核心工具,通过与云计算深度融合,为企业提供了高弹性、高安全性的解决方案。本文将探讨质检LIMS在…...
2025系统架构师---数据抽象(Data Abstraction)与面向对象架构风格
引言 在软件系统复杂度与规模不断攀升的今天,如何设计出可扩展、易维护且能快速响应需求变化的架构,是每一位系统架构师面临的挑战。数据抽象(Data Abstraction)与面向对象架构风格(Object-Oriented Architectu…...
[python] 基于WatchDog库实现文件系统监控
Watchdog库是Python中一个用于监控文件系统变化的第三方库。它能够实时监测文件或目录的创建、修改、删除等操作,并在这些事件发生时触发相应的处理逻辑,因此也被称为文件看门狗。 Watchdog库的官方仓库见:watchdog,Watchdog库的官…...
缺省处理、容错处理
布尔判定 假:false 0 null undefined NaN 可选符.?和?? let obj {name: jim,data: {money: 0,age: 18,fn(a){return a}} }1、如果左侧的值为null或者undefined,则使用右侧值。需要使用"??" obj?.data?.a…...
Taro on Harmony :助力业务高效开发纯血鸿蒙应用
背景 纯血鸿蒙逐渐成为全球第三大操作系统,业界也掀起了适配鸿蒙原生的浪潮,用户迁移趋势明显,京东作为国民应用,为鸿蒙用户提供完整的购物体验至关重要。   去年 9 月,京东 AP…...
Java基础——排序算法
排序算法不管是考试、面试、还是日常开发中都是一个特别高频的点。下面对八种排序算法做简单的介绍。 1. 冒泡排序(Bubble Sort) 原理:相邻元素比较,每一轮将最大元素“冒泡”到末尾。 示例数组:[5, 3, 8, 1, 2] pub…...
【操作系统原理07】输入/输出系统
文章目录 零.大纲一.I/O设备的概念和分类0.大纲1.什么是I/O设备2.I/O分类 二.I/O控制器0.大纲1.I/O设备的电子部件(I/O控制器)2.IO控制器组成3.内存映像I/O VS 寄存器独立编址 三.I/O控制方式0.大纲与总结1.程序直接控制方式(1) 操…...
IM云端搜索全面升级,独家能力拓展更多“社交连接”玩法
在这个数字时代,网络让信息传递前所未有的便捷,但同时,海量数据堆积也让内容检索变得像大海捞针。尤其是在我们日常工作生活中最常用的即时通信软件中,信息的快速查找和精准定位正变得越来越重要。 但传统的本地搜索功能受限于设…...
汽车产业链主表及类别表设计
(提前设计,备用) 一、汽车产业链类别表(industry_chain_category) 设计要点 1、核心字段:定义产业链分类(如零部件、整车制造、销售服务等) 2、主键约束:自增ID作为唯一标…...
有效的字母异位词
recorded:用于统计或抵消字符出现次数。 class Solution { public:bool isAnagram(string s, string t) {int record[26]{0};for(int i0;i<s.size();i){record[s[i]-a];}for(int i0;i<t.size();i){record[t[i]-a]--;}for(int i0;i<26;i){if(record[i]!0){…...
汽车网络安全 -- 理解暴露面、攻击面和攻击向量
1.暴露面是攻击面的子集 举个例子,房子都有门、窗户,这些窗户、门不管是否打开,都可能被小偷利用进入到房内,因此这些门窗可能是潜在的漏洞,所以称之为攻击面(Attack Surface)。 小偷经过长期观察,发现家…...
C++异步利器:全面理解 std::packaged_task
在现代 C(C11及以后)中,并发与异步编程是不可回避的重要技能。我们常常希望把某些计算任务扔给后台线程去处理,同时又能优雅地获取任务结果。 这时候,std::packaged_task 就是一个非常强大的工具。 本文将带你深入理解…...
Animate 中HTMLCanvas 画布下的鼠标事件列表(DOM 鼠标)
在 JavaScript 和 Adobe Animate(CreateJS) 中,常用的鼠标交互事件可分为两大类:基础 DOM 事件 和 CreateJS 扩展事件12。以下是完整分类: 一、基础 DOM 鼠标事件 事件名触发场景冒泡特性click鼠标左键单…...
RagFlow文档切块提升
1.RagFlow切块介绍 2.复现优化 2.1 General 通用分块 def parser_text(self, txt, blockSize512, overlapSize0, delimiter"\n!?;。;!?"):文本分割sentences self.split_text_by_period_qh(txt, delimiter, blockSizeblockSize)…...
音频转base64
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>音频转Base64</title><style>.containe…...
蓝桥杯 11. 打印大X
打印大X 原题目链接 题目描述 小明希望用星号拼凑,打印出一个大 X,他要求能够控制笔画的宽度和整个字的高度。 为了便于比对空格,所有的空白位置都以句点符 . 来代替。 输入描述 输入两个整数 m 和 n,表示笔画的宽度和 X 的高…...
页面需要重加载才能显示的问题修改
1.问题描述:跳转页面后,只有点击重新加载后才会显示内容 经过测试后: / 跳转详情 const goToDetail (bookId) > { router.push({ path: /classic-detail, query: { book_id: bookId } }) } 执行完以上代码后,页面从classics…...
On the Biology of a Large Language Model——Claude团队的模型理解文章【论文阅读笔记】其二——数学计算部分
这篇内容的源博文是 On the Biology of a Large Language Model 这是Anthropic,也就是Claude的团队的一遍技术博客。他的主要内容是用一种改良版的稀疏编码器来解释LLM在inference过程中内部语义特征的激活模式。因为原文太长,我把原文分成了几份来写阅读…...
Python语言基础知识详解:标识符与变量
Python语言基础知识详解:标识符与变量 一、标识符(Identifiers) 定义 标识符是用于命名变量、函数、类、模块或其他对象的名称。它是代码中对实体的唯一标识。 1. 标识符的命名规则 Python的标识符需遵循以下规则: 允许的字符 由…...
google chrome 中 fcitx5 候选框不跟随光标
我的电脑:ubuntu22.04,窗口系统:wayland 2025/4/26 号更新的谷歌浏览器 今天打开浏览器发现输入法的候选框固定在左上角不动了,一番折腾,发现解决办法如下: 在搜索框中输入 about:flags搜索 wayland&#…...