前后端
-
站点centos系统迁移ubuntu系统
概述 本站点最早使用的是centos系统,但是该系统官方不再更新维护了,随最近将服务器的系统更换为ubuntu系统,先记录站点的迁移过程。 迁移要点 由于本站点主要是基于php+nginx+wordpress框架设计。备份的原理是php、nginx、wordpress软件属于工具是可以在新的系统上新安装,但是wordpress相关的配置如主题、数据库、插件等属于数据是需要进行复制迁移的,因此备份的关键核心就是打包wordpress+mysql数据库。 wordpress:wordpress组件包括插件、主题等。 数据库:wordpress所需要的数据库。 上面两个主要就是站点的关键数据了,需要原封迁移到新的服务器上去。其他次要点依情况选择: nginx配置:一般只需要把ssl的证书保留就行,不过也可以在云平台上重新下载。保守就是把/etc/nginx都打包一份。 版本:注意记录一下php、nginx的版本,以免出现版本不匹配。 迁移方式 迁移的方式有两种方式,一种是使用wordpress插件的方式,另外就是完全手动复制的方式。下面先总结一下这两种方式关键的路径: 使用wordpress插件方式 该方式主要使用wordpress插件WPvivid Backup插件,该插件可以直接备份wordpress+数据库。核心思路是: 备份wordpress+数据库:在WPvivid Backup后台做一次最新的备份。 备份nginx(可选):/etc/nginx压缩打包,可以只保留证书也行。 存储:将备份文件存储到本地或云端。 迁移:在服务器重置安装新的系统,然后将备份文件复制到新的服务器上。 安装:搭建一个新的php、nginx、wordpress环境。包括配置nginx和创建wordpress新的数据库链接等。 恢复:在新的wordpress站点上安装WPviviBackup插件,扫描备份文件进行还原。 配置:重新配置nginx支持https等。 使用手动方式 该方式不使用插件,手动进行会稍微多一些步骤。核心思路如下: 备份wordpress:将wordpress进行压缩打包。 备份数据库:使用mysqldump备份数据库。 备份nginx(可选):/etc/nginx压缩打包,可以只保留证书也行。 存储:将备份的文件存储到本地或云端。 迁移:服务器重置系统后,将备份文件复制到新服务器上。 按装:按照php、nginx。 恢复:将wordpress解压到nginx的主页目录下,恢复mysql数据库。 配置:重新配置nginx支持https等。 插件方式 备份 步骤1:备份wordpress与数据库 在插件WPvivid Backup后台备份wordpress+数据库到本地,然后通过ftp或scp拷贝到本地电脑。 步骤2:备份nginx的证书 在服务器/etc/nginx目录下,把cert证书压缩同理通过ftp或scp拷贝到本地电脑。这个证书路径可能不一样,根据自己实际情况。 重装 用插件做好数据迁移到本地备份后,即可销毁服务器上的系统重新安装了,怎么安装选择系统这里就不阐述了,本文重装的是ubuntu系统,系统启动后进行如下步骤按照软件。 步骤1:按照基本的软件 首先,确保软件包列表是最新的,这样可以避免安装过时的软件版本。 sudo apt update 如果你想升级已安装的软件到最新版本,使用:sudo apt upgrade 安装 Nginx:sudo apt install nginx 检查 Nginx 服务状态:sudo systemctl status nginx 启动Nginx:sudo systemctl start nginx 安装 MySQL:sudo apt install mysql-server 启动MySQL: sudo systemctl start mysql 安装 PHP 和相关的扩展:sudo apt install php-fpm php-mysql php-cli php-curl php-mbstring php-xml php-zip php-gd 安装其他如curl 或 git:sudo apt install curl git 按照wordpress: wget https://wordpress.org/latest.tar.gz tar -xzvf latest.tar.gz mv wordpress /var/www/html/ 步骤2:配置wordpress的数据库 先创建一个数据库。 mysql -u root -p CREATE DATABASE wordpress_db; CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON wordpress_db.* TO 'wpuser'@'localhost'; FLUSH PRIVILEGES; EXIT; 创建一个mysql的数据库,用于给wordpress创建一个数据库。 wordpress_db:数据库的名称。 wpuser: 访问数据库的用户名。 your_password:访问数据需要的密码。 接着修改wordpress的配置可以访问数据库。 cd /var/www/html/wordpress/ cp wp-config-sample.php wp-config.php vim wp-config.php 更新刚设置的数据库名 define('DB_NAME', 'wordpress_db'); define('DB_USER', 'wpuser'); define('DB_PASSWORD', 'your_password'); define( 'DB_HOST', 'localhost' ); 步骤3:配置nginx 先做个备份 cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default_backup vim /etc/nginx/sites-available/default server { listen 80; server_name laumy.tech www.laumy.tech; root /var/www/html/wordpress; index index.php index.html index.htm index.nginx-debian.html; location / { try_files $uri $uri/ /index.php?$args; } location ~ .php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /.ht { deny all; } } server { listen 443 ssl; server_name laumy.tech www.laumy.tech; ssl_certificate /etc/nginx/cert/www.laumy.tech.pem; ssl_certificate_key /etc/nginx/cert/www.laumy.tech.key; root /var/www/html/wordpress; index index.php index.html index.htm index.nginx-debian.html; location / { try_files $uri $uri/ /index.php?$args; } location ~ .php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /.ht { deny all; } } 一个是配置http的监听端口,另外一个是配置https的监听端口。 server_name: 表示监听的网站,填自己的域名。 root /var/www/html/wordpress: 指定了网站寻址的路径,这里是wordpress存放的路径。 ssl_certificate**: 这个指定的是https的证书路径,只有443这个端口才需要填。 配置好后执行: sudo nginx -t && sudo systemctl reload nginx 修改wordpress配置强制支持https,vim /var/www/html/wordpress/wp-config.php,加上下面两行。 define('FORCE_SSL_ADMIN', true); define('FORCE_SSL_LOGIN', true); 步骤4:登录配置wordpress 没什么问题的话,登录自己的域名就可以进入到wordpress界面设置用户名和密码进入到后台了。 注意记得把后台的站点地址用https的方式,不然http和https混合访问的问题。 恢复 按照前面的步骤,顺利进入到wordpress后台之后,到这里就简单了。只需要按照插件WPvivid Backup进行还原即可。 步骤1:将备份文件拷贝到插件指定目录 默认一般是在/var/www/html/wordpress/wp-content/wpvividbackups 步骤:还原 在插件界面扫描到备份文件,进行还原。等还原完成,到此就大功告成了。 手动方式 手动跟插件的方式这里只列出差异点,就不再过多赘述了。 备份 对wordpress整个文件夹进行压缩,其次对mysql数据库进行备份。 mysqldump -u root -p your_database_name > wordpress-db-backup.sql your_database_name 是你的数据库名称,要是忘记了就去wordpress/wp-config.php看看DB_NAME,密码亦是如此如下。 define('DB_NAME', 'wordpress_db'); define('DB_USER', 'wpuser'); define('DB_PASSWORD', 'your_password'); 接着把wordpress压缩包、sql、nginx的证书拷贝到本地或云端。 恢复 将wordpress解压到/var/www/html/下。然后对数据库进行恢复,这里重点补充一下。 首先是要在mysql里面创建一个新的数据库。 mysql -u root -p CREATE DATABASE wordpress_db; CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON wordpress_db.* TO 'laumy'@'localhost'; FLUSH PRIVILEGES; exit 注意这里的wordpress_db、wpuser、your_password分别为数据库名称、用户名、密码。可以跟你wordpress里面wp-config.php的一样,这样到时候就不用改了。 接入导入新的数据库。 mysql -uwpuser -pyour_password wordpress_db < wordpress-db-backup.sql 这样数据库就导入完成了。 总结一下,无论是那种方式的备份,最关键的就是将wordpress和数据库能够迁移复制过去就行。而php、nginx正常按照新的就行。而nginx网站的配置,主要就是注意将wordpress要解压位置设置好,以及读写权限要配置好。其次就是配置/etc/nginx/sites-available/default指定好网站的root路径(即wordpress路径)和证书的路径即可完成。 最后要是要是过程中遇到问题可以询问ai,当下ai已经非常强大了,只要把环境信息告知好,基本上所有的问题都能能解决。 -
站点开源
Laumy Fresh Theme 本站点是基于wordpress设计,现开源其主题代码,让感兴趣的爱好者直接可复用。源码地址:https://github.com/laumy0929/wd-laumy-fresh-theme。 🚀 快速开始 方法一:WordPress后台安装(推荐) 下载主题压缩包 laumy-fresh-theme.zip 在WordPress后台进入 外观 > 主题 点击 添加新主题 > 上传主题 选择下载的压缩包文件 点击 现在安装 并激活主题 方法二:FTP上传 解压主题文件到本地 通过FTP上传到 /wp-content/themes/laumy-fresh-theme/ 目录 在WordPress后台激活主题 方法三:Git克隆 cd wp-content/themes/ git clone https://github.com/laumy0929/wd-laumy-fresh-theme.git 🌟 主题特色 设计理念 简洁美观:采用现代简约设计风格,专注于内容阅读体验 响应式布局:完美适配桌面端、平板和移动设备 深色模式:支持一键切换深色/浅色主题 高效性能:优化的CSS和JavaScript,确保快速加载 核心功能 📱 三栏布局:左侧分类导航 + 中间内容区域 + 右侧个人信息 🔍 智能搜索:顶部居中搜索框,快速查找内容 📂 分类管理:支持多级分类展开/收起,显示文章数量 📖 文章目录:自动生成多级目录,支持滚动高亮 💬 评论系统:完整的WordPress评论功能 📊 数据统计:显示文章数量和阅读量统计 🎨 主题切换:一键切换深色/浅色模式 ⚙️ 主题配置详解 个人资料设置 进入自定义设置 在WordPress后台点击 外观 > 自定义 或者点击 外观 > 主题 中的 自定义 按钮 配置个人信息 在 作者信息 部分设置: 头像: 点击 选择图片 上传个人头像 推荐尺寸:320×180 像素 支持格式:JPG、PNG、GIF 如果不上传,会显示默认头像 昵称: 输入你想显示的名称 会显示在右侧个人信息卡片的顶部 支持中文和英文 职业: 输入你的职业或身份标签 会显示为昵称右上角的绿色徽章 例如:前端工程师、全栈开发、技术博主 个性签名: 输入个人简介或座右铭 会显示在昵称下方 支持多行文本 保存设置 点击 发布 按钮保存所有设置 分类菜单配置 创建主菜单 进入 外观 > 菜单 点击 创建新菜单 输入菜单名称(如:主菜单) 选择菜单位置为 Primary Menu 添加分类到菜单 在左侧 分类 面板中选择需要的分类 点击 添加到菜单 拖拽调整分类顺序 设置父子分类关系: 将子分类拖拽到父分类下方 向右缩进表示层级关系 菜单结构示例 📁 技术笔记 📁 前端开发 📁 HTML/CSS 📁 JavaScript 📁 Vue.js 📁 后端开发 📁 PHP 📁 Python 📁 Node.js 📁 运维部署 📁 Linux 📁 Docker 📁 Nginx 文章缩略图设置 方法一:设置特色图片(推荐) 在编辑文章时,找到右侧 特色图片 面板 点击 设置特色图片 上传或选择图片 推荐尺寸:160×100 像素 点击 设置特色图片 确认 方法二:在文章内容中插入图片 在文章开头位置插入图片 主题会自动提取第一张图片作为缩略图 建议图片尺寸:160×100 像素或更大 缩略图优先级 特色图片(最高优先级) 文章内容第一张图片 默认占位图(如果以上都没有) 默认缩略图位置 文件路径:assets/images/default-thumbnail.svg 可以替换为自定义图片 建议保持 160×100 像素尺寸 文章目录功能 自动生成规则 基于文章中的 H2-H5 标题自动生成 支持无限级嵌套目录 自动编号:1、1.1、1.1.1... 使用方法 在文章中使用标准的标题标签: <h2>主要章节</h2> <h3>子章节</h3> <h4>详细内容</h4> 目录会自动显示在文章左侧 支持点击跳转和滚动高亮 目录样式 左侧:📋 剪贴板图标 右侧:☰ 汉堡菜单图标 支持展开/收起功能 深色模式配置 启用深色模式 在页面顶部找到主题切换按钮(太阳/月亮图标) 点击即可切换深色/浅色模式 设置会自动保存到浏览器本地存储 深色模式特性 自动调整所有颜色 背景色:深灰色 (#181a1b) 文字色:浅色 (#eceff1) 卡片背景:深色 (#202225) 📱 响应式设计说明 桌面端 (>1200px) 完整三栏布局 左侧分类导航:260px 右侧个人信息:300px 中间内容区域:自适应宽度 平板端 (992px-1200px) 保持三栏布局 侧边栏宽度自动调整 内容区域适当缩小 移动端 (<768px) 单列布局 隐藏网站标题和部分导航 搜索框居中显示 保留主题切换功能 🎨 自定义样式 CSS变量 主题使用CSS变量,便于自定义: :root { --header-height: 56px; --sidebar-left-width: 260px; --sidebar-right-width: 300px; --container-max-width: 1800px; --gap: 14px; --color-bg: #ffffff; --color-text: #222; --color-muted: #666; --color-primary: #3498db; --color-border: #e6e6e6; } 自定义颜色 在 外观 > 自定义 > 颜色 中调整: - 主色调 - 背景色 - 文字颜色 - 边框颜色 📁 文件结构 laumy-fresh-theme/ ├── style.css # 主题样式文件 ├── index.php # 首页模板 ├── single.php # 文章页面模板 ├── category.php # 分类页面模板 ├── search.php # 搜索结果模板 ├── page.php # 页面模板 ├── header.php # 头部模板 ├── footer.php # 底部模板 ├── functions.php # 主题功能文件 ├── comments.php # 评论模板 ├── searchform.php # 搜索表单 ├── screenshot.png # 主题截图 ├── assets/ │ ├── js/ │ │ └── theme.js # 主题JavaScript │ └── images/ │ ├── default-thumbnail.svg # 默认缩略图 │ └── default-profile.svg # 默认头像 └── README.md # 说明文档 🔧 高级功能配置 评论系统设置 进入 设置 > 讨论 启用 嵌套评论 功能 设置评论审核规则 配置评论通知 搜索功能优化 进入 设置 > 固定链接 选择 文章名 或 自定义结构 保存设置以优化搜索 性能优化 启用WordPress缓存插件 压缩图片文件 定期清理数据库 使用CDN加速 🎯 使用技巧 分类管理最佳实践 使用清晰的分类名称 合理设置分类层级(建议不超过3级) 定期整理和合并相似分类 为每个分类添加描述 文章写作建议 使用标准的H2-H5标题结构 在文章开头插入相关图片 设置合适的特色图片 添加适当的标签 用户体验优化 定期更新个人资料 保持分类结构清晰 使用高质量的缩略图 及时回复评论 🐛 常见问题解答 Q: 分类不显示或显示异常? A: 检查以下设置: 1. 在 外观 > 菜单 中是否设置了主菜单 2. 菜单位置是否选择为 Primary Menu 3. 分类是否已添加到菜单中 4. 分类是否有文章内容 Q: 个人资料头像不显示? A: 检查以下设置: 1. 在 外观 > 自定义 > 作者信息 中是否上传了头像 2. 图片格式是否支持(JPG、PNG、GIF) 3. 图片大小是否合适(建议320×180像素) Q: 文章缩略图显示异常? A: 尝试以下方法: 1. 设置文章特色图片 2. 在文章开头插入图片 3. 检查图片格式和大小 4. 替换默认缩略图文件 Q: 深色模式切换不生效? A: 检查以下设置: 1. 确保浏览器支持localStorage 2. 清除浏览器缓存 3. 检查是否有JavaScript错误 4. 尝试刷新页面 Q: 移动端显示异常? A: 检查以下设置: 1. 主题已针对移动端优化 2. 检查是否有其他插件冲突 3. 清除浏览器缓存 4. 检查CSS是否被其他插件覆盖 Q: 文章目录不显示? A: 检查以下设置: 1. 文章内容是否包含H2-H5标题 2. 标题标签是否正确使用 3. 检查JavaScript是否正常加载 4. 查看浏览器控制台是否有错误 📞 交流方式 作者:Laumy 邮箱:laumy0929@gmail.com 网站:http://www.laumy.tech/ GitHub:https://github.com/laumy0929/wd-laumy-fresh-theme 📄 许可证 本主题基于 GPL v2 或更高版本开源协议发布。 -
emqx通过REST API获取设备是否在线
本文通过emqx REST API的方式来进行配置 创建一个API密钥 记录密钥的名称 API key是用户名称,Secret KEY是密码 浏览器登录验证 用户名就是 API Key,密码就是Secret KEY. 登录上之后就可以获取到设备的信息了。 可以参考: http://localhost:18083/api-docs/index.html 支持哪些API 官网参考: https://docs.emqx.com/zh/emqx/latest/admin/api.html -
webrtc网页代码分析二
交互流程 上图是完整的处理流程。 获取URL参数 const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const deviceId = urlParams.get('deviceId'); 这段代码先获取当前页面 URL 中的查询字符串部分(window.location.search),然后使用 URLSearchParams 对象解析查询字符串,尝试获取名为 deviceId 的参数值,后续很多操作会根据这个 deviceId 的有无来进行不同的逻辑处理。 页面加载时的初始化操作 window.onload = function () { if (deviceId == undefined) { document.getElementById('video').style.display = 'none'; document.getElementById('getting-started').style.display = 'block'; } const canvas = document.createElement('canvas') canvas.width = 640 canvas.height = 480 const ctx = canvas.getContext('2d') ctx.fillStyle = 'rgba(200, 200, 200, 100)' ctx.fillRect(0, 0, 640, 480) const img = new Image(640, 480) img.src = canvas.toDataURL() document.getElementById('imgStream').src = img.src; } window.onload是页面加载完成后的回调函数,上面使用的是匿名函数,即当页面所有内容加载完成后执行的逻辑。具体逻辑如下 这里判断如果deviceId未定义(也就是可能页面直接访问,没有带上对应的参数),则隐藏视频相关区域(id为video的div),显示初始引导内容区域(id 为 getting-started 的 div)。在函数内创建了一个 canvas 元素,绘制了一个半透明的矩形,将其转换为图片数据后设置给img id=\\\\"imgStream\\\\"元素作为初始显示内容。 MQTT操作 发起连接 var options = { keepalive: 600, username: 'xxx', password: 'xxxx', }; const client = mqtt.connect('wss://xxx.xxx.com:8084/mqtt', options); 定义了 MQTT连接的相关选项(如保活时间、用户名、密码等),然后尝试连接到指定的MQTT服务器地址(wss://xxx.xxx.xxx:8084/mqtt),后续通过client对象来处理 MQTT 的各种消息收发以及相关操作。 事件处理 connect事件 client.on('connect', function () { console.log("connected") client.subscribe('webrtc/' + deviceId + '/jsonrpc-reply', function (err) { if (!err) { } }); console.log('publish to webrtc/' + deviceId + '/jsonrpc'); client.publish('webrtc/' + deviceId + '/jsonrpc', JSON.stringify({ jsonrpc: '2.0', method: 'offer', id: offerId, }), { qos: 2 }); }) 结构为client.on(\\\\'connect\\\\', function () {... }); client.on这里是mqtt对象的事件回调,这里将function(){...}作为回调函数注册进去,funciton实现是匿名函数的方式。当前收到connect事件时,就会执行function的内容。具体逻辑如下。 当成功连接到MQTT服务器后,先在控制台输出 connected 表示连接成功,然后订阅一个特定主题(webrtc/\\\\' + deviceId + \\\\'/jsonrpc-reply)用于接收回复消息,接着向另一个特定主题(webrtc/\\\\' + deviceId + \\\\'/jsonrpc)发布一条包含offer信息的 JSON 格式消息(设置了消息质量等级 qos 为 2),用于发起某种交互(可能与 WebRTC 相关的会话建立等操作)。 (1)订阅函数subscribe client.subscribe(topic, options, callback); topic: 需要订阅的主题,可以是一个字符串或者一个主题数组。主题是 MQTT 中用于分类消息的方式。主题的命名规则通常是层级的,使用斜杠(/)分隔每一层级,例如 \\\\'home/livingroom/temperature\\\\'。 options: 可选参数,配置订阅的一些选项,如 QoS(服务质量)级别。常见的选项包括: -- qos: 服务质量(Quality of Service)级别,取值范围是: -- 0 - 至多一次:消息最多发送一次,不做确认。 -- 1 - 至少一次:消息至少发送一次,可能会重复。 -- 2 - 只有一次:消息只能传递一次,不会丢失,也不会重复。 callback: 可选的回调函数,当订阅成功时会被调用。回调函数接受一个错误对象作为参数,如果订阅成功,err 为 null;如果订阅失败,则返回相应的错误信息。 (2)发布函数publish client.publish(topic, message, options, callback); topic: 需要发布消息的主题,可以是一个字符串,表示主题名称。 message: 需要发布的消息内容,可以是字符串、Buffer、对象等。一般情况下,消息会被自动转换为字符串或 Buffer(如果传入对象,会自动通过 JSON.stringify() 转换为字符串)。 options: 可选参数,配置发布消息的选项,常见的选项包括: -- qos: 服务质量(Quality of Service)级别(如前述),默认是 0。 -- retain: 是否保留消息,若为 true,代理服务器会保存消息并在新订阅者连接时发送该消息。 callback: 可选的回调函数,发布消息成功后会被调用。回调函数接受一个错误对象作为参数,如果发布成功,err 为 null;如果发布失败,则返回相应的错误信息。 messange事件 MQTT消息接收处理(client.on(\\\\'message\\\\', function (topic, message) {... })),语法与上面类似,逻辑如下。 client.on('message', function (topic, message) { let msg = JSON.parse(message.toString()); if (msg.id == offerId) { let sdp = msg.result; let offer = { type: 'offer', sdp: sdp }; log(offer); pc.setRemoteDescription(offer); pc.createAnswer().then(d => pc.setLocalDescription(d)).catch(log); } else if (msg.id == answerId) { log('receive answer ok'); } }) 当接收到MQTT消息时,先将消息内容解析为 JSON 对象,然后根据消息中的 id 判断消息类型,如果 id 与之前定义的 offerId 相等,说明是对之前 offer 的回复,会提取其中的 sdp 信息设置为远程描述(setRemoteDescription),并创建一个本地回答(createAnswer)然后设置本地描述(setLocalDescription);如果 id 等于 answerId,则只是在控制台输出表示接收到了回答消息。 WebRTC操作 创建对象 var pc = new RTCPeerConnection({ iceServers: [ { urls: "turn:rtpeer.allwinnertech.com:3478", username: "pctest", credential: "Aa123456", }, ], }); 创建了一个 RTCPeerConnection 对象用于建立 WebRTC 连接,配置了 iceServers,这里指定了一个 TURN 服务器的相关信息(地址、用户名、密码),有助于在复杂网络环境(如存在 NAT 等情况)下建立稳定的点对点连接,确保音视频数据能顺利传输。 获取PC录音 navigator.mediaDevices.getUserMedia({ video: false, audio: true }) .then(stream => { stream.getTracks().forEach(track => pc.addTrack(track, stream)); }).catch(log); 通过 navigator.mediaDevices.getUserMedia 方法请求获取音频媒体流(这里明确只要音频,video: false, audio: true),如果获取成功,将音频轨道添加到 RTCPeerConnection 对象(pc)中,以便后续传输音频数据,如果出现错误则通过 log 函数(也就是在控制台输出错误信息)进行处理。 连接状态 pc.oniceconnectionstatechange = e => { log(pc.iceConnectionState); document.getElementById('status').innerHTML = pc.iceConnectionState; if (pc.iceConnectionState == 'connected') { // default to muted if (!isMuted) onMuted(); } } 当 WebRTC 连接的 ICE 连接状态发生变化时,先在控制台输出当前连接状态(通过 log 函数),然后更新页面上 id 为 status 的元素文本内容为当前连接状态,并且当连接状态变为 connected(已连接)时,如果当前不是静音状态(!isMuted),则调用 onMuted 函数将音频静音。 ICE候选者 pc.onicecandidate = event => { if (event.candidate === null) { console.log(pc.localDescription.sdp) let json = { jsonrpc: \\'2.0\\', method: \\'answer\\', params: pc.localDescription.sdp, id: answerId, } console.log(json) client.publish(\\'webrtc/\\' + deviceId + \\'/jsonrpc\\', JSON.stringify(json)) setInterval(() => {....}, 1000); } } 当有 ICE 候选者信息产生并且当候选者为 null 时(意味着 ICE 收集过程结束),执行一系列操作,包括打印 RTCPeerConnection 的本地描述中的 sdp 信息,向 MQTT 特定主题发布包含本地描述 sdp 信息的 JSON 消息,并且每隔 1 秒获取连接的统计信息(通过 pc.getStats 方法),整理后更新到页面中 id 为 stats-box 的元素内展示出来。 当收到对端的offer候选信息后,本地会进行与stun/turn服务交互,交互获取到ICE信息后,就会触发回调这个函数,将自己SDP信息组装成json通过mqtt publish发送给对端。 接收音视频 pc.ontrack = function (event) { if (event.track.kind == \\'video\\') { var el = document.getElementById(\\'videoStream\\'); var newStream = new MediaStream(); newStream.addTrack(event.track); el.srcObject = newStream; el.autoplay = true el.controls = false el.muted = true document.getElementById(\\'imgStream\\').style.display = \\'none\\'; document.getElementById(\\'videoStream\\').style.display = \\'block\\'; // 当视频流添加成功后,获取视频分辨率和码率信息并更新显示 el.addEventListener(\\'loadedmetadata\\', function () { updateVideoInfo(event.track); }); } else if (event.track.kind == \\'audio\\') { var el = document.getElementById(\\'audioStream\\'); var newStream = new MediaStream(); newStream.addTrack(event.track); el.srcObject = newStream; el.controls = false el.muted = false } } 当通过 RTCPeerConnection 接收到远端传来的媒体轨道(视频或音频轨道)时,根据轨道类型进行不同的处理。对于视频轨道,获取页面中的视频元素(id 为 videoStream),创建新的 MediaStream 对象添加轨道后设置为视频元素的播放源,同时设置自动播放、隐藏控制条、静音等属性,并在视频元数据加载完成后(通过 loadedmetadata 事件)调用 updateVideoInfo 函数更新视频分辨率和码率信息显示;对于音频轨道,类似地获取音频元素(id 为 audioStream),添加轨道到新的 MediaStream 对象并设置为音频元素播放源,设置不显示控制条且不静音。 数据通道 const datachannel = pc.createDataChannel(\\'pear\\') datachannel.onclose = () => console.log(\\'datachannel has closed\\'); datachannel.onopen = () => { console.log(\\'datachannel has opened\\'); console.log(\\'sending ping\\'); setInterval(() => { console.log(\\'sending ping\\'); datachannel.send(\\'ping\\'); }, 1000); } datachannel.onmessage = e => { if (e.data.byteLength === undefined) { console.log(e.data); } else { // is binary data. mjpeg stream // console.log(e.data.byteLength); var arrayBufferView = new Uint8Array(e.data); var blob = new Blob([arrayBufferView], { type: \\"image/jpeg\\" }); var urlCreator = window.URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL(blob); var imageElement = document.getElementById(\\'imgStream\\'); imageElement.src = imageUrl; } } 创建了一个名为pear的数据通道(const datachannel = pc.createDataChannel(\\'pear\\')),并分别对数据通道的打开、关闭、接收消息等事件绑定了相应的回调函数。例如在数据通道打开时(datachannel.onopen),会定期(每隔 1 秒)发送 ping 消息用于保持连接或检测连接状态等;在接收到消息时(datachannel.onmessage),根据消息数据类型判断,如果是文本消息直接在控制台输出,如果是二进制数据(判断为 MJPEG 流),则将其转换为图片的 URL 并设置给页面中的图片元素(id 为 imgStream)用于显示。 所以这里支持mjpeg的方式,但是是通过数据通道来实现的。 -
webrtc网页代码分析一
文档结构 <!doctype html> <html lang="en"> <head> ... </head> <body> ... </body> </html> 文档类型声明():声明文档使用HTML5标准。 html标签:设置文档的语言为英语(lang=\"en\")。 head标签:包含元数据、样式、外部链接等,定义网页的头部信息。 body标签:页面的主体部分,包含所有网页内容。 head标签 字符集和窗口设置 <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> meta标签:用于定义页面的字符集、视口设置、作者信息等。 charset=\\"utf-8\\":指定字符编码为UTF-8,确保网页能够正确显示多种语言的字符。 viewport 设置:用于响应式设计,确保页面在不同设备上显示良好。width=device-width 表示页面宽度等于设备的屏幕宽度,initial-scale=1 表示初始缩放比例为 1。 外部资源引入 <script src="https://unpkg.com/mqtt@4.1.0/dist/mqtt.min.js"></script> <script src="https://kit.fontawesome.com/8c8bbe3334.js" crossorigin="anonymous"></script> MQTT库:引入了 MQTT JavaScript 客户端库(版本 4.1.0)。MQTT 是一种轻量级的消息发布/订阅协议,通常用于 IoT(物联网)应用。这表明页面可能涉及到实时消息传递或设备间通信。 FontAwesome图标库:引入了 FontAwesome 的 JavaScript 库,用于提供图标支持。crossorigin=\"anonymous\" 允许浏览器在不发送凭据的情况下访问该资源。 CSS样式 style标签包含了许多样式规则,以下是主要部分的分析。 全局设置 html { font-family:"Calibri", sans-serif; box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } font-family: \"Calibri\", sans-serif;:设置了页面字体为 \"Calibri\",如果不支持则使用 sans-serif。 box-sizing: border-box;:使得所有元素的 padding 和 border 都包含在 width 和 height 的计算中,避免了布局问题。 响应式设计 @media screen and (max-width: 650px) { .column { width: 100%; display: block; } } 使用 @media 媒体查询来调整页面布局。在屏幕宽度小于或等于 650px 时,将 .column 类的元素的宽度设置为 100%,并显示为块级元素。这样有助于在小屏幕设备(如手机)上优化布局。 卡片样式和容器 .card { box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); } .container { position: relative; text-align: center; color: white; } .card:为卡片元素设置了一个阴影效果,使其在页面上看起来更立体。 .container:设置容器为相对定位,并使文本居中。文本颜色为白色。 按钮样式 .title { color: grey; } .bottom-left, .top-left, .top-right, .bottom-right { position: absolute; padding: 10px; } .button { border: none; outline: 0; display: inline-block; padding: 8px; color: white; background-color: #000; text-align: center; cursor: pointer; } .button:hover { background-color: #555; } .title:为标题设置灰色字体颜色。 .bottom-left, .top-left, .top-right, .bottom-right:为定位在四个角的元素提供了绝对定位样式。通过定位,它们可以被放置在容器的四个角。 .button:定义了按钮的基本样式,包括无边框、白色文字、黑色背景以及光标指针。hover 样式将按钮背景色更改为灰色(#555)。 导航栏样式 .topnav { overflow: hidden; background-color: #333; } .topnav a { float: left; display: block; color: #f2f2f2; text-align: center; padding: 14px 16px; text-decoration: none; font-size: 17px; } .topnav a:hover { background-color: #ddd; color: black; } .topnav a.active { background-color: #04AA6D; color: white; } .topnav:定义了一个水平的导航栏,背景色为深灰色,且隐藏溢出的内容。 .topnav a:定义了导航栏中每个链接的样式,包括文本颜色、对齐方式、内边距等。 .topnav a:hover:设置链接在悬停时背景色变为浅灰色,文字颜色变为黑色。 .topnav a.active:当链接处于 \"活动\" 状态时,背景色变为绿色(#04AA6D),文字变为白色。 按钮样式 .btn-group button { margin-top: -4px; background-color: white; border: 1px solid white; color: black; padding: 10px 24px; cursor: pointer; } .btn-group button:hover { background-color: #f2f2f2; } .btn-group button:定义了按钮组中的按钮样式,包括背景色为白色,边框为白色,文本为黑色,且在悬停时背景色变为浅灰色。 普通按钮样式 .btn { background-color: DodgerBlue; border: none; color: white; padding: 12px 16px; font-size: 16px; cursor: pointer; } .btn:hover { background-color: RoyalBlue; } .btn:定义了一个蓝色的按钮,包含内边距、字体颜色等样式。在悬停时,按钮的背景色会变成更深的蓝色(RoyalBlue)。 body标签 body包含页面部分显示部分和script部分。 页面内容 导航栏区域 <div class="topnav" id="myTopnav"> <a href="#">Peer Project</a> <a href="https://ko-fi.com/sepfy95" style="float: right">Sponsor</a> <a href="https://github.com/sepfy/pear" style="float: right">Github</a> </div> 这是一个顶部导航栏(topnav)元素,包含三个链接: Peer Project:这是主页面的链接,指向当前页面(#)。可以是应用的标题或者主页面链接。 ko-fi.com:链接到 ko-fi.com,可能是用于捐赠或支持项目。 github: GitHub 项目的链接,用户可以访问该链接查看源代码或参与项目。 这些链接使用了 float: right 样式,使得“Sponsor”和“Github”链接都被浮动到右侧,而“Peer Project”链接则停留在左侧。 音视频展示区域 //这是包含视频内容的容器 <div class="column" id="video"> //用来包裹所有显示视频、音频及空间的内容,创建一个卡片式布局 <div class="card"> //容器内放置了视频和音频元素,包含一些展示信息的<p>标签 <div class="container"> // 媒体元素信息 //图片元素,在没有视频流可用时显示,他的id是imgStream <img id="imgStream" style="width: 100%"> //video元素,用于显示视频流,display样式为none,默认视频流不可见。通过js控制 <video id="videoStream" playsinline style="width:100%; display: none"></video> //audio元素,用于播放音频流的,默认被隐藏,通过js控制。 <audio id="audioStream" style="display: none"></audio> //用于设置设备信息 //段落元素用于显示设备信息,这里是device id <p class="top-left" id="device-id"></p> //段落元素,用于显示设备当的状态。 <p class="top-right" id="status">waiting</p> </div> //控制按钮,包括音量、录音、旋转、暂停 <div class="btn-group" style="width:100%"> //控制音量的按钮,调用onVolume函数,图标为fa-solid fa-volume-xmark <button style="width:25%" class="btn" onclick="onVolume()"><i id="volume-icon" class="fa-solid fa-volume-xmark"></i></button> //控制是否静音,调用onMuted函数,图标为fa-solid fa-microphone-slash <button style="width:25%" class="btn" onclick="onMuted()"><i id="mute-icon" class="fa-solid fa-microphone-slash"></i></button> //用于旋转视频,点击调用onRotate函数,图标为solid fa-arrows-rotate <button style="width:25%" class="btn" onclick="onRotate()"><i class="fa-solid fa-arrows-rotate"></i></button> //用于停止视频或音频流,点击调用onStop函数,图标为fa-solid fa-circle-stop <button style="width:25%" class="btn" onclick="onStop()"><i id="stop-icon" class="fa-solid fa-circle-stop"></i></button> </div> </div> </div> -
javascript之dom
什么是dom DOM(Document Object Model)是一个编程接口,它将 HTML 或 XML 文档呈现为一个由节点和对象(这些节点和对象其实是文档的各种元素、属性和文本内容等)组成的树形结构。这个树形结构允许开发者使用编程语言(如 JavaScript)来访问、修改和操作文档的内容、结构和样式。对于以下简单的 HTML 文档: <!DOCTYPE html> <html> <head> <title>My Page</title> </head> <body> <h1>Hello, World!</h1> <p id=\\\"myParagraph\\\">This is a paragraph.</p> </body> </html> 它的 DOM 树结构大致如下: - html 节点是根节点,它包含两个子节点:head 和 body。 - head 节点包含一个子节点 title,title 节点的文本内容是 \\\\\\\"My Page\\\\\\\"。 - body 节点包含两个子节点:h1 和 p。h1 节点的文本内容是 \\\\\\\"Hello, World!\\\\\\\",p 节点有一个属性 id 为 \\\\\\\"myParagraph\\\\\\\",其文本内容是 \\\\\\\"This is a paragraph.\\\\\\\"。 获取dom元素 getElementById 这是最常用的方法之一,用于通过元素的id属性获取单个元素。id在HTML文档中应该是唯一的,语法如下。 document.getElementById(\\\"elementId\\\"); 在上面的 HTML 文档中,如果要获取p元素,可以使用以下 JavaScript 代码: var paragraph = document.getElementById(\\\"myParagraph\\\"); console.log(paragraph.textContent); // 输出:This is a paragraph. getElementsByTagName 根据标签名获取元素集合。它返回一个类似数组的对象(HTMLCollection),包含所有匹配标签名的元素。语法如下: document.getElementsByTagName(\\\"tagName\\\"); 要获取文档中的所有 h1 元素: var h1Elements = document.getElementsByTagName(\\\"h1\\\"); for (var i = 0; i < h1Elements.length; i++) { console.log(h1Elements[i].textContent); // 输出:Hello, World! } getElementsByClassName 通过类名获取元素集合。同样返回一个 HTMLCollection,包含所有具有指定类名的元素。 document.getElementsByClassName(\\\"className\\\"); 假设HTML中有多个元素有highlight类,如下: <p class=\\\"highlight\\\">This is a highlighted paragraph.</p> <span class=\\\"highlight\\\">This is a highlighted span.</span> 可以使用以下代码获取这些元素: var highlightedElements = document.getElementsByClassName(\\\"highlight\\\"); for (var i = 0; i < highlightedElements.length; i++) { console.log(highlightedElements[i].textContent); // 输出两个元素的文本内容 } 修改元素 修改元素内容 textContent属性 用于获取或设置元素的文本内容,如改变前面获取的p元素的内容 var paragraph = document.getElementById(\\\"myParagraph\\\"); paragraph.textContent = \\\"This is a new paragraph content.\\\"; innerHTML属性 用于获取或设置元素内部的HTML内容。可以用于添加或修改元素内部的标签和文本。如在前面p元素内部添加一个strong标签: var paragraph = document.getElementById(\\\"myParagraph\\\"); paragraph.innerHTML = \\\"This is a <strong>modified</strong> paragraph.\\\"; 修改元素属性 获取和设置属性 可以通过元素对象的属性来获取和设置大多数 HTML 属性。例如,对于一个 元素的 src 属性。假设 HTML 中有一个img元素,可以这样修改其src属性: <img id=\\\"myImage\\\" src=\\\"original.jpg\\\"> var image = document.getElementById(\\\"myImage\\\"); image.src = \\\"new.jpg\\\"; 使用setAttribute和getAttribute setAttribute 用于设置元素的属性,getAttribute 用于获取元素的属性。如对于上面的img元素,也可以这样操作: var image = document.getElementById(\\\"myImage\\\"); image.setAttribute(\\\"src\\\", \\\"new.jpg\\\"); var currentSrc = image.getAttribute(\\\"src\\\"); console.log(currentSrc); // 输出:new.jpg 创建与添加DOM元素 创建元素 使用createElement方法。具体为先使用 document.createElement(\\\\\\\"tagName\\\\\\\") 创建一个新的 HTML元素。如创建一个新的div元素并设置其 id 和内容,如下。 var newDiv = document.createElement(\\\"div\\\"); newDiv.id = \\\"newDiv\\\"; newDiv.textContent = \\\"This is a new div.\\\"; 添加元素 appendChild 使用appendChild方法,将一个元素添加为另一个元素的子元素。例如,将新创建的div元素添加到 body元素中。 var body = document.getElementsByTagName(\\\"body\\\")[0]; var newDiv = document.createElement(\\\"div\\\"); newDiv.id = \\\"newDiv\\\"; newDiv.textContent = \\\"This is a new div.\\\"; body.appendChild(newDiv); insertBefore 在指定的子元素之前插入一个新元素。它需要两个参数:要插入的新元素和参考元素(新元素将插入在参考元素之前)。如假设有两个 p 元素在 body 中,在第一个 p 元素之前插入新的 div 元素。 <body> <p>First Paragraph</p> <p>Second Paragraph</p> </body> var body = document.getElementsByTagName(\\\"body\\\")[0]; var newDiv = document.createElement(\\\"div\\\"); newDiv.id = \\\"newDiv\\\"; newDiv.textContent = \\\"This is a new div.\\\"; var firstParagraph = document.getElementsByTagName(\\\"p\\\")[0]; body.insertBefore(newDiv, firstParagraph); 删除DOM元素 用于从父元素中删除指定的子元素,语法如下。 parentElement.removeChild(childElement); 如假设要删除前面添加的div元素(假设它有 id 为 \\\\\\\"newDiv\\\\\\\")。 var divToRemove = document.getElementById(\\\"newDiv\\\"); var body = document.getElementsByTagName(\\\"body\\\")[0]; body.removeChild(divToRemove); -
快速搭建一个可访问的网页
简介 搭建网站需要以下几个组件, Web服务器(如Apache、Nginx):它是网页能够被访问的核心组件。当用户在浏览器中输入网站的域名或 IP 地址并请求访问网页时,Web 服务器软件会接收这些 HTTP 请求。负责从服务器的存储设备(如硬盘)中找到对应的网页文件(如 HTML、CSS、JavaScript 文件等),并将这些文件发送回用户的浏览器。 编程语言支持(如PHP、Python、Node.js):实现网页的动态生成和业务逻辑,如果只是静态网页或不需要服务器端脚本处理PHP也不需要安装。 SSL证书(可选):用于加密HTTP通信,提升安全性。如果不需要安全链接,也不需要安装。 数据库(如MySQL、PostgreSQL)(可选):如果网页需要存储和管理大量的数据(如用户信息、文章内容、产品信息等),数据库就非常重要。 安装 安装nginx apache和nginx可以二选一 软件安装步骤 步骤1: 执行命令安装nginx sudo yum install nginx 步骤2: 启动nginx sudo systemctl start nginx 步骤3:查看启动状态 sudo systemctl status nginx 配置证书支持HTTPS 步骤1: 打开nginx.conf文件 /etc/nginx/nginx.conf 步骤2: 配置证书路径 步骤3: 重启nginx sudo systemctl restart nginx 根页面 在nginx.conf文件中,可以配置根页面,如上图的root /usr/share/nginx/html。 安装Apache 软件安装步骤 步骤1:执行命令安装Apache及其扩展包。 sudo yum -y install httpd httpd-manual httpd-devel mod_ssl mod_perl php-mysqli 如果回显信息显示Complete!,则表示Apache安装成功。 步骤2:依次执行命令启动Apache并设置自启动。 sudo systemctl start httpd sudo systemctl enable httpd 步骤3:执行命令查看Apache运行状态。如果回显信息显示active(running)时,表示Apache已启动。 systemctl status httpd 4.在本地电脑浏览器地址栏中输入http://<ECS服务器的公网IP>,测试Apache服务是否安装成功。如果显示如图所示的测试页面,表示Apache服务已安装成功。ECS服务器的弹性公网IP可以从ECS实例页面获取。 至此,按照上面流程,就可以访问一个页面了。页面的位置在;/usr/share/httpd/noindex/index.html 证书更新步骤 步骤1:找到ssl.conf文件 cd /etc/httpd/conf.d 步骤2: 编辑ssl.conf更新SSLCertificateFile、SSLCertificateKeyFile、SSLCertificateChainFile。 步骤3: 重启Apache。 sudo systemctl restart httpd 通过以上的步骤,把你的网页放到/var/www/html下面,远端就可以访问了。 数据库 暂时用不到,后续再补充。 PHP 暂时用不到,后续再补充。 其他 关于开机自启动 设置应用开机自启动 systemctl enable nginx 查询应用是否开机 systemctl list-unit-files | grep nginx 或者systemctl is-enabled nginx 关闭应用自启动 systemctl disable httpd -
部署MQTT云服务
Centos安装 配置emqx的源 curl -s https://assets.emqx.com/scripts/install-emqx-rpm.sh | sudo bash 安装emqx sudo yum install emqx -y 启动emqx sudo systemctl start emqx 如果要卸载 sudo yum remove emqx 后台配置 访问前,服务端的端口18083权限打开。 截屏2024-12-01 08.53.43 xxx.xxx.xxx:18083 默认登录账号:admin,public。首次登录会要求更新密码。 如果要支持tcp/ssl/ws/wss这几种监听,也需要把相应的端口打开以及服务器端口权限打开。 测试访问 使用mosquitto测试 这里使用的是ubuntu系统上的mosquito_sub/pub工具测试。先安装工具: sudo apt-get install mosquitto-clients 订阅 mosquitto_sub -h www.xxx.xxx -t \"test\" -v 发布 mosquitto_pub -h www.laumy.tech -t \"test\" -m \"hello world\" 使用MQTTX软件测试 下载地址:https://mqttx.app/zh/downloads 建立一个回话连接用于订阅消息 接着对填写订阅主题 再建立一个回话用于发布消息 连接上后进行发布主题 这样订阅的回话就收到订阅消息了 开启SSL/TLS连接 参考:https://docs.emqx.com/zh/emqx/v5.8/network/overview.html 启动异常排查 查找emqx.log cd / find -name \"emqx.log*\" 参考: https://docs.emqx.com/zh/cloud/latest/connect_to_deployments/react_sdk.html https://docs.emqx.com/zh/emqx/v5.8/connect-emqx/developer-guide.html -
coturn安装
安装 yum install coturn -y 配置 vim /etc/coturn/turnserver.conf listening-port=3478 #指定 coturn 监听的端口。默认的 TURN 协议端口是 3478,用于接收 TURN 客户端的连接请求。 tls-listening-port=5349 #指定用于 TLS (传输层安全协议) 加密连接的监听端口。TURN 支持通过 TLS 来增强通信的安全性,默认的端口是 5349。 relay-ip=172.20.65.5 #指定 TURN 服务器用于中继流量的 IP 地址。这个 IP 地址通常是内部的局域网地址,用于数据中继传输。 relay-ip=fe80::216:3eff:fe01:105b #指定 TURN 服务器的 IPv6 地址,通常用于支持 IPv6 网络的环境中。 external-ip=8.134.108.235 #指定TURN服务器的外部公网 IP 地址。当 TURN 服务器位于 NAT 后面时,external-ip 用于告知客户端如何访问该服务器。 relay-threads=50 #指定服务器处理中继流量时使用的线程数量。增加线程数有助于提高并发连接的处理能力。 min-port=40000 max-port=65535 #指定 coturn 使用的最小和最大端口范围。coturn 会在该范围内选择端口来建立临时的媒体连接。这个端口范围通常需要开放,供客户端进行通讯。 server-name=www.laumy.tech #指定 TURN 服务器的名称。这通常用于身份验证或显示给客户端的服务器标识。 lt-cred-mech # 打开这个表示要使用加密,默认是打开的,加密的话就需要验证username和密码,如下的user字段。 no-auth #如果打开这个,表示不需要加密,和lt-cred-mech是二选一。 user=laumy:A8xxxx22 #指定允许连接到 TURN 服务器的用户名和密码。格式为 username:password,用于身份验证。laumy 是用户名,A8xxxx22 是密码。 realm=www.laumy.tech #指定 TURN 服务器的“域”名,用于标识一个身份验证域。这个域通常与 user 结合使用,确保身份验证的正确性。 cert=/etc/xxx/cert/www.laumy.tech.pem pkey=/etc/nginx/cert/www.laumy.tech.key # 设置证书密钥 启停 启动: systemctl start coturn ps -ef |grep turnserver 关闭: systemctl stop coturn 调试 查看启动日志 /var/log/coturn/turnserver.log 测试 https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/ 参考: https://www.zhiboblog.com/2105.html -
密码保护:SSL证书更新
此内容受密码保护。如需查阅,请在下列字段中输入您的密码。 密码: