【Node.js 】在Windows 下搭建适配 DPlayer 的轻量(简陋)级弹幕后端服务

news2025/7/16 15:35:27

一、引言

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}`,
            });
        }
    };
    1. 接收前端发送的弹幕数据。

    2. 将弹幕数据保存到数据库中。

    3. 如果保存成功,删除对应的 Redis 缓存并返回成功响应。

    4. 如果保存失败,记录错误日志并返回错误响应。

    get.js

    function htmlEncode (str) {
        return str ? str.replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#x27;')
            .replace(/\//g, '&#x2f;') : '';
    }
    
    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) || '']),
        });
    };
    1. 接收前端发送的视频 ID 和弹幕数量限制参数。

    2. 尝试从 Redis 缓存中获取弹幕数据,如果缓存存在则直接返回。

    3. 如果缓存不存在,从 MongoDB 数据库中查询弹幕数据,并将结果存入 Redis 缓存。

    4. 根据 limit 参数对数据进行截取,只返回最近的弹幕数据。

    5. 对弹幕的作者和文本内容进行 HTML 编码,防止 XSS 攻击。

    6. 返回格式化后的弹幕数据给客户端。

    五、分析结果

    1.数据库表结构

    表:danmaku(弹幕表)

    字段名类型描述
    idINT PRIMARY KEY弹幕唯一标识
    playerVARCHAR(255)关联的视频唯一标识
    authorVARCHAR(255)弹幕作者
    timeDECIMAL(10, 2)弹幕出现的时间(秒)
    textTEXT弹幕文本内容
    colorVARCHAR(7)弹幕颜色(十六进制)
    typeINT弹幕类型(如滚动、顶部、底部)
    ipVARCHAR(45)发送弹幕的用户IP地址
    refererTEXT请求来源页面
    dateTIMESTAMP弹幕发送的时间戳

    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 ;

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2345562.html

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

    相关文章

    OpenSSH配置连接远程服务器MS ODBC驱动与Navicat数据库管理

    OpenSSH配置连接远程服务器MS ODBC驱动与Navicat数据库管理 目录 OpenSSH配置连接远程服务器MS ODBC驱动与Navicat数据库管理 一、MS ODBC驱动 1.1、安装到Windows后的表现形式 1.2、版本的互斥性 1.3、安装程序 1.4、配置后才可用 二、Navicat数据库管理工具 2.1、安…

    操作系统:计算机世界的基石与演进

    一、操作系统的本质与核心功能 操作系统如同计算机系统的"总管家"&#xff0c;在硬件与应用之间架起关键桥梁。从不同视角观察&#xff0c;其核心功能呈现多维价值&#xff1a; 硬件视角的双重使命&#xff1a; 硬件管理者&#xff1a;通过内存管理、进程调度和设…

    Codeium 免费的AI编程助手

    Codeium 由 Exafunction 团队&#xff08;主要也是美国华人&#xff09;开发的一款免费AI编程助手&#xff0c;是一个建立在顶尖AI技术上的代码加速工具&#xff0c;其背后的老板非常厉害&#xff0c;据说投资过马斯克的SpaceX。Codeium 本身具有颇多的亮点&#xff0c;支持70种…

    在MySQL Shell里 重启MySQL 8.4实例

    前一段时间看到MySQL官方视频的Oracle工程师在mysql shell里面重启mysql实例&#xff0c;感觉这个操作很方便&#xff0c;所以来试试&#xff0c;下面为该工程师的操作截图 1.MySQL Shell 通过root用户连上mysql&#xff0c;shutdown mysql实例 [rootmysql8_3 bin]# mysqlshMy…

    FANUC机器人GI与GO位置数据传输设置

    FANUC机器人GI与GO位置数据传输设置&#xff08;整数小数分开发&#xff09; 一、概述 在 Fanuc 机器人应用中&#xff0c;如果 IO 点位足够&#xff0c;可以利用机器人 IO 传输位置数据及偏移位置数据等。 二、操作步骤 1、确认通讯软件安装 首先确认机器人控制柜已经安装…

    LeetCode 24 两两交换链表中的节点

    ​给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&#xff1a;[2,1…

    低代码平台开发手机USB-HID调试助手

    项目介绍 USB-HID调试助手是一种专门用于调试和测试USB-HID设备的软件工具。USB-HID设备是一类通过USB接口与计算机通信的设备&#xff0c;常见的HID设备包括键盘、鼠标、游戏控制器、以及一些专用的工业控制设备等。 主要功能包括&#xff1a; 数据监控&#xff1a;实时监控和…

    亚组风险比分析与可视化

    1. 结果解读 1.1 风险比概述 1.1.1 风险比基本概念 风险比(Hazard Ratio)用于衡量治疗组与对照组事件发生的风险差异。 风险比为1,表示两组风险相同;小于1,治疗组风险低;大于1,治疗组风险高。 1.1.2 性别亚组分析 A性风险比小于1,表明治疗对A性有积极效果,风险降低。…

    【博客系统】博客系统第一弹:博客系统项目配置、MyBatis-Plus 实现 Mapper 接口、处理项目公共模块:统一返回结果、统一异常处理

    案例综合练习 - 博客系统 本节目标 从 0 到 1 完成博客系统后端项目的开发。 前言 通过前面课程的学习&#xff0c;我们掌握了 Spring 框架和 MyBatis 的基本使用&#xff0c;并完成了图书管理系统的常规功能开发。接下来我们系统地从 0 到 1 完成一个项目的开发。 项目介绍 …

    基于OpenMV+STM32+OLED与YOLOv11+PaddleOCR的嵌入式车牌识别系统开发笔记

    基于OpenMV、STM32与OLED的嵌入式车牌识别系统开发笔记 基于OpenMV、STM32与OLED的嵌入式车牌识别系统开发笔记系统架构全景 一、实物演示二、OpenMV端设计要点1. 硬件配置优化2. 智能帧率控制算法3. 数据传输协议设计 三、PyTorch后端核心实现&#xff1a;YOLOv11与PaddleOCR的…

    w~嵌入式C语言~合集4

    我自己的原文哦~ https://blog.51cto.com/whaosoft/13870376 一、STM32怎么选型 什么是 STM32 STM32&#xff0c;从字面上来理解&#xff0c;ST是意法半导体&#xff0c;M是Microelectronics的缩写&#xff0c;32表示32位&#xff0c;合起来理解&#xff0c;STM32就是指S…

    K8S安全认证

    一。用户认证的基本框架 在K8S集群中&#xff0c;客户端通常有两类&#xff1a; 1.User Account&#xff1a;一般独立于K8S之外的其他服务管理的用过户账号 2.Service Account&#xff1a;K8S管理的账号&#xff0c;用于为Pod中的服务进程在访问K8S提供身份标识 ApiServer是…

    mybatis-plus里的com.baomidou.mybatisplus.core.override.MybatisMapperProxy 类的详细解析

    以下是 com.baomidou.mybatisplus.core.override.MybatisMapperProxy 类的详细解析&#xff1a; 1. 类的作用 MybatisMapperProxy 是 MyBatis-Plus 框架中用于实现 Mapper 接口动态代理的核心类。它继承自 MyBatis 的 MapperProxy&#xff0c;并扩展了以下功能&#xff1a; …

    在java程序中,类,进程,线程他们之间的关系以及main方法与他们之间的关系

    在 Java 程序里&#xff0c;类、进程、线程各自有着不同的概念&#xff0c;同时也存在着紧密的联系&#xff0c;下面来详细分析它们之间的关系以及 main 方法和它们的关系。 类 类是 Java 中最基础的编程单元&#xff0c;是一种自定义的数据类型&#xff0c;它定义了对象的属…

    [ACTF2020 新生赛]BackupFile题解

    启动环境 进入后只有一段话&#xff0c;没有其他的说法。 解题方法 但是题目命名为backup file&#xff0c;应该是备份文件的意思&#xff0c;那么就用dirsearch工具来扫一下看看。 查看扫描结果 index.php.bak 下载下这个文件&#xff0c;查看文件内容。 进行php代码审计…

    如何修改npm的全局安装路径?

    修改 npm 的全局安装路径可以通过以下步骤完成&#xff0c;确保全局包&#xff08;使用 -g 安装的模块&#xff09;和缓存文件存储到自定义路径。以下是详细步骤&#xff1a; 1. 创建自定义路径的目录 在目标路径下创建两个文件夹&#xff0c;分别用于存储全局模块和缓存文件…

    巧用 Element - UI 实现图片上传按钮的智能隐藏

    引言 在前端开发中&#xff0c;使用 Element - UI 组件库来构建用户界面是非常常见的操作。其中图片上传功能更是在许多项目中频繁出现&#xff0c;比如用户头像上传、商品图片上传等场景。有时候&#xff0c;我们会有这样的需求&#xff1a;当上传图片达到一定数量后&#xf…

    从“拼凑”到“构建”:大语言模型系统设计指南!

    你有没有试过在没有说明书的情况下组装宜家家具?那种手忙脚乱却又充满期待的感觉,和设计大语言模型(LLM)系统时如出一辙。如果没有一个清晰的计划,很容易陷入混乱。我曾经也一头扎进去,满心期待却又手足无措,被网上那些复杂的架构图搞得晕头转向。于是,我坐下来,把它们…

    【数据结构与算法】从完全二叉树到堆再到优先队列

    完全二叉树 CBT 设二叉树的深度为 h , 若非最底层的其他各层的节点数都达到最大个数 , 最底层 h 的所有节点都连续集中在左侧的二叉树叫做 完全二叉树 . 特点 对任意节点 , 其右分支下的叶子节点的最底层为 L , 则其左分支下的叶子节点的最低层一定是 L 或 L 1 .完全二叉树…