<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title><![CDATA[老胡的技术博客 - 程序员成长与开发经验分享]]></title> 
<description><![CDATA[程序员博客, 技术分享, 编程学习, 开发经验, 技术成长, 老胡博客, 前端后端技术, 软件开发]]></description>
<link>https://www.vsay.net/</link>
<language>zh-cn</language>
<generator>www.emlog.net</generator>
<item>
    <title>实测：推荐一个大模型API中转站，1元100刀额度，支持GPT5.5/image2/deepseekv4等主流模型，codex/Claude Code/opencode都可用，便宜稳定！</title>
    <link>https://www.vsay.net/mycode/323.html</link>
    <description><![CDATA[<p><a href="https://www.vsay.net/content/uploadfile/202604/thum-cae61777267504.png"><img src="https://www.vsay.net/content/uploadfile/202604/cae61777267504.png" alt="" /></a></p>
<h4>说个实话，这个有点香</h4>
<p><a href="https://sub2api.yuchat.top/register?aff=DTK2Z8VVFGM2" title="点击进入官网">点击进入官网</a></p>
<p>输入我的邀请码更是折上折 DTK2Z8VVFGM2</p>
<blockquote>
<p>1块钱 = 100刀额度<br />
GPT5.5、image2、deepseek v4都能用<br />
codex / Claude Code / opencode 也支持</p>
</blockquote>
<p>我本来以为不稳，结果跑了一段时间还行</p>
<p>想试的自己进去看看 <a href="https://sub2api.yuchat.top/register?aff=DTK2Z8VVFGM2" title="点击进入官网">点击进入官网</a></p>]]></description>
    <pubDate>Mon, 27 Apr 2026 13:19:03 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/323.html</guid>
</item>
<item>
    <title>分享一个牛逼的大模型API整合平台，GPT-4.1、Claude、Gemini 都能免费试？</title>
    <link>https://www.vsay.net/mycode/322.html</link>
    <description><![CDATA[<p><a href="https://www.vsay.net/content/uploadfile/202604/thum-41ae1775023271.png"><img src="https://www.vsay.net/content/uploadfile/202604/41ae1775023271.png" alt="" /></a></p>
<p>最近发现一个还挺实用的网站，叫云雾 API。如果你最近在找 OpenAI API、GPT-4.1 API、Claude API、Gemini API，或者想找一个国内能直接用的 AI API 平台，那这个还挺值得试。现在想用国外那些 AI 接口是真的麻烦，OpenAI、Claude、Gemini 一个比一个难弄，不是要国外手机号，就是要国外卡，还得开代理，最后好不容易注册好了，价格还不便宜。</p>
<p>云雾 API 就是专门解决这个事的。它把 GPT、Claude、Gemini、DeepSeek 这些模型全都放一起了，一个 Key 就能用，不用每家都重新注册。</p>
<p>主要是便宜，很多模型的价格比官方低，平时还能每天签到领余额，拿来试模型、写点小工具，基本不用花钱。</p>
<p>最关键的是，它连 GPT 最新的大模型都能直接用，比如 GPT-4.1、GPT-4o 这些，不用折腾账号，不用绑国外卡，注册完直接调接口。</p>
<p>如果你之前项目已经是 OpenAI 的接口，那更方便，原来：</p>
<pre><code class="language-python">https://api.openai.com/v1</code></pre>
<p>直接改成：</p>
<pre><code class="language-python">https://yunwu.ai/v1</code></pre>
<p>很多代码都不用动。</p>
<p>说白了，它最适合那种想用国外 AI，但是弄不到官方 API，或者嫌官方太贵、太麻烦的人。<br />
免费试用网址：<a href="https://yunwu.ai/register?aff=k0gF">https://yunwu.ai/register?aff=k0gF</a></p>]]></description>
    <pubDate>Wed, 01 Apr 2026 13:55:03 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/322.html</guid>
</item>
<item>
    <title>接着来 Windows 打印机 共享错误 0x000003e3</title>
    <link>https://www.vsay.net/operations/321.html</link>
    <description><![CDATA[<h2>先说0x000003e3解决办法</h2>
<p><strong>“重启打印机服务”</strong></p>
<p>对！你没听错，就是重启打印机服务。</p>
<p>怎么重启呢，首先你要打开服务</p>
<h3>第一步：打开服务面板</h3>
<p>按CTRL+SHIFT+ESC 调出任务管理器<br />
<a href="https://www.vsay.net/content/uploadfile/202601/thum-c5e41769325566.png"><img src="https://www.vsay.net/content/uploadfile/202601/c5e41769325566.png" alt="" /></a></p>
<h3>第二步：找到Print Spooler右击重启服务</h3>
<p><a href="https://www.vsay.net/content/uploadfile/202601/thum-e16f1769325637.png"><img src="https://www.vsay.net/content/uploadfile/202601/e16f1769325637.png" alt="" /></a></p>]]></description>
    <pubDate>Sun, 25 Jan 2026 15:13:19 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/321.html</guid>
</item>
<item>
    <title>StrongSwan 安装及配置 —— 与 TP-Link 路由器建立 IPsec VPN</title>
    <link>https://www.vsay.net/operations/320.html</link>
    <description><![CDATA[<p>简单说明 StrongSwan 是什么、适用场景、为什么要配合路由器做 Site-to-Site 或远程访问 VPN。</p>
<blockquote>
<p>StrongSwan 是一款开源、稳定且支持 IKEv1/IKEv2 的 IPsec VPN 解决方案。<br />
在企业或家庭网络中，我们常常希望通过公网安全地连接不同的局域网，例如云服务器与家用路由器互通。<br />
本文将演示如何在 <strong>Ubuntu 服务器上安装 StrongSwan</strong>，并与 <strong>TP-Link 路由器</strong> 建立 IPsec 隧道，实现内网互访。</p>
</blockquote>
<hr />
<h3>二、实验环境</h3>
<table>
<thead>
<tr>
<th>设备</th>
<th>角色</th>
<th>公网 IP</th>
<th>内网网段</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ubuntu 云服务器</td>
<td>StrongSwan 网关</td>
<td>1.1.1.1</td>
<td>10.0.0.0/24</td>
<td>SiteA</td>
</tr>
<tr>
<td>TP-Link 路由器</td>
<td>IPsec VPN 客户端</td>
<td>动态公网 IP</td>
<td>192.168.1.0/24</td>
<td>SiteB</td>
</tr>
</tbody>
</table>
<blockquote>
<p>目标：1.1.1.1 能访问 192.168.1.x，反之亦然。</p>
</blockquote>
<hr />
<h3>三、安装 StrongSwan</h3>
<pre><code class="language-bash">sudo apt update
sudo apt install strongswan -y</code></pre>
<p>安装完成后会生成主要配置文件：</p>
<ul>
<li><code>/etc/ipsec.conf</code> — 主配置文件</li>
<li><code>/etc/ipsec.secrets</code> — 存储密钥和凭证</li>
</ul>
<hr />
<h3>四、配置 IPsec</h3>
<h4>1. 编辑 <code>/etc/ipsec.conf</code></h4>
<pre><code class="language-bash">sudo nano /etc/ipsec.conf</code></pre>
<p>示例配置：</p>
<pre><code class="language-conf">config setup
    uniqueids=no
    charondebug="ike 2, esp 2"  # 开启日志便于排查

conn aliyun
    authby=secret  # 预共享密钥认证（正确）
    auto=add  # 启动时加载连接
    left=%defaultroute      # 或 left=172.21.97.169
    leftid=1.1.1.1  # 服务器身份标识（与路由器对端网关一致）
    leftsubnet=0.0.0.0/0  # 云端子网（全流量转发，正确）
    right=%any  # 路由器公网IP（动态IP填%any，固定IP填实际公网IP）
    rightsubnet=192.168.1.0/24  # 本地目标IP（正确）

    # 加密套件（建议与路由器同步，若路由器支持则用更强的）
    ike=3des-md5-modp1024!  # 替换3des-md5（优先），若不支持再改回3des-md5-modp1024
    esp=3des-md5!  # 同上，不支持则用3des-md5

    keyexchange=ikev1  # 与路由器IKE版本一致
    aggressive=no  # 关闭野蛮模式（优先用主模式，兼容性更好）
    # aggressive=yes  # 若路由器强制要求野蛮模式，再开启

    # DPD检测（优化重连）
    dpdaction=restart  # 检测到断开后重连（原clear是关闭，不合理）
    dpddelay=30s
    dpdtimeout=120s</code></pre>
<blockquote>
<p>注意：TP-Link 一般只支持 IKEv1，且加密方式需与其 Web 配置一致。</p>
</blockquote>
<hr />
<h4>2. 编辑 <code>/etc/ipsec.secrets</code></h4>
<pre><code class="language-bash">sudo nano /etc/ipsec.secrets</code></pre>
<p>内容示例：</p>
<pre><code> : PSK "123456789"</code></pre>
<blockquote>
<p><code>PSK</code> 就是路由器里配置的预共享密钥。</p>
</blockquote>
<hr />
<h3>五、TP-Link 路由器端配置</h3>
<p>在路由器管理页面（通常是 <code>192.168.1.1</code>）中：</p>
<ol>
<li>打开 <strong>高级设置 → VPN → IPsec VPN</strong></li>
<li>
<p>新建连接：</p>
<ul>
<li><strong>模式</strong>：主模式 (Main Mode)</li>
<li><strong>远程网关</strong>：1.1.1.1（服务器公网）</li>
<li><strong>本地网段</strong>：192.168.1.0/24</li>
<li><strong>远程网段</strong>：10.0.0.0/24</li>
<li><strong>预共享密钥</strong>：123456789</li>
</ul>
</li>
</ol>
<p>保存后点击“连接”。</p>
<hr />
<h3>六、启动与调试</h3>
<h4>启动 StrongSwan</h4>
<pre><code class="language-bash">sudo ipsec restart
sudo ipsec status</code></pre>
<h4>查看连接状态</h4>
<pre><code class="language-bash">sudo ipsec statusall</code></pre>
<p>如果出现：</p>
<pre><code>INSTALLED, TUNNEL, ESP in UDP SPIs: ...</code></pre>
<p>说明隧道已成功建立。</p>
<hr />
<h3>七、验证通信</h3>
<p>在 Ubuntu 端：</p>
<pre><code class="language-bash">ping 192.168.1.1</code></pre>
<p>在 TP-Link LAN 设备中：</p>
<pre><code class="language-bash">ping 10.0.0.1</code></pre>
<p>若能互通，则配置成功。</p>
<hr />
<h3>常见问题</h3>
<table>
<thead>
<tr>
<th>问题</th>
<th>可能原因</th>
<th>解决方案</th>
</tr>
</thead>
<tbody>
<tr>
<td>隧道不建立</td>
<td>PSK 或 ID 不一致</td>
<td>检查 <code>ipsec.secrets</code> 与路由器设置</td>
</tr>
<tr>
<td>只能单向通信</td>
<td>路由未添加</td>
<td>在两端手动添加 <code>ip route</code> 指向隧道接口</td>
</tr>
<tr>
<td>路由器连上但无数据</td>
<td>NAT/防火墙限制</td>
<td>在云服务器上允许 UDP 500/4500 端口</td>
</tr>
<tr>
<td>日志无效</td>
<td>charon 未输出</td>
<td>修改 <code>charondebug</code> 级别为 <code>ike 2, knl 2, cfg 2</code></td>
</tr>
</tbody>
</table>
<hr />
<p>通过本文配置，我们成功让 <strong>云端 Ubuntu StrongSwan</strong> 与 <strong>TP-Link 路由器</strong> 建立了一个稳定的 <strong>IPsec Site-to-Site VPN</strong>，实现了不同局域网之间的透明互通。</p>]]></description>
    <pubDate>Wed, 29 Oct 2025 17:47:43 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/320.html</guid>
</item>
<item>
    <title>数据磁盘满了后增加新硬盘扩展原硬盘软链接(mklink)方案</title>
    <link>https://www.vsay.net/operations/318.html</link>
    <description><![CDATA[<h2>一、背景：数据库突然写不进数据了</h2>
<p>有没有遇到这种场景：</p>
<p>某天数据库突然异常，发现是<strong>磁盘空间不足</strong>。<br />
再一看，数据库的数据目录或日志目录（比如 <code>D:\Database\Data</code>）已经塞满了，而你只有另一个磁盘（比如 <code>E:</code>）还剩大量空间。</p>
<p>如果你正面临以下限制：</p>
<ul>
<li>当前项目已上线，不能修改数据库配置路径</li>
<li>数据文件庞大，迁移花时间</li>
<li>其他磁盘有空间，但数据库程序只认原路径</li>
</ul>
<p>别急，一条命令就能解决 —— <code>mklink</code>。</p>
<hr />
<h2>二、mklink 是什么？</h2>
<p><code>mklink</code> 是 Windows 自带的命令行工具，可以创建软链接（符号链接）。</p>
<p>你可以理解为：<strong>创建一个假的文件夹/文件，其实内部指向了另一个路径</strong>。</p>
<p>程序会以为你还在用原来的 <code>D:\Database\Data</code>，但实际上你已经把数据存到了 <code>E:</code>。</p>
<hr />
<h2>三、实战案例：释放数据库空间</h2>
<p>假设数据库的数据目录在：</p>
<pre><code>D:\Database\Data</code></pre>
<h3>步骤 1：关闭数据库服务</h3>
<p>先停掉数据库服务，防止读写冲突：</p>
<pre><code class="language-bash">net stop mysql   # 示例，具体看你的数据库服务名</code></pre>
<hr />
<h3>步骤 2：移动数据目录到新磁盘</h3>
<pre><code class="language-bash">move D:\Database\Data E:\DatabaseData</code></pre>
<p>此时，<code>D:\Database\Data</code> 这个原始路径已经空了。</p>
<hr />
<h3>步骤 3：使用 mklink 创建目录符号链接</h3>
<pre><code class="language-bash">mklink /D D:\Database\Data E:\DatabaseData</code></pre>
<p>执行后，<code>D:\Database\Data</code> 会变成一个“伪装门口”，实则内部已经跳转到 <code>E:\DatabaseData</code>。</p>
<hr />
<h3>步骤 4：重启数据库服务</h3>
<pre><code class="language-bash">net start mysql</code></pre>
<p>程序和服务丝毫不知情，但你已经巧妙地把数据搬了家。</p>
<hr />
<h2>四、这种做法有什么好处？</h2>
<p>✅ <strong>零配置变更</strong>：不需要改数据库配置文件<br />
✅ <strong>快速见效</strong>：几分钟就能完成整个搬迁<br />
✅ <strong>安全可靠</strong>：不影响服务启动、数据访问<br />
✅ <strong>节省磁盘空间</strong>：彻底解决“原盘空间打爆”的问题</p>
<hr />
<h2>五、适用于哪些数据库？</h2>
<p>这方法适用于<strong>大多数部署在 Windows 上的数据库</strong>：</p>
<ul>
<li>MySQL / MariaDB</li>
<li>SQL Server</li>
<li>MongoDB</li>
<li>PostgreSQL</li>
<li>SQLite（单文件）</li>
<li>甚至本地开发用的轻量级数据库或缓存服务</li>
</ul>
<hr />
<h2>六、一些注意事项</h2>
<table>
<thead>
<tr>
<th>问题</th>
<th>解决方案</th>
</tr>
</thead>
<tbody>
<tr>
<td>无法创建链接？</td>
<td>请用「<strong>管理员身份</strong>」运行 CMD</td>
</tr>
<tr>
<td>移动数据失败？</td>
<td>确保数据库服务已完全关闭</td>
</tr>
<tr>
<td>怎么知道链接成功？</td>
<td>用 <code>dir</code> 命令查看目录类型，应该是 <code>&lt;SYMLINKD&gt;</code></td>
</tr>
<tr>
<td>删除链接会删数据吗？</td>
<td>删除链接本身不会删除目标文件夹数据</td>
</tr>
</tbody>
</table>
<hr />
<h2>七、拓展场景</h2>
<p>你也可以用 <code>mklink</code> 做：</p>
<ul>
<li>日志文件夹转移</li>
<li>数据备份目录链接</li>
<li>多环境共用配置</li>
<li>跨盘共享数据资源</li>
</ul>
<hr />
<h2>八、总结</h2>
<p>磁盘满了不是末日，不一定非得删数据、扩容或重建路径。<br />
在 Windows 下，<code>mklink</code> 是一个<strong>老派但实用的武器</strong>，能帮你优雅地处理这种临时性资源瓶颈问题。</p>
<p>只需一条命令，你就能把数据“搬走”，而系统、程序、服务<strong>完全不知情</strong>。</p>
<hr />
<h2>附：mklink 命令语法速查</h2>
<pre><code class="language-bash">mklink            # 创建文件软链接
mklink /D         # 创建目录软链接（最常用）
mklink /J         # 创建目录联接（Junction，速度更快）</code></pre>
<table>
<thead>
<tr>
<th>特性</th>
<th><code>mklink /D</code>（目录符号链接）</th>
<th><code>mklink /J</code>（目录联接 Junction）</th>
</tr>
</thead>
<tbody>
<tr>
<td>支持跨分区</td>
<td>✅ 可以指向不同盘符（如 C: → D:）</td>
<td>✅ 可以指向不同盘符</td>
</tr>
<tr>
<td>是否为符号链接（软链接）</td>
<td>✅ 是</td>
<td>❌ 不是（是一种 NTFS 的“挂载”机制）</td>
</tr>
<tr>
<td>是否被识别为链接</td>
<td>✅ 是符号链接，部分程序能识别</td>
<td>❌ 在大多数程序眼中就是普通目录</td>
</tr>
<tr>
<td>需要 NTFS 文件系统</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>权限需求（Win10+）</td>
<td><strong>需要管理员权限或启用开发者模式</strong></td>
<td>管理员权限即可</td>
</tr>
<tr>
<td>支持远程路径</td>
<td>✅ 可以链接网络路径 \server\dir</td>
<td>❌ 不支持网络路径</td>
</tr>
<tr>
<td>被删除目录的行为</td>
<td>链接仍在，但会提示“目标不存在”</td>
<td>链接仍在，但访问会报错</td>
</tr>
<tr>
<td>推荐用途</td>
<td>更像 Linux 的软链接，适合跨设备、备份、数据复用等</td>
<td>更快更兼容，适合软件目录重定向、大型数据挂载</td>
</tr>
</tbody>
</table>]]></description>
    <pubDate>Wed, 16 Jul 2025 15:11:02 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/318.html</guid>
</item>
<item>
    <title>奇葩问题解决 Windows 共享错误 0x80004005</title>
    <link>https://www.vsay.net/operations/317.html</link>
    <description><![CDATA[<p>今天发现一个奇怪的问题，Win11共享出现0x80004005，最终费时半个小时解决了<br />
下面给出解决办法：</p>
<hr />
<h2>解决 Windows 共享错误 0x80004005 的两大关键点</h2>
<p>访问局域网共享文件夹时，遇到 <code>错误代码：0x80004005</code>（未指定错误）？常见原因主要集中在两点：<strong>凭据问题</strong> 和 <strong>SMB 协议不兼容</strong>。</p>
<hr />
<h3>✅ 1. 清除错误的凭据并添加凭据</h3>
<p>Windows 有时会缓存错误的共享账号密码，导致后续访问失败。主要还是缓存惹的</p>
<p><strong>操作步骤：</strong></p>
<ol>
<li>打开 <code>控制面板 → 用户账户 → 凭据管理器</code></li>
</ol>
<p><a href="https://www.vsay.net/content/uploadfile/202507/thum-d8511751865769.png"><img src="https://www.vsay.net/content/uploadfile/202507/d8511751865769.png" alt="" /></a></p>
<ol start="2">
<li>
<p>进入 “Windows 凭据” 选项卡<br />
<a href="https://www.vsay.net/content/uploadfile/202507/thum-ed431751865819.png"><img src="https://www.vsay.net/content/uploadfile/202507/ed431751865819.png" alt="" /></a></p>
</li>
<li>
<p>删除对应的凭据，如果没有就别删了，不放心就全删了也行。</p>
</li>
</ol>
<p><a href="https://www.vsay.net/content/uploadfile/202507/thum-7f0a1751865883.png"><img src="https://www.vsay.net/content/uploadfile/202507/7f0a1751865883.png" alt="" /></a></p>
<ol start="4">
<li>重新添加共享主机的ip</li>
</ol>
<p><a href="https://www.vsay.net/content/uploadfile/202507/thum-fe241751865920.png"><img src="https://www.vsay.net/content/uploadfile/202507/fe241751865920.png" alt="" /></a></p>
<ol start="5">
<li>重新访问共享主机即可。</li>
</ol>
<hr />
<h3>✅ 2. 启用 SMB1.0 协议（兼容旧设备）</h3>
<p>一些老NAS、老打印服务器仍使用 SMB1 协议，Win10/11 默认未开启。</p>
<p><strong>开启方法：</strong></p>
<ol>
<li>打开“启用或关闭 Windows 功能”</li>
<li>勾选：<code>SMB 1.0/CIFS 文件共享支持</code></li>
<li>重启电脑</li>
</ol>
<p><a href="https://www.vsay.net/content/uploadfile/202507/7fcd1751865976.png"><img src="https://www.vsay.net/content/uploadfile/202507/7fcd1751865976.png" alt="" /></a></p>
<hr />
<h3>🔚 结束</h3>
<p><code>0x80004005</code> 虽然提示模糊，但大多数共享访问问题都可以通过这两步解决。如果还有问题，可以尝试映射网络驱动器或检查目标设备的共享权限。</p>]]></description>
    <pubDate>Mon, 07 Jul 2025 13:17:36 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/317.html</guid>
</item>
<item>
    <title>共享打印机连接错误提示错误代码 0x0000000a 解决方法</title>
    <link>https://www.vsay.net/operations/316.html</link>
    <description><![CDATA[<h3>打印机共享出现0x0000000a的解决方法</h3>
<h3>问题分析</h3>
<p>出现0x0000000a错误是因为主机没有打开Windows Remote Management服务导致的。</p>
<h3>解决方法</h3>
<p>在共享主机上打开服务，开启Windows Remote Management服务<br />
<a href="https://www.vsay.net/content/uploadfile/202505/thum-86871748671500.png"><img src="https://www.vsay.net/content/uploadfile/202505/86871748671500.png" alt="" /></a></p>]]></description>
    <pubDate>Sat, 31 May 2025 14:02:09 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/316.html</guid>
</item>
<item>
    <title>edge浏览器google浏览器显示由该组织管理由你的组织管理解决方法</title>
    <link>https://www.vsay.net/operations/315.html</link>
    <description><![CDATA[<h3>问题描述</h3>
<p>最近老能看到有浏览器出现 由该组织管理 的情况<br />
出现的问题就是无法修改浏览器的设置<br />
偶尔还有导致浏览器无法上网。。。</p>
<h3>解决方法</h3>
<h4>1.按 win + R 调出运行窗口，输入regedit，确定，打开注册表编辑器</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202505/c5001747804140.png"><img src="https://www.vsay.net/content/uploadfile/202505/c5001747804140.png" alt="" /></a></p>
<h4>2.找到路径 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft 右击删除里面的 edge 和 Google 字样的文件夹</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202505/thum-aa691747804335.png"><img src="https://www.vsay.net/content/uploadfile/202505/aa691747804335.png" alt="" /></a></p>
<h4>3.重启浏览器或电脑后生效</h4>]]></description>
    <pubDate>Wed, 21 May 2025 13:06:09 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/315.html</guid>
</item>
<item>
    <title>js 通过坐标计算两个坐标之间的距离</title>
    <link>https://www.vsay.net/web/314.html</link>
    <description><![CDATA[<h3>Haversine 公式计算两点之间的大圆距离（地球曲面距离）</h3>
<p>直接上代码了</p>
<h4>以米为单位</h4>
<pre><code class="language-javascript">function calculateDistance(lat1, lng1, lat2, lng2) {
  const R = 6371000; // 地球半径，单位为米
  const toRad = x =&gt; x * Math.PI / 180;

  const dLat = toRad(lat2 - lat1);
  const dLng = toRad(lng2 - lng1);
  const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
            Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
            Math.sin(dLng / 2) * Math.sin(dLng / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  const distance = R * c;

  console.log('两点距离:', distance, '米');
  return distance;
}</code></pre>
<h4>以公里为单位</h4>
<pre><code class="language-javascript">function calculateDistance(lat1, lng1, lat2, lng2) {
  const R = 6371; // 地球半径，单位：km
  const toRad = x =&gt; x * Math.PI / 180;

  const dLat = toRad(lat2 - lat1);
  const dLng = toRad(lng2 - lng1);
  const a = Math.sin(dLat / 2) ** 2 +
            Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
            Math.sin(dLng / 2) ** 2;
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  const distance = R * c; // 单位：km

  console.log('两点距离:', distance.toFixed(2), '公里');
  return distance;
}</code></pre>
<h3>示例：用户与天安门的距离</h3>
<pre><code class="language-javascript">const targetLat = 39.9087;
const targetLng = 116.3975;

function calculateToTiananmen(lat, lng) {
  const km = calculateDistance(lat, lng, targetLat, targetLng);
  alert(`您距离天安门还有 ${km.toFixed(2)} 公里`);
}</code></pre>
<p>如需四舍五入或保留 1-2 位小数，使用 .toFixed(2)。</p>]]></description>
    <pubDate>Tue, 20 May 2025 14:18:25 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/314.html</guid>
</item>
<item>
    <title>分享一个微信朋友圈点赞秒赞软件，无需下载一秒几千赞</title>
    <link>https://www.vsay.net/mycode/313.html</link>
    <description><![CDATA[<h1>先看图</h1>
<p><a href="https://www.vsay.net/content/uploadfile/202504/thum-73dc1744854238.png"><img src="https://www.vsay.net/content/uploadfile/202504/thum-73dc1744854238.png" alt="" /></a></p>
<h1>网址</h1>
<p><a href="https://pyq.vsay.net" title="https://pyq.vsay.net">https://pyq.vsay.net</a></p>
<h1>编辑动态内容</h1>
<ol>
<li>
<p>点击相应位置修改你的信息，如：昵称、头像、动态内容、图片、位置、视频号（如果需要）、发布时间等<br />
<a href="https://www.vsay.net/content/uploadfile/202504/263c1744854460.png"><img src="https://www.vsay.net/content/uploadfile/202504/263c1744854460.png" alt="" /></a></p>
</li>
<li>
<p>图片或者链接<br />
<a href="https://www.vsay.net/content/uploadfile/202504/f6341744854472.png"><img src="https://www.vsay.net/content/uploadfile/202504/f6341744854472.png" alt="" /></a></p>
</li>
</ol>
<p>2.1 链接也是可以搞定的呢<br />
<a href="https://www.vsay.net/content/uploadfile/202504/5b371744854491.png"><img src="https://www.vsay.net/content/uploadfile/202504/5b371744854491.png" alt="" /></a></p>
<h1>一键生成点赞评论</h1>
<ol>
<li>
<p>随机生成，点击点赞按钮，随机生成只需要填一个数字，填多少生成多少赞。<br />
<a href="https://www.vsay.net/content/uploadfile/202504/cf081744854518.png"><img src="https://www.vsay.net/content/uploadfile/202504/cf081744854518.png" alt="" /></a></p>
</li>
<li>
<p>自定义生成<br />
当然你想在最前面添加几个认识的小伙伴也是可以在自定义里面做到<br />
<a href="https://www.vsay.net/content/uploadfile/202504/0a571744854536.png"><img src="https://www.vsay.net/content/uploadfile/202504/0a571744854536.png" alt="" /></a></p>
</li>
</ol>
<h1>点击预览下载查看</h1>
<p><a href="https://www.vsay.net/content/uploadfile/202504/646d1744854556.png"><img src="https://www.vsay.net/content/uploadfile/202504/646d1744854556.png" alt="" /></a></p>
<p>这个界面您可以调整截图显示方式，包括：<br />
• 截图显示的机型（安卓/苹果）<br />
• 截图是否是深色模式<br />
• 手机状态栏的图标和时间或者网络状态<br />
• 截图方式（朋友圈内/照片/详情）<br />
• 截长图还是分页截<br />
• 装逼模式下的炫彩朋友圈截图只有你想不到，没有你装不到的，不信你看图：<br />
<a href="https://www.vsay.net/content/uploadfile/202504/thum-b23d1744854588.png"><img src="https://www.vsay.net/content/uploadfile/202504/b23d1744854588.png" alt="" /></a></p>
<h1>保存下载</h1>
<p>长按截图保存到相册, 电脑端右击保存图片<br />
<a href="https://www.vsay.net/content/uploadfile/202504/b6201744854604.png"><img src="https://www.vsay.net/content/uploadfile/202504/b6201744854604.png" alt="" /></a></p>]]></description>
    <pubDate>Thu, 17 Apr 2025 09:42:28 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/313.html</guid>
</item>
<item>
    <title>免费SSL证书、自动化续签证书，acme.sh 完整教程：安装、使用、常见错误及解决方案</title>
    <link>https://www.vsay.net/operations/312.html</link>
    <description><![CDATA[<h2>1. 什么是 acme.sh？</h2>
<p><code>acme.sh</code> 是一个 ACME 客户端，用于申请、安装和自动更新 SSL/TLS 证书，例如 Let’s Encrypt 和 ZeroSSL 证书。相比 Certbot，<code>acme.sh</code> 更轻量、依赖少、支持多种 CA 及自动化功能。</p>
<h2>2. acme.sh 的特点</h2>
<ul>
<li><strong>纯 Shell 实现</strong>，无 Python 或 OpenSSL 依赖。</li>
<li><strong>支持多种 CA</strong>：默认使用 Let’s Encrypt，也支持 ZeroSSL、Buypass、SSL.com 等。</li>
<li><strong>多种验证方式</strong>：HTTP、Standalone、DNS API。</li>
<li><strong>支持通配符证书</strong>（<code>*.example.com</code>）。</li>
<li><strong>自动续期</strong>，无需手动更新证书。</li>
<li><strong>灵活部署</strong>，可自动安装证书到 Nginx、Apache、HAProxy 等。</li>
</ul>
<hr />
<h2>3. 国内安装 acme.sh</h2>
<h3>3.1. 使用国内镜像安装（推荐）</h3>
<p>由于国内访问 GitHub 可能较慢，可以使用 Gitee 镜像安装：</p>
<pre><code class="language-bash">curl https://gitee.com/neilpang/acme.sh/raw/master/acme.sh | sh</code></pre>
<p>安装完成后，acme.sh 位于 <code>~/.acme.sh/</code> 目录。</p>
<h3>3.2. 重新加载 Shell 以生效</h3>
<pre><code class="language-bash">source ~/.bashrc  # 或者 source ~/.zshrc</code></pre>
<h3>3.3. 验证安装</h3>
<pre><code class="language-bash">acme.sh --version</code></pre>
<hr />
<h2>4. 申请 SSL 证书</h2>
<p>acme.sh 提供多种方式申请证书。</p>
<h3>4.1. 使用 Webroot 方式（适用于已运行的 Web 服务器）</h3>
<pre><code class="language-bash">acme.sh --issue -d example.com -d www.example.com --webroot /var/www/html</code></pre>
<h3>4.2. 使用 Standalone 方式（适用于无 Web 服务器的情况）</h3>
<pre><code class="language-bash">acme.sh --issue -d example.com --standalone</code></pre>
<blockquote>
<p><strong>注意</strong>：需要确保 80 端口未被占用。</p>
</blockquote>
<h3>4.3. 使用 DNS API 方式（适用于通配符证书）</h3>
<pre><code class="language-bash">export Ali_Key="你的阿里云 AccessKey ID"
export Ali_Secret="你的阿里云 AccessKey Secret"
acme.sh --issue -d example.com -d "*.example.com" --dns dns_ali</code></pre>
<blockquote>
<p><strong>支持的 DNS API</strong>：阿里云（dns_ali）、腾讯云（dns_dp）、Cloudflare（dns_cf）等。</p>
</blockquote>
<hr />
<h2>5. 安装证书</h2>
<pre><code class="language-bash">acme.sh --install-cert -d example.com \
  --key-file /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.crt \
  --reloadcmd "systemctl reload nginx"</code></pre>
<hr />
<h2>6. 证书续期</h2>
<p>acme.sh 会自动续期证书，也可手动执行：</p>
<pre><code class="language-bash">acme.sh --renew -d example.com</code></pre>
<p>或者续期所有证书：</p>
<pre><code class="language-bash">acme.sh --renew-all</code></pre>
<h3>6.1. 设置自动续期（默认已启用）</h3>
<p>acme.sh 默认会自动续期证书，但如果需要手动确认，可以执行：</p>
<pre><code class="language-bash">acme.sh --cron --home ~/.acme.sh</code></pre>
<p>此外，可以在 <code>crontab</code> 中检查是否存在 acme.sh 的自动任务：</p>
<pre><code class="language-bash">crontab -l</code></pre>
<p>如果不存在，可手动添加（每天 0 点执行）：</p>
<pre><code class="language-bash">echo "0 0 * * * ~/.acme.sh/acme.sh --cron --home ~/.acme.sh &gt; /dev/null" | crontab -</code></pre>
<hr />
<h2>7. 解决常见错误</h2>
<h3>7.1. ZeroSSL 需要注册账户</h3>
<p><strong>错误信息：</strong></p>
<pre><code>[Tue Apr  1 09:49:07 CST 2025] No EAB credentials found for ZeroSSL, let's obtain them
[Tue Apr  1 09:49:07 CST 2025] Please update your account with an email address first.</code></pre>
<p><strong>解决方案：</strong></p>
<pre><code class="language-bash">acme.sh --register-account -m my@example.com</code></pre>
<h3>7.2. Let’s Encrypt 速率限制（Rate Limit）</h3>
<p><strong>错误信息：</strong></p>
<pre><code>[Tue Apr  1 09:51:14 CST 2025] Error creating new order. Le_OrderFinalize not found.
"type": "urn:ietf:params:acme:error:rateLimited",</code></pre>
<p><strong>解决方案：</strong></p>
<ul>
<li>等待 7 天后再尝试。</li>
<li>使用已有证书 <code>acme.sh --install-cert</code>。</li>
<li>改用 ZeroSSL <code>acme.sh --set-default-ca --server zerossl</code>。</li>
<li>测试时使用 Let’s Encrypt <code>staging</code> 服务器。</li>
</ul>
<h3>7.3. Nginx 端口占用问题</h3>
<p><strong>错误信息：</strong></p>
<pre><code>acme.sh --issue --standalone 失败</code></pre>
<p><strong>解决方案：</strong></p>
<pre><code class="language-bash">systemctl stop nginx
acme.sh --issue -d example.com --standalone
systemctl start nginx</code></pre>
<hr />
<h2>8. 切换 CA</h2>
<h3>8.1. 切换到 Let’s Encrypt</h3>
<pre><code class="language-bash">acme.sh --set-default-ca --server letsencrypt</code></pre>
<h3>8.2. 切换到 ZeroSSL</h3>
<pre><code class="language-bash">acme.sh --set-default-ca --server zerossl
acme.sh --register-account -m my@example.com</code></pre>
<hr />
<h2>9. 卸载 acme.sh</h2>
<pre><code class="language-bash">acme.sh --uninstall
rm -rf ~/.acme.sh/</code></pre>
<hr />
<h2>10. 总结</h2>
<ul>
<li><code>acme.sh</code> 是一个轻量级 ACME 客户端，适用于 Let’s Encrypt 和 ZeroSSL 证书。</li>
<li>支持 Webroot、Standalone、DNS API 验证方式。</li>
<li>证书默认存储在 <code>~/.acme.sh/</code> 目录，需要手动安装到 Web 服务器。</li>
<li>自动续期，并提供详细的错误日志。</li>
<li>可切换 CA，避免 Let’s Encrypt 速率限制。</li>
</ul>]]></description>
    <pubDate>Tue, 01 Apr 2025 10:43:16 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/312.html</guid>
</item>
<item>
    <title>关于HP打印机在WIN7上打印窗口无响应的解决办法</title>
    <link>https://www.vsay.net/operations/311.html</link>
    <description><![CDATA[<p>Windows 7 安装 HP 打印机驱动后，测试打印时 <strong>Windows 资源管理器（explorer.exe）崩溃</strong>，或者打印表格时表格也无响应，可能是由于以下几种原因导致的：  </p>
<h3><strong>原因</strong></h3>
<ol>
<li>
<p><strong>HP 打印机驱动兼容性问题（这个简单换驱动即可，如果你认为你的驱动没问题那么往下看）</strong>  </p>
<ul>
<li>你安装的驱动可能不完全兼容 Windows 7，导致崩溃。  </li>
<li>可能是 32 位和 64 位驱动版本不匹配。  </li>
</ul>
</li>
<li>
<p><strong>系统文件损坏或更新导致的兼容性问题（这个很有可能，但是嘛WIN7已经让微软放弃了，更新就算了）</strong>  </p>
<ul>
<li>Windows 7 本身可能缺少某些更新补丁，导致 HP 驱动无法正确运行。  </li>
</ul>
</li>
<li>
<p><strong>以前有装过相似的驱动程序，导致两个版本冲突（大多数是这原因啊）</strong>  </p>
<ul>
<li>假设你在一台电脑上装了两个HP的驱动，就会导致冲突。。。这可能是WIN7的问题吧。</li>
</ul>
</li>
</ol>
<h3><strong>来说说的的解决方法吧</strong></h3>
<h4><strong>1：卸载并重新安装打印机驱动，必须！！！</strong></h4>
<ol>
<li><strong>完全卸载 HP 打印机驱动</strong>
<ul>
<li><strong>打开“设备和打印机”</strong>（Win + R → 输入 <code>control printers</code> → 回车）。</li>
<li>右键点击你的 HP 打印机 → <strong>删除设备</strong>。</li>
<li>按 Win + R，输入 <code>printmanagement.msc</code>，删除所有与 HP 相关的打印驱动程序。</li>
<li>进入 <code>C:\Windows\System32\spool\drivers\x64\3\</code>，删除 HP 相关文件。</li>
</ul></li>
</ol>
<h4><strong>2：删除残留的驱动文件</strong></h4>
<ol>
<li>按 <strong>Win + R</strong>，输入 <code>services.msc</code>，回车。</li>
<li>找到 <strong>Print Spooler</strong>，右键点击 → <strong>停止</strong>。</li>
<li>进入 <code>C:\Windows\System32\spool\PRINTERS\</code>，删除里面所有文件。</li>
<li>再次启动 <strong>Print Spooler</strong> 服务。</li>
</ol>
<h4>**3：删除注册表信息</h4>
<ol>
<li>按 <strong>Win + R</strong>，输入 <code>regedit</code>，回车。</li>
<li>找到<code>\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows (32位NT x86,64位x64)\Drivers\Version-3</code></li>
<li>删除该项下的所有子项，如果没有就算了。<br />
<a href="https://www.vsay.net/content/uploadfile/202503/44581742782276.png"><img src="https://www.vsay.net/content/uploadfile/202503/44581742782276.png" alt="" /></a></li>
</ol>
<h4>最后重启电脑！！！</h4>
<h4>重新安装驱动，完美解决</h4>]]></description>
    <pubDate>Mon, 24 Mar 2025 10:00:41 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/311.html</guid>
</item>
<item>
    <title>金蝶发票助手打开提示:本功能基于跨技术平台的嵌套方式实现，对正浏览器版本和兼容模式有要求。解决</title>
    <link>https://www.vsay.net/operations/310.html</link>
    <description><![CDATA[<p>蛐蛐一下：金蝶到2025年了，还不愿意集成谷歌内核。</p>
<p><a href="https://www.vsay.net/content/uploadfile/202503/thum-f7441741231246.png"><img src="https://www.vsay.net/content/uploadfile/202503/f7441741231246.png" alt="" /></a></p>
<h3>解决办法</h3>
<h4>打开Internet属性</h4>
<ol>
<li>
<p>WIN+R键调出运行，输入control，打开控制面板，当然你有更好的方法请使用自己的方式打开控制面板<br />
<a href="https://www.vsay.net/content/uploadfile/202503/0b451741231755.png"><img src="https://www.vsay.net/content/uploadfile/202503/0b451741231755.png" alt="" /></a></p>
</li>
<li>
<p>点击网络和Internet，打开Internet选项<br />
<a href="https://www.vsay.net/content/uploadfile/202503/thum-a0881741231819.png"><img src="https://www.vsay.net/content/uploadfile/202503/a0881741231819.png" alt="" /></a><br />
<a href="https://www.vsay.net/content/uploadfile/202503/thum-7cd71741231869.png"><img src="https://www.vsay.net/content/uploadfile/202503/7cd71741231869.png" alt="" /></a></p>
</li>
</ol>
<p>3.根据下面步骤，将网址 <a href="https://kingdee.wit-parking.com">https://kingdee.wit-parking.com</a> 添加到信任列表中<br />
<a href="https://www.vsay.net/content/uploadfile/202503/thum-df531741232085.png"><img src="https://www.vsay.net/content/uploadfile/202503/df531741232085.png" alt="" /></a></p>
<p>如下（然后点关闭）：<br />
<a href="https://www.vsay.net/content/uploadfile/202503/thum-69b51741232146.png"><img src="https://www.vsay.net/content/uploadfile/202503/69b51741232146.png" alt="" /></a></p>
<p>4.选择自定义级别，将里面所有禁用和询问的都改成启用。<br />
<a href="https://www.vsay.net/content/uploadfile/202503/thum-b67c1741232183.png"><img src="https://www.vsay.net/content/uploadfile/202503/b67c1741232183.png" alt="" /></a></p>
<p><a href="https://www.vsay.net/content/uploadfile/202503/38e11741232289.png"><img src="https://www.vsay.net/content/uploadfile/202503/38e11741232289.png" alt="" /></a></p>
<p>5.点击确定-确定<br />
<a href="https://www.vsay.net/content/uploadfile/202503/thum-9bde1741232324.png"><img src="https://www.vsay.net/content/uploadfile/202503/9bde1741232324.png" alt="" /></a></p>
<p>6.重启金蝶问题解决！</p>]]></description>
    <pubDate>Thu, 06 Mar 2025 11:19:29 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/310.html</guid>
</item>
<item>
    <title>js将字符串数字数组转换成uint8Array数组，用于mcu通讯指令。</title>
    <link>https://www.vsay.net/web/309.html</link>
    <description><![CDATA[<h4>将字符串数字数组转换成uint8Array</h4>
<pre><code class="language-javascript">function toUint8Array(data) {
    let buffer;
    if (typeof data === 'string') {
        const textEncoder = new TextEncoder();
        buffer = textEncoder.encode(data).buffer;
    } else if (typeof data === 'number') {
        let byteLength;
        if (data &gt;= 0 &amp;&amp; data &lt;= 255) { // 8位数字
            byteLength = 1;
        } else if (data &gt;= 0 &amp;&amp; data &lt;= 65535) { // 16位数字
            byteLength = 2;
        } else if (data &gt;= 0 &amp;&amp; data &lt;= 4294967295) { // 32位数字
            byteLength = 4;
        } else { // 64位数字
            byteLength = 8;
        }
        buffer = new ArrayBuffer(byteLength);
        const dataView = new DataView(buffer);
        if (byteLength === 1) {
            dataView.setUint8(0, data);
        } else if (byteLength === 2) {
            dataView.setUint16(0, data, false);
        } else if (byteLength === 4) {
            dataView.setUint32(0, data, false);
        } else {
            dataView.setBigUint64(0, BigInt(data), false);
        }
    } else if (Array.isArray(data)) {
        buffer = new ArrayBuffer(data.length);
        const uint8Array = new Uint8Array(buffer);
        for (let i = 0; i &lt; data.length; i++) {
            uint8Array[i] = data[i];
        }
    } else {
        throw new Error('Unsupported data type');
    }
    return new Uint8Array(buffer);
}</code></pre>
<p>使用：</p>
<pre><code class="language-javascript">toUint8Array('vsay');
//输出 [118, 115, 97, 121]
toUint8Array(257);
//输出 [1, 1] 二进制 00000001 00000001</code></pre>
<h4>将uint8Array转换成字符串</h4>
<pre><code class="language-javascript">function Uint8ArrayToString(data) {
    // Uint8Array，存储了 UTF-8 编码的汉字数据
    const dataArray = new Uint8Array(data);
    // 找到第一个为 0 的字节的索引
    const zeroIndex = dataArray.findIndex(byte =&gt; byte === 0);
    // 截断数组到第一个 0 的位置
    const trimmedArray = dataArray.slice(0, zeroIndex !== -1 ? zeroIndex : dataArray.length);
    // 创建一个 TextDecoder 对象，指定 UTF-8 编码
    const decoder = new TextDecoder('utf-8');
    // 解码截断后的 remark 数据
    return decoder.decode(trimmedArray);
}</code></pre>
<p>使用：</p>
<pre><code class="language-javascript">Uint8ArrayToString([65,66,67,68,69,70]);
//输出 'ABCDEF'</code></pre>]]></description>
    <pubDate>Tue, 04 Mar 2025 10:15:17 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/309.html</guid>
</item>
<item>
    <title>记录一次单片机IO接线太长导致中断频发或死机问题的解决办法（长线通讯噪声抑制方法）</title>
    <link>https://www.vsay.net/mcu/308.html</link>
    <description><![CDATA[<p><strong>单总线通讯延长线导致STC单片机死机问题解决</strong></p>
<h3>问题起因</h3>
<p>在开发一个单总线通讯的小项目时，我使用了STC单片机作为接收端，上位机通过数据线向STC单片机发送数据。由于两者之间的距离大约1米，我使用了一根长约1米的数据传输线。但在实际调试时，我遇到了一个令人头疼的问题：短线连接时一切正常，但当线长达到1米时，STC单片机在接收数据时出现了异常现象——中断信号被无限触发，导致单片机进入死机状态。</p>
<h3>问题分析</h3>
<p>经过排查，我发现问题出在中断触发机制。当单片机处于掉电模式时，它依赖外部中断来唤醒，但长线延长后，信号可能因电磁干扰或者传输质量下降，产生了不稳定的波动或噪声，进而引发了中断的频繁触发。这个问题在短线情况下并不明显，因为信号质量相对较好，但在1米长的传输线中，信号干扰变得更加突出。</p>
<h3>解决方案</h3>
<p>通过查阅资料，我了解到，针对这种情况，<strong>可以在IO口与外部电路之间串联一个电阻来抑制噪声干扰。推荐的电阻值范围是100Ω到1kΩ之间，可以根据实际情况进行调整</strong>。此外，为了进一步稳定信号，我并联了一个470pF的电容，用以滤除高频噪声。</p>
<p>经过调整后，问题得以解决，STC单片机不再出现死机现象，数据传输也变得稳定。</p>
<h3>总结</h3>
<ol>
<li><strong>问题</strong>：单片机在接收数据时中断被无限触发，导致死机，问题只在使用1米长的数据线时出现。</li>
<li><strong>原因</strong>：长线信号干扰或电磁噪声导致中断触发异常。</li>
<li><strong>解决方案</strong>：在IO口串联100Ω到1kΩ的电阻，并并联470pF电容，稳定了信号，解决了死机问题。</li>
</ol>]]></description>
    <pubDate>Thu, 27 Feb 2025 13:48:06 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mcu/308.html</guid>
</item>
<item>
    <title>STC8G使用串口打印调试</title>
    <link>https://www.vsay.net/mcu/306.html</link>
    <description><![CDATA[<p>STC8G/8H/8C系列1T8051单片机<br />
直接上代码：</p>
<pre><code class="language-c">void Uart1_Init(void) // 9600bps@11.0592MHz
{
    SCON = 0x50;  // 8位数据,可变波特率
    AUXR |= 0x40; // 定时器时钟1T模式
    AUXR &amp;= 0xFE; // 串口1选择定时器1为波特率发生器
    TMOD &amp;= 0x0F; // 设置定时器模式
    TL1 = 0xE0;   // 设置定时初始值
    TH1 = 0xFE;   // 设置定时初始值
    ET1 = 0;      // 禁止定时器中断
    TR1 = 1;      // 定时器1开始计时
}

void Uart1_Send_Byte(uint8_t dat) // 串口1发送一个字节
{
    SBUF = dat;
    while (!TI)
        ;
    TI = 0;
}

void Uart1_Send_String(uint8_t *str) // 串口1发送字符串
{
    while (*str)
    {
        Uart1_Send_Byte(*str++);
    }
}</code></pre>
<p><a href="https://www.vsay.net/content/uploadfile/202502/thum-feca1740275185.png"><img src="https://www.vsay.net/content/uploadfile/202502/feca1740275185.png" alt="" /></a></p>
<p>完后可调试查看。</p>]]></description>
    <pubDate>Sun, 23 Feb 2025 09:42:40 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mcu/306.html</guid>
</item>
<item>
    <title>STC8G驱动WS2812</title>
    <link>https://www.vsay.net/mcu/305.html</link>
    <description><![CDATA[<h2>技术名词解释</h2>
<h4>1t单片机</h4>
<ul>
<li>1t可以说是相对传统51单片机来说的，理论上比传统51单片机快12倍。并且该型号频率可以达到33mhz。如果以普通的51单片机来类比，当传统51单片机晶振为12mhz时，一条<em>nop</em>();指令的时间为1us，而stc8g就是1/12us，约等于0.083us。</li>
<li>为了更好模拟准确的时序，我们在下载时把内部时钟设置为 24mhz ，这样一条 <em>nop</em>(); 指令的时间为1/24us约等于 0.0417us(即41.7ns)，三条就刚好约为125ns</li>
</ul>
<h4>ws2812</h4>
<ul>
<li>WS2812B-V5是一个集控制电路与发光电路于一体的智能外控LED光源。其外型与一个5050LED灯珠相同， 每个元件即为一个像素点。像素点内部包含了智能数字接口数据锁存信号整形放大驱动电路，还包含有高精度的 内部振荡器和可编程定电流控制部分，有效保证了像素点光的颜色高度一致。 数据协议采用单线归零码的通讯方式，像素点在上电复位以后，DIN端接受从控制器传输过来的数据，首先送过 来的24bit数据被第一个像素点提取后，送到像素点内部的数据锁存器，剩余的数据经过内部整形处理电路整形放 大后通过DO端口开始转发输出给下一个级联的像素点，每经过一个像素点的传输，信号减少24bit。像素点采用 自动整形转发技术，使得该像素点的级联个数不受信号传送的限制，仅受限信号传输速度要求。</li>
</ul>
<p>ws2812详细数据协议<br />
WS2812通过rgb三种颜色来产生理论上255x255x255种颜色，其通信方式如下所示：<br />
<a href="https://www.vsay.net/content/uploadfile/202502/thum-06f11739865237.png"><img src="https://www.vsay.net/content/uploadfile/202502/06f11739865237.png" alt="" /></a></p>
<p>所以我们只要模拟出上图的时序就能控制ws2812了，但要注意他的数据顺序是G-&gt;R-&gt;B</p>
<h4>为了更好模拟准确的时序，我们在下载时把内部时钟设置为 24mhz ，这样一条 <em>nop</em>(); 指令的时间为1/24us约等于 0.0417，三条就刚好约为125ns。</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202502/7fc71739864324.png"><img src="https://www.vsay.net/content/uploadfile/202502/7fc71739864324.png" alt="" /></a></p>
<h2>Keil工程的设置</h2>
<h4>Keil需要选择对应的单片机型号</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202502/thum-cb641739866425.png"><img src="https://www.vsay.net/content/uploadfile/202502/cb641739866425.png" alt="" /></a></p>
<h4>设置为24Mhz</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202502/thum-31491739866433.png"><img src="https://www.vsay.net/content/uploadfile/202502/31491739866433.png" alt="" /></a></p>
<h4>关闭优化功能为Level0(避免编译器过度优化,导致时序不正确)</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202502/thum-8d411739866483.png"><img src="https://www.vsay.net/content/uploadfile/202502/8d411739866483.png" alt="" /></a></p>
<h2>代码编程</h2>
<ul>
<li>需要<strong>#include “stc8.h”</strong></li>
<li>使用<em>nop</em>()函数前,需要<strong>#include “intrins.h”</strong></li>
<li>根据主时钟频率,调整延时函数的数量</li>
<li>– STC8G1K08A最高支持35MHz</li>
<li>– 这里使用24MHz</li>
<li>使用下载时,注意选择型号并配置时钟频率 (如下图)</li>
</ul>
<p><a href="https://www.vsay.net/content/uploadfile/202502/thum-1c321739866524.png"><img src="https://www.vsay.net/content/uploadfile/202502/1c321739866524.png" alt="" /></a></p>
<h2>看代码</h2>
<h4>main.c</h4>
<pre><code class="language-c">#include &lt;STC8G.H&gt;
#include "WS2812.h"

void main(void)
{   
    while(1)
    {
        WS2812_SendData(255,0,0);
    }
}</code></pre>
<h4>WS2812.h</h4>
<pre><code class="language-c">#ifndef WS2812_H
#define WS2812_H

#include &lt;STC8G.H&gt;
#include &lt;intrins.h&gt;

#define WS28_PIN P33 // 引脚定义
#define WS28_NUM 4

// 初始化引脚
void WS28_Init();

// 模块写入一组数据
void WS28_SetRGB(unsigned char r_data,  // 红色
                 unsigned char g_data,  // 绿色
                 unsigned char b_data); // 蓝色

#endif // WS2812_H</code></pre>
<h4>WS2812.c</h4>
<pre><code class="language-c">#include "WS2812.h"

// 初始化引脚
void WS28_Init()
{
    // 引脚P33初始化为输出
    P3M0 |= 0x08;
    P3M1 &amp;= 0xF7;
}

// 模块写入一组数据
void WS28_SetRGB(unsigned char r_data, // 红色
                 unsigned char g_data, // 绿色
                 unsigned char b_data) // 蓝色
{
    unsigned char temp[3];
    unsigned char n, j;
    unsigned char dat;

    // 注意顺序,按照数据手册为grb顺序排列
    temp[0] = g_data;
    temp[1] = r_data;
    temp[2] = b_data;

    for (n = 0; n &lt; 3; n++) // 循环3次(发送一组 rgb 颜色数据)
    {
        dat = temp[n];
        for (j = 0; j &lt; WS28_NUM; j++) // 循环WS28_NUM次(发送1byte数据)
        {
            if (dat &amp; (0x80&gt;&gt;i)) // 写入数据1
            {
                // 上面说了：为了更好模拟准确的时序，我们在下载时把内部时钟设置为 24mhz ，这样一条 nop(); 指令的时间为1/24us约等于 0.0417，三条就刚好约为125ns。
                // ws2812写1的时候 H-&gt;580ns~1us L-&gt;220ns~420ns
                // 此处20个nop一个41.7ns  20*41.7ns≈834ns符合写1 T1H 580ns~1us
                WS28_PIN = 1;
                _nop_();_nop_();_nop_();_nop_();_nop_();//_nop_();    delay 41ns
                _nop_();_nop_();_nop_();_nop_();_nop_();
                _nop_();_nop_();_nop_();_nop_();_nop_();
                _nop_();_nop_();_nop_();_nop_();_nop_();
                // 此处7个nop一个41.7ns  7*41.7ns≈291ns符合写1 T1L 220ns~420ns
                WS28_PIN = 0;
                _nop_();_nop_();_nop_();_nop_();_nop_();
                _nop_();_nop_();
            }
            else // 写入数据0
            {
                // 上面说了：为了更好模拟准确的时序，我们在下载时把内部时钟设置为 24mhz ，这样一条 nop(); 指令的时间为1/24us约等于 0.0417，三条就刚好约为125ns。
                // 此处正好相反
                WS28_PIN = 1;
                _nop_();_nop_();_nop_();_nop_();_nop_();
                _nop_();_nop_();
                WS28_PIN = 0;
                _nop_();_nop_();_nop_();_nop_();_nop_();//_nop_();    delay 41ns
                _nop_();_nop_();_nop_();_nop_();_nop_();
                _nop_();_nop_();_nop_();_nop_();_nop_();
                _nop_();_nop_();_nop_();_nop_();_nop_();
            }
        }
    }
}</code></pre>
<p>参考：<br />
<a href="https://blog.csdn.net/Xhw3f586/article/details/132295552">https://blog.csdn.net/Xhw3f586/article/details/132295552</a><br />
<a href="https://blog.csdn.net/github_38203983/article/details/142327258">https://blog.csdn.net/github_38203983/article/details/142327258</a><br />
<a href="https://blog.csdn.net/qq_61688416/article/details/132503077">https://blog.csdn.net/qq_61688416/article/details/132503077</a></p>]]></description>
    <pubDate>Tue, 18 Feb 2025 15:34:03 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mcu/305.html</guid>
</item>
<item>
    <title>HID 报告描述详解</title>
    <link>https://www.vsay.net/mcu/304.html</link>
    <description><![CDATA[<h2>HID 报告描述</h2>
<h4>1. 报告描述简介</h4>
<p>Report Description，即上报事件的描述，描述所支持事件的格式及取值意义，比如鼠标按键上报格式、左键右键中间键分别对应的值等；报告描述类似，都是结构数据，格式类似</p>
<pre><code>{usage:1, {id:0,usage:1.1,size:1,count:1,min:0,max:5,format:xx}}</code></pre>
<h4>2. 描述符结构</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202502/thum-a5951739843437.png"><img src="https://www.vsay.net/content/uploadfile/202502/a5951739843437.png" alt="" /></a></p>
<p><strong>几个主要的名词解释一下：</strong></p>
<ul>
<li>Collection，集合，相当于{}，用来包含一组描述；Collection有Application和Physical之分，Application表示大项，里有可以有很多Physical子项</li>
<li>Report，报告，在上图中只是一个ID号，来区分不同report。</li>
<li>Main Item,主项，例出来哪些项</li>
<li>Report Size，每一项有多少位。</li>
<li>Report Count, 总共有多少项</li>
<li>Logical Minimum，每个项的取值范围中的最小值，即最小只能取的值。</li>
<li>Logical Maximum，每个项的取值范围中的最大值，即最大只能取的值。</li>
<li>Usage，用途，表示有哪些功能，一个Usage代表一个功能。</li>
</ul>
<p>项描述基本上是由两个字节构成，第一个字节表示名称，第二个字节表示值，<br />
如Report Size,8<br />
Report Size 是名称，8是它的值。<br />
Report Size 对应的16制数据是0x75，在代码里上面的表现是0x75,0x08。</p>
<blockquote>
<p>项描述基本上是由两个字节构成，第一个字节表示名称，第二个字节表示值，<br />
如Report Size,8<br />
Report Size 是名称，8是它的值。<br />
Report Size 对应的16制数据是0x75，在代码里上面的表现是0x75,0x08。<br />
有些名称不需要带值，如Collection的结束符( ) )为0xC0，就没有第二个字节。有些名称不需要带值，如Collection的结束符( ) )为0xC0，就没有第二个字节。</p>
</blockquote>
<h4>3. 名称</h4>
<p>名称有分长字和短字，这里只介绍短字，短字是由一个字节构成，结构如下：<br />
<a href="https://www.vsay.net/content/uploadfile/202502/771d1739843558.png"><img src="https://www.vsay.net/content/uploadfile/202502/771d1739843558.png" alt="" /></a></p>
<p>第3 ~ 2位表示类型，有如下几种：</p>
<ul>
<li>0 = Main</li>
<li>1 = Global</li>
<li>2 = Local</li>
<li>3 = Reserved</li>
</ul>
<p>第1 ~ 0位表示名称的值由多少个字节构成，具体意义如下：</p>
<ul>
<li>0 = 0 bytes，名称后面不带值</li>
<li>1 = 1 bytes，名称后面带1个值</li>
<li>2 = 2 bytes，名称后面带2个值</li>
<li>3 = 4 bytes，名称后面带4个值</li>
</ul>
<p>第7 ~ 4位表示Tag，Tag与类型对应，每种类型有很多不同的Tag。</p>
<h4>3.1 Main类对应的Tag</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202502/thum-06f31739843627.png"><img src="https://www.vsay.net/content/uploadfile/202502/06f31739843627.png" alt="" /></a><br />
在上面表格中，可以看到，第3 ~ 2位是00，这两位是bType值，00表示类型为Main；nn是第1 ~ 0位，是bSize，表示数值的字节数；第7 ~ 4位是bTag，如<br />
1000(0x8?)表示Input；Valid Data表示值的意义，如Input项，Bit 0 {Data(0) | Constant(1)}，意思是说，如果值的第0位的是0，即表示Data，如果是<br />
Constant，如下语句：</p>
<ul>
<li>0x81, 0x02, // Input: (Data,Variable, Absolute)</li>
<li>它的值是2，第0位是0对应的是Data，第1位是1 对应的是Variable，第二位是0对应的是Absolute，所以它是一个Data、Variable、 Absolute的类型。</li>
</ul>
<p>下面对Data、Variable等的意思作一下解释。</p>
<ul>
<li>Data 表示是一个可写的数据。</li>
<li>Constant 表示是一个只读的数据。</li>
<li>Aarray 表示数据里的值代表一个Usage，Report Size表示位数，即Report Size的存储单元里的值是Usage的Index。Report Count一般为1，如果大以同时出现多个Usage.</li>
<li>Variable 则是一个Report Size存储单元表示一个Usage,其值表示Usage的状态；Report Size表示位数，Report Count表示长度。</li>
<li>Absolute 表示绝对数据，如触模屏数据，便使用Absolute。</li>
<li>Relative 表示相对数据，如鼠标数据，便使用Relative。</li>
<li>其也用的少就不多说了，详细参看《HID协议》。</li>
</ul>
<h4>3.2 Global类对应的Tag</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202502/thum-7d3e1739843843.png"><img src="https://www.vsay.net/content/uploadfile/202502/7d3e1739843843.png" alt="" /></a><br />
<a href="https://www.vsay.net/content/uploadfile/202502/thum-12141739843854.png"><img src="https://www.vsay.net/content/uploadfile/202502/12141739843854.png" alt="" /></a></p>
<p>在上面表格中，可以看到，第3 ~ 2位是01，这两位是bType值，01表示类型为Global；nn是第1 ~ 0位，是bSize，表示数值的字节数；第7 ~ 4位是bTag，<br />
0000(0x0?)表示Usage Page。</p>
<h4>3.3 Local类对应的Tag</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202502/thum-7da01739843894.png"><img src="https://www.vsay.net/content/uploadfile/202502/7da01739843894.png" alt="" /></a><br />
在上面表格中，可以看到，第3 ~ 2位是10，这两位是bType值，10表示类型为Local；nn是第1 ~ 0位，是bSize，表示数值的字节数；第7 ~ 4位是bTag，<br />
0000(0x0?)表示Usage。</p>
<h4>4 样例解读</h4>
<p>下面是一个伪代码描述的样例<br />
其中涉及到的页码可查看第5段用途表：</p>
<pre><code>Usage Page (Generic Desktop), //定位到Generic Desktop页，这个相当于指针跳转一样的东西
Usage (Mouse), //指定Generic Desktop里的mouse，表示这是一个鼠标
Collection (Application), // Collection Application，是对Mouse的解释
Usage(Pointer), //表示指针形式
Collection (Physical), // CollectionPhysical，是对Pointer的解释
ReportID (0A), //id为0x0A的报告
Usage(X), Usage (Y), //上报X,Y两个数据
LogicalMinimum (-127), ;Report data values range from -127
LogicalMaximum (127), ;to 127 //X,Y的取值范围是-127~127
ReportSize (8), Report Count (2), //总共要上报2个字节，即x一个字节,y一个字节
Input(Data, Variable, Relative), //将X,Y这两个字节添加到0x0A的报告里，且这两个值是可写并且是相对的
LogicalMinimum (0), ;
LogicalMaximum (1), ;//下面Button的取值范围是0~1
ReportSize (1), Report Count (3), //3个1位的数据
UsagePage (Button Page),//是一个BUTTON
UsageMinimum (1),
Usage Maximum (3),//共有BUTTON1~BUTTON3，即总共有3个BUTTON
Input(Data, Variable, Absolute),//将3个分别代表的BUTTON1,BUTTON2,BUTTON3的位添加到0x0A的报告
ReportSize (5),
Input(Constant), //增加5个无效的位与上面3位凑成一个字节
EndCollection,
End Collection</code></pre>
<p>综上所示，上面样例所表达的意思就是下图所示的：<br />
<a href="https://www.vsay.net/content/uploadfile/202502/thum-38b81739843980.png"><img src="https://www.vsay.net/content/uploadfile/202502/38b81739843980.png" alt="" /></a></p>
<p>再举一个实际的例子进行解读，下面是摘自TI CC2540/CC2541 SDK里的BLE-CC254x_v1.4.0\Projects\ble\Profiles\HIDDevKbM\hidkbmservice.c</p>
<pre><code class="language-c">static CONST uint8 hidReportMap[] =
{
// 鼠标
0x05, 0x01, // UsagePage (Generic Desktop)
 0x09, 0x02, // Usage (Mouse)
 0xA1, 0x01, // Collection (Application)
 0x85, 0x01, // Report Id (1)
 0x09, 0x01, // Usage (Pointer)
 0xA1, 0x00, // Collection (Physical)
 0x05, 0x09, // Usage Page (Buttons)
 0x19, 0x01, // Usage Minimum (01) -Button 1
 0x29, 0x03, // Usage Maximum (03) -Button 3
 0x15, 0x00, // Logical Minimum (0)
 0x25, 0x01, // Logical Maximum (1)
 0x75, 0x01, // Report Size (1)
 0x95, 0x03, // Report Count (3)
 0x81, 0x02, // Input (Data, Variable,Absolute) - Button states
 0x75, 0x05, // Report Size (5)
 0x95, 0x01, // Report Count (1)
 0x81, 0x01, // Input (Constant) - Paddingor Reserved bits
 0x05, 0x01, // Usage Page (GenericDesktop)
 0x09, 0x30, // Usage (X)
 0x09, 0x31, // Usage (Y)
 0x09, 0x38, // Usage (Wheel)
 0x15, 0x81, // Logical Minimum (-127)
 0x25, 0x7F, // Logical Maximum (127)
 0x75, 0x08, // Report Size (8)
 0x95, 0x03, // Report Count (3)
 0x81, 0x06, // Input (Data, Variable,Relative) - X &amp; Y coordinate
 0xC0, // End Collection
 0xC0, // End Collection
// 键盘
0x05, 0x01, // UsagePg (Generic Desktop)
 0x09, 0x06, // Usage (Keyboard)
 0xA1, 0x01, // Collection: (Application)
 0x85, 0x02, // Report Id (2)
 //
 0x05, 0x07, // Usage Pg (Key Codes)
 0x19, 0xE0, // Usage Min (224)
 0x29, 0xE7, // Usage Max (231)
 0x15, 0x00, // Log Min (0)
 0x25, 0x01, // Log Max (1)
 // Modifier byte
 0x75, 0x01, // Report Size (1)
 0x95, 0x08, // Report Count (8)
 0x81, 0x02, // Input: (Data, Variable,Absolute)
 // Reserved byte
 0x95, 0x01, // Report Count (1)
 0x75, 0x08, // Report Size (8)
 0x81, 0x01, // Input: (Constant)
 // LED report
 0x95, 0x05, // Report Count (5)
 0x75, 0x01, // Report Size (1)
 0x05, 0x08, // Usage Pg (LEDs)
 0x19, 0x01, // Usage Min (1)
 0x29, 0x05, // Usage Max (5)
 0x91, 0x02, // Output: (Data, Variable,Absolute)
 // LED report padding
 0x95, 0x01, // Report Count (1)
 0x75, 0x03, // Report Size (3)
 0x91, 0x01, // Output: (Constant)
 // Key arrays (6 bytes)
 0x95, 0x06, // Report Count (6)
 0x75, 0x08, // Report Size (8)
 0x15, 0x00, // Log Min (0)
 0x25, 0x65, // Log Max (101)
 0x05, 0x07, // Usage Pg (Key Codes)
 0x19, 0x00, // Usage Min (0)
 0x29, 0x65, // Usage Max (101)
 0x81, 0x00, // Input: (Data, Array)
 0xC0, // End Collection
// Consumer
0x05, 0x0C, // Usage Pg (Consumer Devices)
 0x09, 0x01, // Usage (Consumer Control)
 0xA1, 0x01, // Collection (Application)
 0x85, 0x03, // Report Id (3)
 0x09, 0x02, // Usage (Numeric KeyPad)
 0xA1, 0x02, // Collection (Logical)
 0x05, 0x09, // Usage Pg (Button)
 0x19, 0x01, // Usage Min (Button1)
 0x29, 0x0A, // Usage Max (Button10)
 0x15, 0x01, // Logical Min (1)
 0x25, 0x0A, // Logical Max (10)
 0x75, 0x04, // Report Size (4)
 0x95, 0x01, // Report Count (1)
 0x81, 0x00, // Input (Data, Ary,Abs)
 0xC0, // End Collection
 0x05, 0x0C, // Usage Pg (ConsumerDevices)
 0x09, 0x86, // Usage (Channel)
 0x15, 0xFF, // Logical Min (-1)
 0x25, 0x01, // Logical Max (1)
 0x75, 0x02, // Report Size (2)
 0x95, 0x01, // Report Count (1)
 0x81, 0x46, // Input (Data, Var,Rel, Null)
 0x09, 0xE9, // Usage (Volume Up)
 0x09, 0xEA, // Usage (Volume Down)
 0x15, 0x00, // Logical Min (0)
 0x75, 0x01, // Report Size (1)
 0x95, 0x02, // Report Count (2)
 0x81, 0x02, // Input (Data, Var,Abs)
 0x09, 0xE2, // Usage (Mute)
 0x09, 0x30, // Usage (Power)
 0x09, 0x40, // Usage (Menu)
 0x09, 0xB1, // Usage (Pause)
 0x09, 0xB2, // Usage (Record)
 0x0a, 0x23, 0x02, // Usage (Home)
 0x0a, 0x24, 0x02, // Usage (Back)
 0x09, 0xB3, // Usage (Fast Forward)
 0x09, 0xB4, // Usage (Rewind)
 0x09, 0xB5, // Usage (Scan Next)
 0x09, 0xB6, // Usage (Scan Prev)
 0x09, 0xB7, // Usage (Stop)
 0x15, 0x01, // Logical Min (1)
 0x25, 0x0C, // Logical Max (12)
 0x75, 0x04, // Report Size (4)
 0x95, 0x01, // Report Count (1)
 0x81, 0x00, // Input (Data, Ary,Abs)
 0x09, 0x80, // Usage (Selection)
 0xA1, 0x02, // Collection (Logical)
 0x05, 0x09, // Usage Pg (Button)
 0x19, 0x01, // Usage Min (Button1)
 0x29, 0x03, // Usage Max (Button3)
 0x15, 0x01, // Logical Min (1)
 0x25, 0x03, // Logical Max (3)
 0x75, 0x02, // Report Size (2)
 0x81, 0x00, // Input (Data, Ary,Abs)
 0xC0, // End Collection
 0x81, 0x03, // Input (Const, Var,Abs)
 0xC0 // End Collection
};</code></pre>
<p>上面用三大应用功能，分别鼠标、键盘和Consumer，每个应用功能都是用Collection Application括起来的。</p>
<p><strong>我们先来解析鼠标的报告描述：</strong></p>
<pre><code class="language-c">// 0x04代表是Global类的Usage Page功能，最位2位表示带多少个字节的数据，因为只带1个数据，所以是1，跟0x04组合起来就是0x05了。其他名称的不多，数值可以参照上面的《3名称》
0x05,0x01, // Usage Page (Generic Desktop)
// 表示这是一个鼠标， Usage是为了给对方解析数据时有个参照
0x09, 0x02, // Usage (Mouse)
// 0xA1, 0x01 表示Collection Application ; 0xA1,0x00表示CollectionPhysical.表示下面所包含的是对Mouse的解释
0xA1, 0x01, // Collection (Application)
// 该报告对应的ID是1
0x85, 0x01, // Report Id (1)
// 这是个指针形式
0x09, 0x01, // Usage (Pointer)
// 下面所包含的是对指针的解释
0xA1, 0x00, // Collection (Physical)
// 下面定义的是按键
0x05, 0x09, // Usage Page (Buttons)
// 总共有3个按键
0x19, 0x01, // Usage Minimum (01) -Button 1
0x29, 0x03, // Usage Maximum (03) -Button 3
// 按键的值是0和1，表示放开和按下
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
// 有3个1位，即用3bits分别对应三个按键
0x75, 0x01, // Report Size (1)
0x95, 0x03, // Report Count (3)
// 将这三个位加入本报告的数据中，这三位是可读写的绝对值
0x81, 0x02, // Input (Data, Variable,Absolute) - Button states
// 定义1个5位的数据
0x75, 0x05, // Report Size (5)
0x95, 0x01, // Report Count (1)
// 将这个数据添加到本报告的数据中，主要是与前面3位组成一个字节，这5位是Constant数据
0x81, 0x01, // Input (Constant) - Paddingor Reserved bits
// 下面定义的是X,Y,Wheel三个功能
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x38, // Usage (Wheel)
// X,Y,Wheel的取值范围是-127~127
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
// 用三个字节来表示x,y,wheel
0x75, 0x08, // Report Size (8)
0x95, 0x03, // Report Count (3)
// 将这三个字节添加到本报告中
0x81, 0x06, // Input (Data, Variable,Relative) - X &amp; Y coordinate
0xC0, // End Collection</code></pre>
<p>上面解析出来的数据格式如下:<br />
<a href="https://www.vsay.net/content/uploadfile/202502/thum-fe611739844554.png"><img src="https://www.vsay.net/content/uploadfile/202502/fe611739844554.png" alt="" /></a></p>
<p><strong>我们来看一下，如果要发一个鼠标的坐标，该如何发：</strong></p>
<pre><code class="language-c">static void hidEmuKbdSendMouseReport( uint8buttons )
{
    uint8 buf[HID_MOUSE_IN_RPT_LEN];
    buf[0] = buttons; // Buttons
    buf[1] = 0; // X
    buf[2] = 0; // Y
    buf[3] = 0; // Wheel
    HidDev_Report( HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT, HID_MOUSE_IN_RPT_LEN, buf );
}</code></pre>
<p>从上面函数可以看到，X,Y在第2、3个字节，结合上面的数据格式图可以看出，正好是对应的。</p>
<p><strong>我们接着解析键盘的报告描述：</strong></p>
<pre><code class="language-c">// 这是一个键盘
0x05,0x01, // Usage Pg (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
// 本报告的ID是2
0xA1, 0x01, // Collection: (Application)
0x85, 0x02, // Report Id (2)
// 下面定义的是按键码
0x05, 0x07, // Usage Pg (Key Codes)
// 按键码分别是224~231，共总有8个按键码
0x19, 0xE0, // Usage Min (224)
0x29, 0xE7, // Usage Max (231)
// 按键码的值是0和1，分别代表放开和按下
0x15, 0x00, // Log Min (0)
0x25, 0x01, // Log Max (1)
// 用8个bit分别表示8个按键的状态
// Modifier byte
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
// 将这8个bit添加到本报告中
0x81, 0x02, // Input: (Data, Variable,Absolute)
// 另外再预留8个bit备用，暂时没用
// Reserved byte
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input: (Constant)
// 定义5个1bit
// LED report
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
// 这是LED
0x05, 0x08, // Usage Pg (LEDs)
// 5个bit分别对应LED1~LED5
0x19, 0x01, // Usage Min (1)
0x29, 0x05, // Usage Max (5)
// 将这5个bit添加到本报告中，LED需要作为OUT
0x91, 0x02, // Output: (Data, Variable,Absolute)
// 再增加3个bit，与上面5个bit组成一个字节
// LED report padding
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x01, // Output: (Constant)
// 定义6个字节
// Key arrays (6 bytes)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
// 每个字节的取值范围是0~101
0x15, 0x00, // Log Min (0)
0x25, 0x65, // Log Max (101)
// 这个也是键盘码
0x05, 0x07, // Usage Pg (Key Codes)
// 分别是键盘码0~键盘码101
0x19, 0x00, // Usage Min (0)
0x29, 0x65, // Usage Max (101)
// 将这6个字节添加到本报告中,表示同时可产生6个键值。
0x81, 0x00, // Input: (Data, Array)
0xC0, // End Collection</code></pre>
<p>上面解析出来的数据格式如下:<br />
<a href="https://www.vsay.net/content/uploadfile/202502/thum-2ab91739845991.png"><img src="https://www.vsay.net/content/uploadfile/202502/2ab91739845991.png" alt="" /></a><br />
Input和Out是不同的两条通道。现在我们来看一下，如果要发一个按键K0~K101，需要怎么发，如下：</p>
<pre><code class="language-c">static void hidEmuKbdSendReport( uint8keycode )
{
    uint8 buf[HID_KEYBOARD_IN_RPT_LEN];
    buf[0] = 0; // Modifierkeys
    buf[1] = 0; // Reserved
    buf[2] = keycode; // Keycode 1
    buf[3] = 0; // Keycode 2
    buf[4] = 0; // Keycode 3
    buf[5] = 0; // Keycode 4
    buf[6] = 0; // Keycode 5
    buf[7] = 0; // Keycode 6
    HidDev_Report( HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT, HID_KEYBOARD_IN_RPT_LEN, buf );
}</code></pre>
<p>上面函数可以看到，它是放在第3个字节，结合数据格式图可以看出，第3个字节开始，刚好是在K0~K101的按键区。</p>
<p><strong>我们最后来解析Consumer的报告描述：</strong></p>
<pre><code class="language-c">// 这是个Consumer控制
0x05,0x0C, // Usage Pg (Consumer Devices)
0x09, 0x01, // Usage (Consumer Control)
// 本报告ID为3
0xA1, 0x01, // Collection (Application)
0x85, 0x03, // Report Id (3)
// 下面定义的是数字键盘
0x09, 0x02, // Usage (Numeric KeyPad)
// 下面定义的是按键
0xA1, 0x02, // Collection (Logical)
0x05, 0x09, // Usage Pg (Button)
// 分别是Button1~Button10
0x19, 0x01, // Usage Min (Button1)
0x29, 0x0A, // Usage Max (Button10)
// 每个按键的取值范围为1~10
0x15, 0x01, // Logical Min (1)
0x25, 0x0A, // Logical Max (10)
// 1个4bit的值，来表示键值1~10，这个值是哪个就表示哪个键按下。
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
// 将这4bit添加到本报告中
0x81, 0x00, // Input (Data, Ary, Abs)
0xC0, // End Collection
// 这里定义的是频道
0x05, 0x0C, // Usage Pg (ConsumerDevices)
0x09, 0x86, // Usage (Channel)
// 频道值范围是-1~1，这里应该只用到-1和1，表示频道+和-
0x15, 0xFF, // Logical Min (-1)
0x25, 0x01, // Logical Max (1)
// 用一个2bit来表示，第1个bit表示频道+，第二个表示频道-
0x75, 0x02, // Report Size (2)
0x95, 0x01, // Report Count (1)
// 将这个2bit加到本报告中
0x81, 0x46, // Input (Data, Var,Rel, Null)
// 定义两个按键，音量加和音量减
0x09, 0xE9, // Usage (Volume Up)
0x09, 0xEA, // Usage (Volume Down)
// 按键值为0~1，这里少了LogicalMax,继承上面的LogicalMax=1
0x15, 0x00, // Logical Min (0)
0x75, 0x01, // Report Size (1)
// 定义2个1bit，每个bit代表一个键
0x95, 0x02, // Report Count (2)
// 将2个1bit添加到本报告中
0x81, 0x02, // Input (Data, Var,Abs)
// 定义12个按键
0x09, 0xE2, // Usage (Mute)
0x09, 0x30, // Usage (Power)
0x09, 0x40, // Usage (Menu)
0x09, 0xB1, // Usage (Pause)
0x09, 0xB2, // Usage (Record)
0x0a, 0x23, 0x02, // Usage (Home)
0x0a, 0x24, 0x02, // Usage (Back)
0x09, 0xB3, // Usage (Fast Forward)
0x09, 0xB4, // Usage (Rewind)
0x09, 0xB5, // Usage (Scan Next)
0x09, 0xB6, // Usage (Scan Prev)
0x09, 0xB7, // Usage (Stop)
// 用一个4位来存储1~12，1表示Mute … 12表示Stop
0x15, 0x01, // Logical Min (1)
0x25, 0x0C, // Logical Max (12)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
// 将这个4bit添加到报告中
0x81, 0x00, // Input (Data, Ary,Abs)
0x09, 0x80, // Usage (Selection)
// 这是按键
0xA1, 0x02, // Collection (Logical)
0x05, 0x09, // Usage Pg (Button)
// 分别是Button1~Button3
0x19, 0x01, // Usage Min (Button1)
0x29, 0x03, // Usage Max (Button3)
// 每个按键取值范围是1~3
0x15, 0x01, // Logical Min (1)
0x25, 0x03, // Logical Max (3)
// 这里缺少了Report Count，继承上面的Report Count =1，也就是用1个2bit来存储1~3
0x75, 0x02, // Report Size (2)
// 将1个字节添加到本报告中
0x81, 0x00, // Input (Data, Ary,Abs)
0xC0, // End Collection
// 再补充2个bit将上面的凑成一个字节，ReportSize=2和Report Count=1继承上面的。
0x81, 0x03, // Input (Const, Var,Abs)
0xC0 // End Collection</code></pre>
<p>修改后，解析出来的数据格式如下:<br />
<a href="https://www.vsay.net/content/uploadfile/202502/thum-9b831739846244.png"><img src="https://www.vsay.net/content/uploadfile/202502/9b831739846244.png" alt="" /></a><br />
我们来看一下，如果要发音量+/-键该怎么发：</p>
<pre><code class="language-c">static void hidCCSendReport( uint8 cmd,bool keyPressed )
{
    //Only send the report if something meaningful to report
    uint8 buf[HID_CC_IN_RPT_LEN] = { 0, 0 };
    //No need to include Report Id
    if( keyPressed )
    {
        hidCCBuildReport( buf, cmd );
    }
    HidDev_Report( HID_RPT_ID_CC_IN, HID_REPORT_TYPE_INPUT, HID_CC_IN_RPT_LEN, buf );
}</code></pre>
<p>在hidCCBuildReport对音量加减解析出来是：<br />
音量加是：buf[0] =0x40, buf[1] = 0x00<br />
音量减是：buf[0] =0x80, buf[1] = 0x00<br />
正好与数据格式图中第一个字节的第6位和第7位相对应。</p>
<h4>5 用途表</h4>
<p>下面用途表中列了几个常用的表，其他详细请参看《HID用途表》<br />
<strong>Usage Page</strong></p>
<table>
<thead>
<tr>
<th>Page ID</th>
<th>Page Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Undefined</td>
</tr>
<tr>
<td>1</td>
<td>Generic Desktop Controls</td>
</tr>
<tr>
<td>2</td>
<td>Simulation Controls</td>
</tr>
<tr>
<td>3</td>
<td>VR Controls</td>
</tr>
<tr>
<td>4</td>
<td>Sport Controls</td>
</tr>
<tr>
<td>5</td>
<td>Game Controls</td>
</tr>
<tr>
<td>6</td>
<td>Generic Device Controls</td>
</tr>
<tr>
<td>7</td>
<td>Keyboard / Keypad</td>
</tr>
<tr>
<td>8</td>
<td>LEDs</td>
</tr>
<tr>
<td>9</td>
<td>Button</td>
</tr>
<tr>
<td>0A</td>
<td>Ordinal</td>
</tr>
<tr>
<td>0B</td>
<td>Telephony</td>
</tr>
<tr>
<td>0C</td>
<td>Consumer</td>
</tr>
<tr>
<td>0D</td>
<td>Digitizer</td>
</tr>
<tr>
<td>0E</td>
<td>Reserved</td>
</tr>
<tr>
<td>0F</td>
<td>PID Page</td>
</tr>
<tr>
<td>10</td>
<td>Unicode</td>
</tr>
<tr>
<td>11-13</td>
<td>Reserved</td>
</tr>
<tr>
<td>14</td>
<td>Alphanumeric Display</td>
</tr>
<tr>
<td>15-3F</td>
<td>Reserved</td>
</tr>
<tr>
<td>40</td>
<td>Medical Instruments</td>
</tr>
<tr>
<td>41-7F</td>
<td>Reserved</td>
</tr>
<tr>
<td>80-83</td>
<td>Monitor pages</td>
</tr>
<tr>
<td>84-87</td>
<td>Power pages</td>
</tr>
<tr>
<td>88-8B</td>
<td>Reserved</td>
</tr>
<tr>
<td>8C</td>
<td>Bar Code Scanner page</td>
</tr>
<tr>
<td>8D</td>
<td>Scale page</td>
</tr>
<tr>
<td>8E</td>
<td>Magnetic Stripe Reading (MSR) Devices</td>
</tr>
<tr>
<td>8F</td>
<td>Reserved Point of Sale pages</td>
</tr>
</tbody>
</table>
<p><strong>Generic Desktop Page（0x01）</strong></p>
<table>
<thead>
<tr>
<th>Usage ID</th>
<th>Usage Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Undefined</td>
</tr>
<tr>
<td>1</td>
<td>Pointer</td>
</tr>
<tr>
<td>2</td>
<td>Mouse</td>
</tr>
<tr>
<td>3</td>
<td>Reserved</td>
</tr>
<tr>
<td>4</td>
<td>Joystick</td>
</tr>
<tr>
<td>5</td>
<td>Game Pad</td>
</tr>
<tr>
<td>6</td>
<td>Keyboard</td>
</tr>
<tr>
<td>7</td>
<td>Keypad</td>
</tr>
<tr>
<td>8</td>
<td>Multi-axis Controller</td>
</tr>
<tr>
<td>9</td>
<td>Tablet PC System Controls</td>
</tr>
<tr>
<td>0A-2F</td>
<td>Reserved</td>
</tr>
<tr>
<td>30</td>
<td>X</td>
</tr>
<tr>
<td>31</td>
<td>Y</td>
</tr>
<tr>
<td>32</td>
<td>Z</td>
</tr>
<tr>
<td>33</td>
<td>Rx</td>
</tr>
<tr>
<td>34</td>
<td>Ry</td>
</tr>
<tr>
<td>35</td>
<td>Rz</td>
</tr>
<tr>
<td>36</td>
<td>Slider</td>
</tr>
<tr>
<td>37</td>
<td>Dial</td>
</tr>
<tr>
<td>38</td>
<td>Wheel</td>
</tr>
<tr>
<td>39</td>
<td>Hat switch</td>
</tr>
<tr>
<td>3A</td>
<td>Counted Buffer</td>
</tr>
<tr>
<td>3B</td>
<td>Byte Count</td>
</tr>
<tr>
<td>3C</td>
<td>Motion Wakeup</td>
</tr>
<tr>
<td>3D</td>
<td>Start OOC</td>
</tr>
<tr>
<td>3E</td>
<td>Select OOC</td>
</tr>
<tr>
<td>3F</td>
<td>Reserved</td>
</tr>
<tr>
<td>40</td>
<td>Vx</td>
</tr>
<tr>
<td>41</td>
<td>Vy</td>
</tr>
<tr>
<td>42</td>
<td>Vz</td>
</tr>
<tr>
<td>43</td>
<td>Vbrx</td>
</tr>
<tr>
<td>44</td>
<td>Vbry</td>
</tr>
<tr>
<td>45</td>
<td>Vbrz</td>
</tr>
<tr>
<td>46</td>
<td>Vno</td>
</tr>
<tr>
<td>47</td>
<td>Feature Notification</td>
</tr>
<tr>
<td>48</td>
<td>Resolution Multiplier</td>
</tr>
<tr>
<td>49-7F</td>
<td>Reserved</td>
</tr>
<tr>
<td>80</td>
<td>System Control</td>
</tr>
<tr>
<td>81</td>
<td>System Power Down</td>
</tr>
<tr>
<td>82</td>
<td>System Sleep</td>
</tr>
<tr>
<td>83</td>
<td>System Wake Up</td>
</tr>
<tr>
<td>84</td>
<td>System Context Menu</td>
</tr>
<tr>
<td>85</td>
<td>System Main Menu</td>
</tr>
<tr>
<td>86</td>
<td>System App Menu</td>
</tr>
<tr>
<td>87</td>
<td>System Menu Help</td>
</tr>
<tr>
<td>88</td>
<td>System Menu Exit</td>
</tr>
<tr>
<td>89</td>
<td>System Menu Select</td>
</tr>
<tr>
<td>8A</td>
<td>System Menu Right</td>
</tr>
<tr>
<td>8B</td>
<td>System Menu Left</td>
</tr>
<tr>
<td>8C</td>
<td>System Menu Up</td>
</tr>
<tr>
<td>8D</td>
<td>System Menu Down</td>
</tr>
<tr>
<td>8E</td>
<td>System Cold Restart</td>
</tr>
<tr>
<td>8F</td>
<td>System Warm Restart</td>
</tr>
<tr>
<td>90</td>
<td>D-pad Up</td>
</tr>
<tr>
<td>91</td>
<td>D-pad Down</td>
</tr>
<tr>
<td>92</td>
<td>D-pad Right</td>
</tr>
<tr>
<td>93</td>
<td>D-pad Left</td>
</tr>
<tr>
<td>94-9F</td>
<td>Reserved</td>
</tr>
<tr>
<td>A0</td>
<td>System Dock</td>
</tr>
<tr>
<td>A1</td>
<td>System Undock</td>
</tr>
<tr>
<td>A2</td>
<td>System Setup</td>
</tr>
<tr>
<td>A3</td>
<td>System Break</td>
</tr>
<tr>
<td>A4</td>
<td>System Debugger Break</td>
</tr>
<tr>
<td>A5</td>
<td>Application Break</td>
</tr>
<tr>
<td>A6</td>
<td>Application Debugger Break</td>
</tr>
<tr>
<td>A7</td>
<td>System Speaker Mute</td>
</tr>
<tr>
<td>A8</td>
<td>System Hibernate</td>
</tr>
<tr>
<td>A9-AF</td>
<td>Reserved</td>
</tr>
<tr>
<td>B0</td>
<td>System Display Invert</td>
</tr>
<tr>
<td>B1</td>
<td>System Display Internal</td>
</tr>
<tr>
<td>B2</td>
<td>System Display External</td>
</tr>
<tr>
<td>B3</td>
<td>System Display Both</td>
</tr>
<tr>
<td>B4</td>
<td>System Display Dual</td>
</tr>
<tr>
<td>B5</td>
<td>System Display Toggle Int/Ext</td>
</tr>
<tr>
<td>B6</td>
<td>System Display Swap Primary/Secondary</td>
</tr>
<tr>
<td>B7</td>
<td>System Display LCD Autoscale</td>
</tr>
<tr>
<td>B8-FFFF</td>
<td>Reserved</td>
</tr>
</tbody>
</table>
<p><strong>Keyboard/Keypad Page(0x07)</strong></p>
<table>
<thead>
<tr>
<th>Usage ID (Dec)</th>
<th>Usage ID (Hex)</th>
<th>Usage Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>01</td>
<td>Keyboard ErrorRollOver9</td>
</tr>
<tr>
<td>2</td>
<td>02</td>
<td>Keyboard POSTFail9</td>
</tr>
<tr>
<td>3</td>
<td>03</td>
<td>Keyboard ErrorUndefined9</td>
</tr>
<tr>
<td>4</td>
<td>04</td>
<td>Keyboard a and A4</td>
</tr>
<tr>
<td>5</td>
<td>05</td>
<td>Keyboard b and B</td>
</tr>
<tr>
<td>6</td>
<td>06</td>
<td>Keyboard c and C4</td>
</tr>
<tr>
<td>7</td>
<td>07</td>
<td>Keyboard d and D</td>
</tr>
<tr>
<td>8</td>
<td>08</td>
<td>Keyboard e and E</td>
</tr>
<tr>
<td>9</td>
<td>09</td>
<td>Keyboard f and F</td>
</tr>
<tr>
<td>10</td>
<td>0A</td>
<td>Keyboard g and G</td>
</tr>
<tr>
<td>11</td>
<td>0B</td>
<td>Keyboard h and H</td>
</tr>
<tr>
<td>12</td>
<td>0C</td>
<td>Keyboard i and I</td>
</tr>
<tr>
<td>13</td>
<td>0D</td>
<td>Keyboard j and J</td>
</tr>
<tr>
<td>14</td>
<td>0E</td>
<td>Keyboard k and K</td>
</tr>
<tr>
<td>15</td>
<td>0F</td>
<td>Keyboard l and L</td>
</tr>
<tr>
<td>16</td>
<td>10</td>
<td>Keyboard m and M4</td>
</tr>
<tr>
<td>17</td>
<td>11</td>
<td>Keyboard n and N</td>
</tr>
<tr>
<td>18</td>
<td>12</td>
<td>Keyboard o and O4</td>
</tr>
<tr>
<td>19</td>
<td>13</td>
<td>Keyboard p and P4</td>
</tr>
<tr>
<td>20</td>
<td>14</td>
<td>Keyboard q and Q4</td>
</tr>
<tr>
<td>21</td>
<td>15</td>
<td>Keyboard r and R</td>
</tr>
<tr>
<td>22</td>
<td>16</td>
<td>Keyboard s and S4</td>
</tr>
<tr>
<td>23</td>
<td>17</td>
<td>Keyboard t and T</td>
</tr>
<tr>
<td>24</td>
<td>18</td>
<td>Keyboard u and U</td>
</tr>
<tr>
<td>25</td>
<td>19</td>
<td>Keyboard v and V</td>
</tr>
<tr>
<td>26</td>
<td>1A</td>
<td>Keyboard w and W4</td>
</tr>
<tr>
<td>27</td>
<td>1B</td>
<td>Keyboard x and X4</td>
</tr>
<tr>
<td>28</td>
<td>1C</td>
<td>Keyboard y and Y4</td>
</tr>
<tr>
<td>29</td>
<td>1D</td>
<td>Keyboard z and Z4</td>
</tr>
<tr>
<td>30</td>
<td>1E</td>
<td>Keyboard 1 and !4</td>
</tr>
<tr>
<td>31</td>
<td>1F</td>
<td>Keyboard 2 and @4</td>
</tr>
<tr>
<td>32</td>
<td>20</td>
<td>Keyboard 3 and #4</td>
</tr>
<tr>
<td>33</td>
<td>21</td>
<td>Keyboard 4 and $4</td>
</tr>
<tr>
<td>34</td>
<td>22</td>
<td>Keyboard 5 and %4</td>
</tr>
<tr>
<td>35</td>
<td>23</td>
<td>Keyboard 6 and ^4</td>
</tr>
<tr>
<td>36</td>
<td>24</td>
<td>Keyboard 7 and &amp;4</td>
</tr>
<tr>
<td>37</td>
<td>25</td>
<td>Keyboard 8 and *4</td>
</tr>
<tr>
<td>38</td>
<td>26</td>
<td>Keyboard 9 and (4</td>
</tr>
<tr>
<td>39</td>
<td>27</td>
<td>Keyboard 0 and )4</td>
</tr>
<tr>
<td>40</td>
<td>28</td>
<td>Keyboard Return (ENTER)5</td>
</tr>
<tr>
<td>41</td>
<td>29</td>
<td>Keyboard ESCAPE</td>
</tr>
<tr>
<td>42</td>
<td>2A</td>
<td>Keyboard DELETE (Backspace)13</td>
</tr>
<tr>
<td>43</td>
<td>2B</td>
<td>Keyboard Tab</td>
</tr>
<tr>
<td>44</td>
<td>2C</td>
<td>Keyboard Spacebar</td>
</tr>
<tr>
<td>45</td>
<td>2D</td>
<td>Keyboard - and (underscore)4</td>
</tr>
<tr>
<td>46</td>
<td>2E</td>
<td>Keyboard = and +4</td>
</tr>
<tr>
<td>47</td>
<td>2F</td>
<td>Keyboard [ and {4</td>
</tr>
<tr>
<td>48</td>
<td>30</td>
<td>Keyboard ] and }4</td>
</tr>
<tr>
<td>49</td>
<td>31</td>
<td>Keyboard \ and</td>
<td></td>
</tr>
<tr>
<td>50</td>
<td>32</td>
<td>Keyboard Non-US # and ~2</td>
</tr>
<tr>
<td>51</td>
<td>33</td>
<td>Keyboard ; and :4</td>
</tr>
<tr>
<td>52</td>
<td>34</td>
<td>Keyboard ‘ and “4</td>
</tr>
<tr>
<td>53</td>
<td>35</td>
<td>Keyboard Grave Accent and Tilde4</td>
</tr>
<tr>
<td>54</td>
<td>36</td>
<td>Keyboard , and &lt;4</td>
</tr>
<tr>
<td>55</td>
<td>37</td>
<td>Keyboard . and &gt;4</td>
</tr>
<tr>
<td>56</td>
<td>38</td>
<td>Keyboard / and ?4</td>
</tr>
<tr>
<td>57</td>
<td>39</td>
<td>Keyboard Caps Lock11</td>
</tr>
<tr>
<td>58</td>
<td>3A</td>
<td>Keyboard F1</td>
</tr>
<tr>
<td>59</td>
<td>3B</td>
<td>Keyboard F2</td>
</tr>
<tr>
<td>60</td>
<td>3C</td>
<td>Keyboard F3</td>
</tr>
<tr>
<td>61</td>
<td>3D</td>
<td>Keyboard F4</td>
</tr>
<tr>
<td>62</td>
<td>3E</td>
<td>Keyboard F5</td>
</tr>
<tr>
<td>63</td>
<td>3F</td>
<td>Keyboard F6</td>
</tr>
<tr>
<td>64</td>
<td>40</td>
<td>Keyboard F7</td>
</tr>
<tr>
<td>65</td>
<td>41</td>
<td>Keyboard F8</td>
</tr>
<tr>
<td>66</td>
<td>42</td>
<td>Keyboard F9</td>
</tr>
<tr>
<td>67</td>
<td>43</td>
<td>Keyboard F10</td>
</tr>
<tr>
<td>68</td>
<td>44</td>
<td>Keyboard F11</td>
</tr>
<tr>
<td>69</td>
<td>45</td>
<td>Keyboard F12</td>
</tr>
<tr>
<td>70</td>
<td>46</td>
<td>Keyboard PrintScreen1</td>
</tr>
<tr>
<td>71</td>
<td>47</td>
<td>Keyboard Scroll Lock11</td>
</tr>
<tr>
<td>72</td>
<td>48</td>
<td>Keyboard Pause1</td>
</tr>
<tr>
<td>73</td>
<td>49</td>
<td>Keyboard Insert1</td>
</tr>
<tr>
<td>74</td>
<td>4A</td>
<td>Keyboard Home1</td>
</tr>
<tr>
<td>75</td>
<td>4B</td>
<td>Keyboard PageUp1</td>
</tr>
<tr>
<td>76</td>
<td>4C</td>
<td>Keyboard Delete Forward1</td>
</tr>
<tr>
<td>77</td>
<td>4D</td>
<td>Keyboard End1</td>
</tr>
<tr>
<td>78</td>
<td>4E</td>
<td>Keyboard PageDown1</td>
</tr>
<tr>
<td>79</td>
<td>4F</td>
<td>Keyboard RightArrow1</td>
</tr>
<tr>
<td>80</td>
<td>50</td>
<td>Keyboard LeftArrow1</td>
</tr>
<tr>
<td>81</td>
<td>51</td>
<td>Keyboard DownArrow1</td>
</tr>
<tr>
<td>82</td>
<td>52</td>
<td>Keyboard UpArrow1</td>
</tr>
<tr>
<td>83</td>
<td>53</td>
<td>Keypad Num Lock and Clear11</td>
</tr>
<tr>
<td>84</td>
<td>54</td>
<td>Keypad /1</td>
</tr>
<tr>
<td>85</td>
<td>55</td>
<td>Keypad *</td>
</tr>
<tr>
<td>86</td>
<td>56</td>
<td>Keypad -</td>
</tr>
<tr>
<td>87</td>
<td>57</td>
<td>Keypad +</td>
</tr>
<tr>
<td>88</td>
<td>58</td>
<td>Keypad ENTER5</td>
</tr>
<tr>
<td>89</td>
<td>59</td>
<td>Keypad 1 and End</td>
</tr>
<tr>
<td>90</td>
<td>5A</td>
<td>Keypad 2 and Down Arrow</td>
</tr>
<tr>
<td>91</td>
<td>5B</td>
<td>Keypad 3 and PageDn</td>
</tr>
<tr>
<td>92</td>
<td>5C</td>
<td>Keypad 4 and Left Arrow</td>
</tr>
<tr>
<td>93</td>
<td>5D</td>
<td>Keypad 5</td>
</tr>
<tr>
<td>94</td>
<td>5E</td>
<td>Keypad 6 and Right Arrow</td>
</tr>
<tr>
<td>95</td>
<td>5F</td>
<td>Keypad 7 and Home</td>
</tr>
<tr>
<td>96</td>
<td>60</td>
<td>Keypad 8 and Up Arrow</td>
</tr>
<tr>
<td>97</td>
<td>61</td>
<td>Keypad 9 and PageUp</td>
</tr>
<tr>
<td>98</td>
<td>62</td>
<td>Keypad 0 and Insert</td>
</tr>
<tr>
<td>99</td>
<td>63</td>
<td>Keypad . and Delete</td>
</tr>
<tr>
<td>100</td>
<td>64</td>
<td>Keyboard Non-US \ and</td>
<td>3;6</td>
</tr>
<tr>
<td>101</td>
<td>65</td>
<td>Keyboard Application10</td>
</tr>
<tr>
<td>102</td>
<td>66</td>
<td>Keyboard Power</td>
</tr>
<tr>
<td>103</td>
<td>67</td>
<td>Keypad =</td>
</tr>
<tr>
<td>104</td>
<td>68</td>
<td>Keyboard F13</td>
</tr>
<tr>
<td>105</td>
<td>69</td>
<td>Keyboard F14</td>
</tr>
<tr>
<td>106</td>
<td>6A</td>
<td>Keyboard F15</td>
</tr>
<tr>
<td>107</td>
<td>6B</td>
<td>Keyboard F16</td>
</tr>
<tr>
<td>108</td>
<td>6C</td>
<td>Keyboard F17</td>
</tr>
<tr>
<td>109</td>
<td>6D</td>
<td>Keyboard F18</td>
</tr>
<tr>
<td>110</td>
<td>6E</td>
<td>Keyboard F19</td>
</tr>
<tr>
<td>111</td>
<td>6F</td>
<td>Keyboard F20</td>
</tr>
<tr>
<td>112</td>
<td>70</td>
<td>Keyboard F21</td>
</tr>
<tr>
<td>113</td>
<td>71</td>
<td>Keyboard F22</td>
</tr>
<tr>
<td>114</td>
<td>72</td>
<td>Keyboard F23</td>
</tr>
<tr>
<td>115</td>
<td>73</td>
<td>Keyboard F24</td>
</tr>
<tr>
<td>116</td>
<td>74</td>
<td>Keyboard Execute</td>
</tr>
<tr>
<td>117</td>
<td>75</td>
<td>Keyboard Help</td>
</tr>
<tr>
<td>118</td>
<td>76</td>
<td>Keyboard Menu</td>
</tr>
<tr>
<td>119</td>
<td>77</td>
<td>Keyboard Select</td>
</tr>
<tr>
<td>120</td>
<td>78</td>
<td>Keyboard Stop</td>
</tr>
<tr>
<td>121</td>
<td>79</td>
<td>Keyboard Again</td>
</tr>
<tr>
<td>122</td>
<td>7A</td>
<td>Keyboard Undo</td>
</tr>
<tr>
<td>123</td>
<td>7B</td>
<td>Keyboard Cut</td>
</tr>
<tr>
<td>124</td>
<td>7C</td>
<td>Keyboard Copy</td>
</tr>
<tr>
<td>125</td>
<td>7D</td>
<td>Keyboard Paste</td>
</tr>
<tr>
<td>126</td>
<td>7E</td>
<td>Keyboard Find</td>
</tr>
<tr>
<td>127</td>
<td>7F</td>
<td>Keyboard Mute</td>
</tr>
<tr>
<td>128</td>
<td>80</td>
<td>Keyboard Volume Up</td>
</tr>
<tr>
<td>129</td>
<td>81</td>
<td>Keyboard Volume Down</td>
</tr>
<tr>
<td>130</td>
<td>82</td>
<td>Keyboard Locking Caps Lock</td>
</tr>
<tr>
<td>131</td>
<td>83</td>
<td>Keyboard Locking Num Lock</td>
</tr>
<tr>
<td>132</td>
<td>84</td>
<td>Keyboard Locking Scroll Lock</td>
</tr>
<tr>
<td>133</td>
<td>85</td>
<td>Keypad Comma</td>
</tr>
<tr>
<td>134</td>
<td>86</td>
<td>Keypad Equal Sign</td>
</tr>
<tr>
<td>135</td>
<td>87</td>
<td>Keyboard International1</td>
</tr>
<tr>
<td>136</td>
<td>88</td>
<td>Keyboard International2</td>
</tr>
<tr>
<td>137</td>
<td>89</td>
<td>Keyboard International3</td>
</tr>
<tr>
<td>138</td>
<td>8A</td>
<td>Keyboard International4</td>
</tr>
<tr>
<td>139</td>
<td>8B</td>
<td>Keyboard International5</td>
</tr>
<tr>
<td>140</td>
<td>8C</td>
<td>Keyboard International6</td>
</tr>
<tr>
<td>141</td>
<td>8D</td>
<td>Keyboard International7</td>
</tr>
<tr>
<td>142</td>
<td>8E</td>
<td>Keyboard International8</td>
</tr>
<tr>
<td>143</td>
<td>8F</td>
<td>Keyboard International9</td>
</tr>
<tr>
<td>144</td>
<td>90</td>
<td>Keyboard LANG1</td>
</tr>
<tr>
<td>145</td>
<td>91</td>
<td>Keyboard LANG2</td>
</tr>
<tr>
<td>146</td>
<td>92</td>
<td>Keyboard LANG3</td>
</tr>
<tr>
<td>147</td>
<td>93</td>
<td>Keyboard LANG4</td>
</tr>
<tr>
<td>148</td>
<td>94</td>
<td>Keyboard LANG5</td>
</tr>
<tr>
<td>149</td>
<td>95</td>
<td>Keyboard LANG6</td>
</tr>
<tr>
<td>150</td>
<td>96</td>
<td>Keyboard LANG7</td>
</tr>
<tr>
<td>151</td>
<td>97</td>
<td>Keyboard LANG8</td>
</tr>
<tr>
<td>152</td>
<td>98</td>
<td>Keyboard LANG9</td>
</tr>
<tr>
<td>153</td>
<td>99</td>
<td>Keyboard Alternate Erase</td>
</tr>
<tr>
<td>154</td>
<td>9A</td>
<td>Keyboard SysReq/Attention</td>
</tr>
<tr>
<td>155</td>
<td>9B</td>
<td>Keyboard Cancel</td>
</tr>
<tr>
<td>156</td>
<td>9C</td>
<td>Keyboard Clear</td>
</tr>
<tr>
<td>157</td>
<td>9D</td>
<td>Keyboard Prior</td>
</tr>
<tr>
<td>158</td>
<td>9E</td>
<td>Keyboard Return</td>
</tr>
<tr>
<td>159</td>
<td>9F</td>
<td>Keyboard Separator</td>
</tr>
<tr>
<td>160</td>
<td>A0</td>
<td>Keyboard Out</td>
</tr>
<tr>
<td>161</td>
<td>A1</td>
<td>Keyboard Oper</td>
</tr>
<tr>
<td>162</td>
<td>A2</td>
<td>Keyboard Clear/Again</td>
</tr>
<tr>
<td>163</td>
<td>A3</td>
<td>Keyboard CrSel/Props</td>
</tr>
<tr>
<td>164</td>
<td>A4</td>
<td>Keyboard ExSel</td>
</tr>
<tr>
<td>165-175</td>
<td>A5-CF</td>
<td>Reserved</td>
</tr>
<tr>
<td>176</td>
<td>B0</td>
<td>Keypad 00</td>
</tr>
<tr>
<td>177</td>
<td>B1</td>
<td>Keypad 000</td>
</tr>
<tr>
<td>178</td>
<td>B2</td>
<td>Thousands Separator</td>
</tr>
<tr>
<td>179</td>
<td>B3</td>
<td>Decimal Separator</td>
</tr>
<tr>
<td>180</td>
<td>B4</td>
<td>Currency Unit</td>
</tr>
<tr>
<td>181</td>
<td>B5</td>
<td>Currency Sub-unit</td>
</tr>
<tr>
<td>182</td>
<td>B6</td>
<td>Keypad (</td>
</tr>
<tr>
<td>183</td>
<td>B7</td>
<td>Keypad )</td>
</tr>
<tr>
<td>184</td>
<td>B8</td>
<td>Keypad {</td>
</tr>
<tr>
<td>185</td>
<td>B9</td>
<td>Keypad }</td>
</tr>
<tr>
<td>186</td>
<td>BA</td>
<td>Keypad Tab</td>
</tr>
<tr>
<td>187</td>
<td>BB</td>
<td>Keypad Backspace</td>
</tr>
<tr>
<td>188</td>
<td>BC</td>
<td>Keypad A</td>
</tr>
<tr>
<td>189</td>
<td>BD</td>
<td>Keypad B</td>
</tr>
<tr>
<td>190</td>
<td>BE</td>
<td>Keypad C</td>
</tr>
<tr>
<td>191</td>
<td>BF</td>
<td>Keypad D</td>
</tr>
<tr>
<td>192</td>
<td>C0</td>
<td>Keypad E</td>
</tr>
<tr>
<td>193</td>
<td>C1</td>
<td>Keypad F</td>
</tr>
<tr>
<td>194</td>
<td>C2</td>
<td>Keypad XOR</td>
</tr>
<tr>
<td>195</td>
<td>C3</td>
<td>Keypad ^</td>
</tr>
<tr>
<td>196</td>
<td>C4</td>
<td>Keypad %</td>
</tr>
<tr>
<td>197</td>
<td>C5</td>
<td>Keypad &lt;</td>
</tr>
<tr>
<td>198</td>
<td>C6</td>
<td>Keypad &gt;</td>
</tr>
<tr>
<td>199</td>
<td>C7</td>
<td>Keypad &amp;</td>
</tr>
<tr>
<td>200</td>
<td>C8</td>
<td>Keypad &amp;&amp;</td>
</tr>
<tr>
<td>201</td>
<td>C9</td>
<td>Keypad |</td>
</tr>
<tr>
<td>202</td>
<td>CA</td>
<td>Keypad ||</td>
</tr>
<tr>
<td>203</td>
<td>CB</td>
<td>Keypad :</td>
</tr>
<tr>
<td>204</td>
<td>CC</td>
<td>Keypad #</td>
</tr>
<tr>
<td>205</td>
<td>CD</td>
<td>Keypad Space</td>
</tr>
<tr>
<td>206</td>
<td>CE</td>
<td>Keypad @</td>
</tr>
<tr>
<td>207</td>
<td>CF</td>
<td>Keypad !</td>
</tr>
<tr>
<td>208</td>
<td>D0</td>
<td>Keypad Memory Store</td>
</tr>
<tr>
<td>209</td>
<td>D1</td>
<td>Keypad Memory Recall</td>
</tr>
<tr>
<td>210</td>
<td>D2</td>
<td>Keypad Memory Clear</td>
</tr>
<tr>
<td>211</td>
<td>D3</td>
<td>Keypad Memory Add</td>
</tr>
<tr>
<td>212</td>
<td>D4</td>
<td>Keypad Memory Subtract</td>
</tr>
<tr>
<td>213</td>
<td>D5</td>
<td>Keypad Memory Multiply</td>
</tr>
<tr>
<td>214</td>
<td>D6</td>
<td>Keypad Memory Divide</td>
</tr>
<tr>
<td>215</td>
<td>D7</td>
<td>Keypad +/-</td>
</tr>
<tr>
<td>216</td>
<td>D8</td>
<td>Keypad Clear</td>
</tr>
<tr>
<td>217</td>
<td>D9</td>
<td>Keypad Clear Entry</td>
</tr>
<tr>
<td>218</td>
<td>DA</td>
<td>Keypad Binary</td>
</tr>
<tr>
<td>219</td>
<td>DB</td>
<td>Keypad Octal</td>
</tr>
<tr>
<td>220</td>
<td>DC</td>
<td>Keypad Decimal</td>
</tr>
<tr>
<td>221</td>
<td>DD</td>
<td>Keypad Hexadecimal</td>
</tr>
<tr>
<td>222</td>
<td>DE-DF</td>
<td>Reserved</td>
</tr>
<tr>
<td>224</td>
<td>E0</td>
<td>Keyboard LeftControl</td>
</tr>
<tr>
<td>225</td>
<td>E1</td>
<td>Keyboard LeftShift</td>
</tr>
<tr>
<td>226</td>
<td>E2</td>
<td>Keyboard LeftAlt</td>
</tr>
<tr>
<td>227</td>
<td>E3</td>
<td>Keyboard Left GUI</td>
</tr>
<tr>
<td>228</td>
<td>E4</td>
<td>Keyboard RightControl</td>
</tr>
<tr>
<td>229</td>
<td>E5</td>
<td>Keyboard RightShift</td>
</tr>
<tr>
<td>230</td>
<td>E6</td>
<td>Keyboard RightAlt</td>
</tr>
<tr>
<td>231</td>
<td>E7</td>
<td>Keyboard Right GUI</td>
</tr>
</tbody>
</table>
<p><strong>LED Usage Page(0x08)</strong></p>
<table>
<thead>
<tr>
<th>Usage ID (Hex)</th>
<th>Usage Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>00</td>
<td>Undefined</td>
</tr>
<tr>
<td>01</td>
<td>Num Lock</td>
</tr>
<tr>
<td>02</td>
<td>Caps Lock</td>
</tr>
<tr>
<td>03</td>
<td>Scroll Lock</td>
</tr>
<tr>
<td>04</td>
<td>Compose</td>
</tr>
<tr>
<td>05</td>
<td>Kana</td>
</tr>
<tr>
<td>06</td>
<td>Power</td>
</tr>
<tr>
<td>07</td>
<td>Shift</td>
</tr>
<tr>
<td>08</td>
<td>Do Not Disturb</td>
</tr>
<tr>
<td>09</td>
<td>Mute</td>
</tr>
<tr>
<td>0A</td>
<td>Tone Enable</td>
</tr>
<tr>
<td>0B</td>
<td>High Cut Filter</td>
</tr>
<tr>
<td>0C</td>
<td>Low Cut Filter</td>
</tr>
<tr>
<td>0D</td>
<td>Equalizer Enable</td>
</tr>
<tr>
<td>0E</td>
<td>Sound Field On</td>
</tr>
<tr>
<td>0F</td>
<td>Surround On</td>
</tr>
<tr>
<td>10</td>
<td>Repeat</td>
</tr>
<tr>
<td>11</td>
<td>Stereo</td>
</tr>
<tr>
<td>12</td>
<td>Sampling Rate Detect</td>
</tr>
<tr>
<td>13</td>
<td>Spinning</td>
</tr>
<tr>
<td>14</td>
<td>CAV</td>
</tr>
<tr>
<td>15</td>
<td>CLV</td>
</tr>
<tr>
<td>16</td>
<td>Recording Format Detect</td>
</tr>
<tr>
<td>17</td>
<td>Off-Hook</td>
</tr>
<tr>
<td>18</td>
<td>Ring</td>
</tr>
<tr>
<td>19</td>
<td>Message Waiting</td>
</tr>
<tr>
<td>1A</td>
<td>Data Mode</td>
</tr>
<tr>
<td>1B</td>
<td>Battery Operation</td>
</tr>
<tr>
<td>1C</td>
<td>Battery OK</td>
</tr>
<tr>
<td>1D</td>
<td>Battery Low</td>
</tr>
<tr>
<td>1E</td>
<td>Speaker</td>
</tr>
</tbody>
</table>
<p><strong>Button Usage Page(0x09)</strong></p>
<table>
<thead>
<tr>
<th>Usage ID (Hex)</th>
<th>Usage Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>00</td>
<td>No button pressed</td>
</tr>
<tr>
<td>01</td>
<td>Button 1 (primary/trigger)</td>
</tr>
<tr>
<td>02</td>
<td>Button 2 (secondary)</td>
</tr>
<tr>
<td>03</td>
<td>Button 3 (tertiary)</td>
</tr>
<tr>
<td>04</td>
<td>Button 4 See Note</td>
</tr>
</tbody>
</table>
<p><strong>Consumer Usage Page(0x0C)</strong></p>
<table>
<thead>
<tr>
<th>Usage ID (Hex)</th>
<th>Usage Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>00</td>
<td>Unassigned</td>
</tr>
<tr>
<td>01</td>
<td>Consumer Control</td>
</tr>
<tr>
<td>02</td>
<td>Numeric Key Pad</td>
</tr>
<tr>
<td>03</td>
<td>Programmable Buttons</td>
</tr>
<tr>
<td>04</td>
<td>Microphone</td>
</tr>
<tr>
<td>05</td>
<td>Headphone</td>
</tr>
<tr>
<td>06</td>
<td>Graphic Equalizer</td>
</tr>
<tr>
<td>07-1F</td>
<td>Reserved</td>
</tr>
<tr>
<td>20</td>
<td>+10</td>
</tr>
<tr>
<td>21</td>
<td>+100</td>
</tr>
<tr>
<td>22</td>
<td>AM/PM</td>
</tr>
<tr>
<td>23-3F</td>
<td>Reserved</td>
</tr>
<tr>
<td>30</td>
<td>Power</td>
</tr>
<tr>
<td>31</td>
<td>Reset</td>
</tr>
<tr>
<td>32</td>
<td>Sleep</td>
</tr>
<tr>
<td>33</td>
<td>Sleep After</td>
</tr>
<tr>
<td>34</td>
<td>Sleep Mode</td>
</tr>
<tr>
<td>35</td>
<td>Illumination</td>
</tr>
<tr>
<td>36</td>
<td>Function Buttons</td>
</tr>
<tr>
<td>37-3F</td>
<td>Reserved</td>
</tr>
<tr>
<td>40</td>
<td>Menu</td>
</tr>
<tr>
<td>41</td>
<td>Menu Pick</td>
</tr>
<tr>
<td>42</td>
<td>Menu Up</td>
</tr>
<tr>
<td>43</td>
<td>Menu Down</td>
</tr>
<tr>
<td>44</td>
<td>Menu Left</td>
</tr>
<tr>
<td>45</td>
<td>Menu Right</td>
</tr>
<tr>
<td>46</td>
<td>Menu Es</td>
</tr>
<tr>
<td>47</td>
<td>Menu Value Increase</td>
</tr>
<tr>
<td>48</td>
<td>Menu Value Decrease</td>
</tr>
<tr>
<td>49-5F</td>
<td>Reserved</td>
</tr>
<tr>
<td>60</td>
<td>Data On Screen</td>
</tr>
<tr>
<td>61</td>
<td>Closed</td>
</tr>
</tbody>
</table>]]></description>
    <pubDate>Tue, 18 Feb 2025 09:48:29 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mcu/304.html</guid>
</item>
<item>
    <title>前端JS使用Zip打包下载</title>
    <link>https://www.vsay.net/web/303.html</link>
    <description><![CDATA[<p>实现 ZIP 打包并提供下载功能，可以使用前端 JavaScript 实现，结合 <code>JSZip</code> 库完成文件的压缩和打包。以下是实现步骤：</p>
<hr />
<h2><strong>步骤 1：引入 JSZip 库</strong></h2>
<p>通过 npm 安装或直接使用 CDN 引入 JSZip：</p>
<h3>安装 JSZip：</h3>
<pre><code class="language-bash">npm install jszip</code></pre>
<h3>或通过 CDN 引入：</h3>
<pre><code class="language-html">&lt;script src="https://cdn.jsdelivr.net/npm/jszip"&gt;&lt;/script&gt;</code></pre>
<hr />
<h2><strong>步骤 2：实现 ZIP 打包和下载</strong></h2>
<p>以下是核心代码，用于将多个文件打包成 ZIP 并提供下载：</p>
<pre><code class="language-html">&lt;input type="file" id="fileInput" multiple /&gt;
&lt;button id="downloadZip"&gt;下载 ZIP&lt;/button&gt;
&lt;script src="https://cdn.jsdelivr.net/npm/jszip"&gt;&lt;/script&gt;
&lt;script&gt;
  const fileInput = document.getElementById("fileInput");
  const downloadZipButton = document.getElementById("downloadZip");

  downloadZipButton.addEventListener("click", async () =&gt; {
    const files = fileInput.files;

    if (!files.length) {
      alert("请选择要打包的文件！");
      return;
    }

    const zip = new JSZip();

    // 将文件添加到 ZIP 中
    for (const file of files) {
      const fileData = await file.arrayBuffer(); // 读取文件内容为 ArrayBuffer
      zip.file(file.name, fileData); // 添加文件到 ZIP
    }

    // 生成 ZIP 文件
    zip.generateAsync({ type: "blob" }).then((content) =&gt; {
      // 创建下载链接
      const a = document.createElement("a");
      const url = URL.createObjectURL(content);
      a.href = url;
      a.download = "files.zip";
      a.click();
      URL.revokeObjectURL(url); // 释放 URL
    });
  });
&lt;/script&gt;</code></pre>
<hr />
<h2><strong>功能解释</strong></h2>
<ol>
<li>
<p><strong>文件选择：</strong></p>
<ul>
<li>使用 <code>&lt;input type="file" multiple /&gt;</code> 元素允许用户选择多个文件。</li>
<li><code>fileInput.files</code> 提供了用户选择的文件列表。</li>
</ul>
</li>
<li>
<p><strong>JSZip 创建 ZIP 文件：</strong></p>
<ul>
<li><code>zip.file(name, data)</code> 方法将文件添加到 ZIP 包中。</li>
<li><code>generateAsync({ type: "blob" })</code> 生成 ZIP 文件，并返回一个 <code>Blob</code> 对象。</li>
</ul>
</li>
<li>
<p><strong>下载 ZIP 文件：</strong></p>
<ul>
<li>使用 <code>URL.createObjectURL</code> 将 <code>Blob</code> 转化为临时 URL。</li>
<li>创建一个 <code>&lt;a&gt;</code> 元素，设置其 <code>href</code> 属性为生成的 URL，并触发下载。</li>
</ul>
</li>
</ol>
<hr />
<h2><strong>步骤 3：扩展功能（可选）</strong></h2>
<h3><strong>a. 支持嵌套文件夹</strong></h3>
<p>如果需要支持文件夹结构，可以使用 <code>zip.folder(folderName)</code> 创建文件夹，再将文件添加到该文件夹中。</p>
<pre><code class="language-javascript">const folder = zip.folder("myFolder");
folder.file("example.txt", "Hello, World!");</code></pre>
<h3><strong>b. 显示打包进度</strong></h3>
<p>可以在 <code>generateAsync</code> 的 <code>onUpdate</code> 回调中显示打包进度。</p>
<pre><code class="language-javascript">zip.generateAsync({ type: "blob" }, (metadata) =&gt; {
  console.log(`压缩进度: ${metadata.percent.toFixed(2)}%`);
});</code></pre>
<h3><strong>c. 支持图片、音频、视频等文件</strong></h3>
<p><code>JSZip</code> 支持多种文件类型，可以直接将文件内容作为 <code>Blob</code> 或 <code>ArrayBuffer</code> 添加到 ZIP 中。</p>]]></description>
    <pubDate>Wed, 01 Jan 2025 09:42:14 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/303.html</guid>
</item>
<item>
    <title>奇特的一个需求，在指定时间内随机间隔跑完指定的步数，使用js实现方法</title>
    <link>https://www.vsay.net/web/302.html</link>
    <description><![CDATA[<h3>前言</h3>
<p>这是一个很奇特的需求<br />
在指定的是个时间内<br />
使用随机步数，跑完指定数量的回调。<br />
听起来可能比较绕口<br />
那么这个函数它有什么用呢</p>
<h3>用途</h3>
<p>红包雨：假设有1000个红包需要在60秒内下完<br />
随机请求：假设有10个请求，需要在10分钟内做完，并且间隔时间随机。<br />
...<br />
用途还是很广的，看你需求了。</p>
<h3>代码</h3>
<pre><code class="language-javascript">function generateWithCallback(count, totalTime, callback) {
    if (typeof callback !== "function") {
        throw new Error("Callback must be a function");
    }

    const intervals = [];
    let sum = 0;

    // 1. 生成随机间隔比例
    for (let i = 0; i &lt; count; i++) {
        const rand = Math.random();
        intervals.push(rand);
        sum += rand;
    }

    // 2. 归一化比例到总时间
    for (let i = 0; i &lt; intervals.length; i++) {
        intervals[i] = (intervals[i] / sum) * totalTime;
    }

    let currentIndex = 0; // 当前回调索引
    let isPaused = false; // 控制是否暂停
    let timeoutId = null; // 保存当前的 `setTimeout` ID，用于清理或控制

    const triggerCallback = () =&gt; {
        if (currentIndex &lt; count &amp;&amp; !isPaused) {
            callback(currentIndex); // 调用回调函数，传入当前索引
            const nextInterval = intervals[currentIndex];
            currentIndex++;
            timeoutId = setTimeout(triggerCallback, nextInterval); // 等待下一个随机间隔
        }
    };

    // 提供暂停和恢复方法
    return {
        start() {
            if (!isPaused) {
                triggerCallback();
            }
        },
        pause() {
            isPaused = true;
            if (timeoutId) {
                clearTimeout(timeoutId); // 停止当前的 `setTimeout`
                timeoutId = null;
            }
        },
        resume() {
            if (isPaused) {
                isPaused = false;
                triggerCallback(); // 恢复从当前索引继续
            }
        },
    };
}

// 使用示例
const generator = generateWithCallback(
    10, // 生成 10 次回调
    60000, // 总时间（1分钟）
    (index) =&gt; {
        console.log(`Callback at index: ${index}`);
    }
);

// 启动生成
generator.start();

// 暂停和恢复示例
setTimeout(() =&gt; {
    console.log("Pausing...");
    generator.pause();
}, 15000); // 15秒后暂停

setTimeout(() =&gt; {
    console.log("Resuming...");
    generator.resume();
}, 30000); // 30秒后恢复</code></pre>
<p>拿走不谢~</p>]]></description>
    <pubDate>Wed, 01 Jan 2025 09:34:32 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/302.html</guid>
</item>
<item>
    <title>go golang 配置国内源，解决go get卡住</title>
    <link>https://www.vsay.net/mycode/301.html</link>
    <description><![CDATA[<h3>国内七牛云</h3>
<p><a href="https://goproxy.cn">https://goproxy.cn</a></p>
<h3>国内阿里云</h3>
<p><a href="https://developer.aliyun.com/mirror/go">https://developer.aliyun.com/mirror/go</a></p>
<h3>官方源，备用</h3>
<p>direct</p>
<p>执行下方命令</p>
<pre><code class="language-bash">go env -w GOPROXY=https://goproxy.cn,https://developer.aliyun.com/mirror/go,direct</code></pre>]]></description>
    <pubDate>Tue, 10 Dec 2024 11:09:28 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/301.html</guid>
</item>
<item>
    <title>js 将字符串分割成数组时emoji表情被分割成了乱码的解决办法</title>
    <link>https://www.vsay.net/web/300.html</link>
    <description><![CDATA[<h3>直接看图吧</h3>
<h4>直接使用索引：</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202412/b2761733299808.png"><img src="https://www.vsay.net/content/uploadfile/202412/b2761733299808.png" alt="" /></a></p>
<h4>使用split：</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202412/95d61733299850.png"><img src="https://www.vsay.net/content/uploadfile/202412/95d61733299850.png" alt="" /></a></p>
<h4>使用charAt：</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202412/ede31733299920.png"><img src="https://www.vsay.net/content/uploadfile/202412/ede31733299920.png" alt="" /></a></p>
<h3>解决办法</h3>
<p><a href="https://www.vsay.net/content/uploadfile/202412/075c1733300174.png"><img src="https://www.vsay.net/content/uploadfile/202412/075c1733300174.png" alt="" /></a></p>
<pre><code class="language-javascript">// 文本分割
const splitGraphemes = (input) =&gt; {
    if (typeof Intl !== 'undefined' &amp;&amp; Intl.Segmenter) {
        const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
        return Array.from(segmenter.segment(input), (segment) =&gt; segment.segment);
    } else {
        // 回退到正则分割
        const graphemeRegex = /(?:\p{Extended_Pictographic}(?:\p{Emoji_Modifier}|\u200D\p{Extended_Pictographic})*|[\s\S])/gu;
        return [...input.matchAll(graphemeRegex)].map(match =&gt; match[0]);
    }
}</code></pre>
<h4>解释</h4>
<p><strong><code>Intl.Segmenter</code></strong> 是一个国际化 API，用于根据语言环境和特定的粒度分割字符串。</p>
<ul>
<li>这里判断当前环境是否支持 <code>Intl.Segmenter</code>：
<ul>
<li>如果支持，创建一个 <code>Segmenter</code> 实例：
<pre><code class="language-javascript">const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });</code></pre></li>
<li><code>'en'</code>：指定语言环境为英文（但由于 <code>grapheme</code> 是独立于语言的，因此适用于多种语言）。</li>
<li><code>{ granularity: 'grapheme' }</code>：指定粒度为字素（<code>grapheme</code>），即按<strong>人类可见的字符</strong>进行分割。</li>
<li>然后通过 <code>Array.from</code> 遍历 <code>segmenter.segment(input)</code> 返回的迭代器，将每个 <code>segment</code> 对象的 <code>segment</code> 属性提取到数组中。</li>
</ul></li>
</ul>
<hr />
<p>如果 <code>Intl.Segmenter</code> 不可用，则使用正则表达式进行分割：</p>
<pre><code class="language-javascript">const graphemeRegex = /(?:\p{Extended_Pictographic}(?:\p{Emoji_Modifier}|\u200D\p{Extended_Pictographic})*|[\s\S])/gu;
return [...input.matchAll(graphemeRegex)].map(match =&gt; match[0]);</code></pre>
<ul>
<li><strong><code>\p{Extended_Pictographic}</code></strong>：匹配扩展图形字符（主要是表情符号和特殊符号）。</li>
<li><strong><code>(?:...)</code></strong>：非捕获组，用于将匹配的表情符号与修饰符或零宽连接符（<code>u200D</code>）组合起来。</li>
<li><strong><code>[\s\S]</code></strong>：匹配所有其他字符（如果不是表情符号）。</li>
<li><strong><code>/gu</code></strong>：
<ul>
<li><code>g</code>：全局匹配，匹配所有字素。</li>
<li><code>u</code>：启用 Unicode 模式，确保正确处理 Unicode 字符。</li>
</ul></li>
</ul>
<p><strong>步骤：</strong></p>
<ol>
<li>使用 <code>input.matchAll(graphemeRegex)</code> 获取所有匹配的字素。</li>
<li>将每个匹配的结果提取为一个字符串并返回为数组。</li>
</ol>
<ul>
<li><strong><code>Intl.Segmenter</code></strong> 是更现代、准确的方式，尤其适用于需要国际化支持的环境。</li>
<li>如果 <code>Intl.Segmenter</code> 不可用，则使用正则表达式作为兼容方案。</li>
</ul>
<p><strong>适用场景：</strong></p>
<ul>
<li>处理用户输入中的 Emoji 或复合字符。</li>
<li>文本编辑器、聊天应用等需要逐字素处理的应用。</li>
</ul>]]></description>
    <pubDate>Wed, 04 Dec 2024 16:05:41 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/300.html</guid>
</item>
<item>
    <title>thinkphp tp6 EXP表达式 的几种写法 is null not null数组条件的写法</title>
    <link>https://www.vsay.net/php/298.html</link>
    <description><![CDATA[<p>实在不知道官方的文档是拿脚写的还是不会写</p>
<p>在使用二维数组做条件的时候，如下：</p>
<pre><code class="language-php">$where = [];
$where[] = ['a','=','1'];
$where[] = ['b','=','2'];
$where[] = ['a','=','null'||NULL]; //错误</code></pre>
<p>查阅了很多资料都没有一个说法，官方文档写的简之又简，等于没说。</p>
<p>最后在文档评论下看到了写法：</p>
<pre><code class="language-php">数组 IS NULL 和 IS NOT NULL 用法
$condition=[
    ['remark', "not null",''],
    ['status', "null",'']
];</code></pre>
<p>你妈妈的吻啊，绝了！</p>
<p>顺便整理一下</p>
<h4>where方法的写法</h4>
<pre><code class="language-php">where('email','null');
where('email','not null');
where('id','exp',' IN (1,3,8) ')
whereExp('id', 'IN (1,3,8) ')
whereRaw('email is null')</code></pre>
<h4>数组条件</h4>
<pre><code class="language-php">$where[] = ['id','null',''];
$where[] = ['id','not null',''];
$where[] = ['id','exp','IN (1,3,8)'];</code></pre>]]></description>
    <pubDate>Thu, 28 Nov 2024 13:18:31 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/php/298.html</guid>
</item>
<item>
    <title>vue webpack 编译混淆代码，你发布的程序可能完全暴露了。</title>
    <link>https://www.vsay.net/web/297.html</link>
    <description><![CDATA[<p>今天才发现使用vue build后在浏览器的sources下竟然可以看到完整的vue目录和原始代码，真的是惊呆了。。。</p>
<p>说说怎么配置vue.config.js让你的代码混淆加密一下子</p>
<h4>使用webpack-obfuscator</h4>
<p>1.安装</p>
<pre><code class="language-bash">npm install webpack-obfuscator --save-dev</code></pre>
<p>2.配置vue.config</p>
<pre><code class="language-javascript">const WebpackObfuscator = require('webpack-obfuscator');

module.exports = defineConfig({
  // ...
  productionSourceMap: false, // 禁用 Source Map
  configureWebpack: (config) =&gt; {
    if (process.env.NODE_ENV === 'production') {
      // 在生产环境启用代码混淆
      config.plugins.push(
        new WebpackObfuscator(
          {
            rotateStringArray: true,
          },
          ['*.js'] // 仅混淆指定文件
        )
      )
    }
  }
})</code></pre>
<h4>这样build后代码就被混淆且目录结构也没有了</h4>
<p><a href="https://www.vsay.net/content/uploadfile/202411/thum-be661732505028.png"><img src="https://www.vsay.net/content/uploadfile/202411/be661732505028.png" alt="" /></a></p>]]></description>
    <pubDate>Mon, 25 Nov 2024 11:18:07 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/297.html</guid>
</item>
<item>
    <title>使用chrome调试手机微信内置浏览器</title>
    <link>https://www.vsay.net/mycode/296.html</link>
    <description><![CDATA[<p>在Chrome中调试Android微信浏览器中的网页</p>
<h4>环境准备：USB数据线连接电脑和手机，手机要开启Debug调试模式。</h4>
<p>1、在Chrome中打开：chrome://inspect/#devices</p>
<p>2、微信打开网页： <a href="http://debugxweb.qq.com/?inspector=true">http://debugxweb.qq.com/?inspector=true</a>   （这一步是关键）</p>
<p>（也可直接扫以下二维码打开，该二维码即以上网址）<br />
<a href="https://www.vsay.net/content/uploadfile/202411/924d1732462094.png"><img src="https://www.vsay.net/content/uploadfile/202411/924d1732462094.png" alt="" /></a></p>
<p>出现以下界面，点击要调试网页的inspect即可：</p>
<p><a href="https://www.vsay.net/content/uploadfile/202411/thum-0bfe1732462107.png"><img src="https://www.vsay.net/content/uploadfile/202411/0bfe1732462107.png" alt="" /></a></p>]]></description>
    <pubDate>Sun, 24 Nov 2024 23:24:56 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/296.html</guid>
</item>
<item>
    <title>js 怎么判定一个日期是否是正确的日期？</title>
    <link>https://www.vsay.net/web/295.html</link>
    <description><![CDATA[<h3>方法一：<code>Date</code> 对象和 <code>isNaN</code></h3>
<p>使用 <code>Date</code> 对象来尝试创建日期，然后检查是否为 <code>NaN</code>（非数字）。</p>
<pre><code class="language-javascript">function isValidDate(date) {
    const parsedDate = new Date(date);
    return parsedDate instanceof Date &amp;&amp; !isNaN(parsedDate);
}

console.log(isValidDate("2023-12-01")); // true
console.log(isValidDate("invalid-date")); // false</code></pre>
<h3>方法二：正则表达式</h3>
<p>可以用正则表达式来验证日期格式（例如 YYYY-MM-DD、DD/MM/YYYY）。</p>
<pre><code class="language-javascript">function isDateString(dateStr) {
    const datePattern = /^\d{4}-\d{2}-\d{2}$/;  // 示例: YYYY-MM-DD 格式
    return datePattern.test(dateStr);
}

console.log(isDateString("2023-12-01")); // true
console.log(isDateString("12/01/2023")); // false</code></pre>
<h3>方法三：结合 <code>isDateString</code> 和 <code>isValidDate</code></h3>
<p>可以先用正则表达式检查格式，再用 <code>isValidDate</code> 检查是否为有效日期。</p>
<pre><code class="language-javascript">function isDate(dateStr) {
    const datePattern = /^\d{4}-\d{2}-\d{2}$/;  // YYYY-MM-DD 格式
    if (!datePattern.test(dateStr)) return false;
    return isValidDate(dateStr);
}

console.log(isDate("2023-12-01")); // true
console.log(isDate("12/01/2023")); // false</code></pre>
<h3>方法四：使用 <code>moment.js</code></h3>
<p>如果项目中使用了 <code>moment.js</code>，可以更方便地判断日期：</p>
<pre><code class="language-javascript">// 引入 moment.js
// const moment = require('moment');  // 适用于 Node.js 环境
function isMomentDate(dateStr) {
    return moment(dateStr, "YYYY-MM-DD", true).isValid();
}

console.log(isMomentDate("2023-12-01")); // true
console.log(isMomentDate("12/01/2023")); // false</code></pre>]]></description>
    <pubDate>Wed, 06 Nov 2024 13:42:43 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/295.html</guid>
</item>
<item>
    <title>前端禁用微信浏览器字体调整，用户设置了系统字体大小，导致微信内嵌h5页面字体大小改变</title>
    <link>https://www.vsay.net/web/294.html</link>
    <description><![CDATA[<p>前端按设计稿开发，但是用户得手机调整了文字字体的大小，使得默认字体改的很大，我们APP端都对此进行了限制。但是安卓微信小程序默认没有限制。对h5页面布局产生了影响。</p>
<h3>android下</h3>
<p>App.vue</p>
<pre><code class="language-javascript">&lt;script&gt;
export default {
  name: 'App',
  mounted() {
    const handleFontSize = () =&gt; {
      // 设置网页字体为默认大小
      WeixinJSBridge.invoke('setFontSizeCallback', {fontSize: 0});
      // 重写设置网页字体大小的事件
      WeixinJSBridge.on('menu:setfont', function() {
        WeixinJSBridge.invoke('setFontSizeCallback', {fontSize: 0});
      });
    };
    try {
      // 安卓 禁止用户自定义设置字体大小
      if (typeof WeixinJSBridge == 'object' &amp;&amp; typeof WeixinJSBridge.invoke == 'function') {
        handleFontSize();
      } else {
        if (document.addEventListener) {
          document.addEventListener('WeixinJSBridgeReady', handleFontSize, false);
        } else if (document.attachEvent) {
          document.attachEvent('WeixinJSBridgeReady', handleFontSize);
          document.attachEvent('onWeixinJSBridgeReady', handleFontSize);
        }
      }
    } catch (error) {}
  }
};
&lt;/script&gt;</code></pre>
<h3>ios下</h3>
<pre><code class="language-css">&lt;style lang="less"&gt;
body {
  /* IOS 禁止用户自定义设置字体大小 */
  -webkit-text-size-adjust: 100% !important;
  text-size-adjust: 100% !important;
  -moz-text-size-adjust: 100% !important;
}
&lt;/style&gt;</code></pre>]]></description>
    <pubDate>Mon, 21 Oct 2024 10:47:51 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/294.html</guid>
</item>
<item>
    <title>hi3798mv100编译ch341串口驱动</title>
    <link>https://www.vsay.net/mycode/293.html</link>
    <description><![CDATA[<blockquote>
<p>这是一场持久战<br />
查阅了无数资料<br />
经历了无数失败<br />
直到...<br />
<strong>[373010.516498] usbcore: registered new interface driver ch341<br />
[373010.516706] usbserial: USB Serial support registered for ch341-uart</strong><br />
<a href="https://www.vsay.net/content/uploadfile/202410/thum-545e1728743875.png"><img src="https://www.vsay.net/content/uploadfile/202410/545e1728743875.png" alt="" /></a></p>
</blockquote>
<p>首先感谢各位前辈的分享：<br />
@teasiu <a href="https://www.znds.com/tv-1207323-1-1.html">https://www.znds.com/tv-1207323-1-1.html</a><br />
@AyFun <a href="https://github.com/AyFun/HiSTBLinux_drive">https://github.com/AyFun/HiSTBLinux_drive</a><br />
@07bug <a href="https://github.com/07bug/HiSTBLinuxV100R005C00SPC060">https://github.com/07bug/HiSTBLinuxV100R005C00SPC060</a><br />
@三见故山秋 <a href="https://blog.csdn.net/m0_48931482/article/details/136988649?spm=1001.2014.3001.5502">https://blog.csdn.net/m0_48931482/article/details/136988649?spm=1001.2014.3001.5502</a></p>
<p>那就开始吧。</p>
<h4>盒子的基本信息：</h4>
<pre><code>板型名称 : hi3798mv100_hi3798mdmo1d
CPU 信息 : hi3798mv100-series@4核处理器 | armv7l架构
系统版本 : Ubuntu 20.04.6 LTS | V20230401-4.4.35_ecoo_81092768-32</code></pre>
<h4>编译所用的pc系统信息，是腾讯云的服务器...</h4>
<pre><code>Distributor ID: Ubuntu
Description:    Ubuntu 20.04 LTS
Release:    20.04</code></pre>
<h2>一、下载SDK</h2>
<p><a href="https://github.com/07bug/HiSTBLinuxV100R005C00SPC060">https://github.com/07bug/HiSTBLinuxV100R005C00SPC060</a><br />
解压到root目录下。<br />
<a href="https://www.vsay.net/content/uploadfile/202410/thum-bd411728710939.png"><img src="https://www.vsay.net/content/uploadfile/202410/bd411728710939.png" alt="" /></a></p>
<h2>二、编译环境</h2>
<p>安装需要的编译工具</p>
<pre><code class="language-bahs">apt-get install gcc make gettext bison flex bc zlib1g-dev libncurses5-dev lzma</code></pre>
<h2>三、编译</h2>
<h4>1.根据盒子拷贝相应的config</h4>
<pre><code class="language-bash">cp configs/hi3798mv100/hi3798mdmo1d_hi3798mv100_cfg.mak cfg.mak</code></pre>
<h4>2.make hiboot</h4>
<p>这一步是为了生成交叉编译工具链</p>
<pre><code class="language-bash"># 首先要配置以下环境变量 在sdk根目录下
source ./env.sh
make hiboot</code></pre>
<p>大概38秒就完成了。</p>
<h4>3.编译内核</h4>
<p>这一步是为了编译依赖吧，反正不编译没法直接编译模块</p>
<pre><code class="language-bash">cd /root/HiSTBLinuxV100R005C00SPC060/source/kernel
make all</code></pre>
<p>会出现以下错误 这是sdk的问题<br />
<a href="https://www.vsay.net/content/uploadfile/202410/thum-7f161728738275.png"><img src="https://www.vsay.net/content/uploadfile/202410/7f161728738275.png" alt="" /></a></p>
<p><em>解决：</em><br />
wget 下载linux-4.4.35.tar.gz到third_party/open_source/下</p>
<pre><code class="language-bash"># 先删除
rm ../../third_party/open_source/linux-4.4.35.tar.gz
# 下载到third_party/open_source/
wget -P ../../third_party/open_source/ https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.4.35.tar.gz</code></pre>
<p>下载完成后再 <strong>make all</strong><br />
耐心等待完成...<br />
<a href="https://www.vsay.net/content/uploadfile/202410/thum-a44d1728743363.png"><img src="https://www.vsay.net/content/uploadfile/202410/a44d1728743363.png" alt="" /></a></p>
<h4>4.收集盒子信息：</h4>
<p>在盒子上看看你的内核版本是多少</p>
<pre><code class="language-bash">uname -r</code></pre>
<p>然后到<strong> /lib/modules/4.4.35<em>ecoo</em> </strong> 目录下找到任意一个驱动，查看驱动信息。</p>
<pre><code class="language-bash">modinfo cfg80211.ko</code></pre>
<p>记住下面这一行，编辑vermagic.h的时候要一致。<br />
<a href="https://www.vsay.net/content/uploadfile/202410/thum-e4551728713482.png"><img src="https://www.vsay.net/content/uploadfile/202410/e4551728713482.png" alt="" /></a></p>
<h4>5.到编译机器上编辑<strong>vermagic.h</strong>文件</h4>
<p>编译完成后会出现<strong>linux-4.4.y</strong>目录</p>
<pre><code class="language-bash">vim linux-4.4.y/include/linux/vermagic.h</code></pre>
<p>看下面的拼接字符要和<strong>modinfo cfg80211.ko</strong>里面的一致</p>
<blockquote>
<p>我这边是：替换 UTS_RELEASE 为 4.4.35_ecoo_xxxx;删掉了MODULE_VERMAGIC_PREEMPT的内容</p>
</blockquote>
<p><a href="https://www.vsay.net/content/uploadfile/202410/thum-c92a1728711552.png"><img src="https://www.vsay.net/content/uploadfile/202410/c92a1728711552.png" alt="" /></a></p>
<h4>6.下载官网ch341驱动</h4>
<p><a href="https://www.wch.cn/downloads/CH341SER_LINUX_ZIP.html">https://www.wch.cn/downloads/CH341SER_LINUX_ZIP.html</a><br />
在root下mkdir个ch341目录<br />
把下载的驱动<strong>ch341.h/ch341.c</strong>放在里面，不要官网的Makefile文件。</p>
<h4>7.创建ch341的Makefile文件</h4>
<pre><code class="language-bash">vim /root/ch341/Makefile</code></pre>
<p>键入以下内容：</p>
<pre><code class="language-bash">obj-m := ch341.o

KDIR := /root/HiSTBLinuxV100R005C00SPC060-master/source/kernel/linux-4.4.y  # 修改为你的 Linux 内核源码路径
PWD := $(shell pwd)

all:
        $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
        $(MAKE) -C $(KDIR) M=$(PWD) clean</code></pre>
<h4>8.编辑Makefile文件，主要是交叉编译参数太多，图个方便，避免出错！</h4>
<pre><code class="language-bash">vim Makefile</code></pre>
<p>拷贝上面menuconfig<br />
新行粘贴后修改menuconfig为M=/root/ch341 modules</p>
<p><a href="https://www.vsay.net/content/uploadfile/202410/thum-3b151728711931.png"><img src="https://www.vsay.net/content/uploadfile/202410/3b151728711931.png" alt="" /></a></p>
<h4>9.开始编译模块</h4>
<pre><code class="language-bash"># 开始make
make ch341</code></pre>
<p><a href="https://www.vsay.net/content/uploadfile/202410/thum-a5791728714574.png"><img src="https://www.vsay.net/content/uploadfile/202410/a5791728714574.png" alt="" /></a></p>
<blockquote>
<p>如果出现：<br />
<a href="https://www.vsay.net/content/uploadfile/202410/thum-a77c1728712333.png"><img src="https://www.vsay.net/content/uploadfile/202410/a77c1728712333.png" alt="" /></a><br />
Makefile文件粘贴不合适了，make前面的空格换成tab<br />
<a href="https://www.vsay.net/content/uploadfile/202410/thum-92231728714619.png"><img src="https://www.vsay.net/content/uploadfile/202410/92231728714619.png" alt="" /></a></p>
</blockquote>
<h4>10.进入 /root/ch341 拷贝ko到盒子</h4>
<pre><code class="language-bash">cd /root/ch341</code></pre>
<h4>注意了下面是盒子上操作了</h4>
<h4>1.加载驱动</h4>
<pre><code class="language-bahs">insmod ch341.ko</code></pre>
<p>报错：insmod: ERROR: could not insert module ch341.ko: Invalid module format<br />
输入dmesg | tail查看具体错误：</p>
<pre><code class="language-bahs">dmesg | tail</code></pre>
<p>像下面的就是vermagic.h文件没搞正确<br />
<a href="https://www.vsay.net/content/uploadfile/202410/thum-cfa71728715127.png"><img src="https://www.vsay.net/content/uploadfile/202410/cfa71728715127.png" alt="" /></a></p>
<p>啥错误没提示那就是合适了，恭喜你！</p>
<h4>2.放到 /usr/lib/modules/4.4.35_xxxx/kernel/drivers</h4>
<pre><code class="language-bash">cp ch341.ko /usr/lib/modules/4.4.35_xxxx/ch341.ko
# 更新模块依赖
depmod -a
# 查看状态
dmesg | tail</code></pre>
<p><a href="https://www.vsay.net/content/uploadfile/202410/thum-55201728715105.png"><img src="https://www.vsay.net/content/uploadfile/202410/55201728715105.png" alt="" /></a></p>
<p>dev目录下也有了ttyCH341USB0<br />
<a href="https://www.vsay.net/content/uploadfile/202410/thum-8eb41728745103.png"><img src="https://www.vsay.net/content/uploadfile/202410/8eb41728745103.png" alt="" /></a></p>
<h4>3.开机自动加载</h4>
<p>编辑/etc/modules在最后加入ch341</p>
<pre><code class="language-bash">vim /etc/modules</code></pre>
<p><a href="https://www.vsay.net/content/uploadfile/202410/thum-8f391728744614.png"><img src="https://www.vsay.net/content/uploadfile/202410/8f391728744614.png" alt="" /></a></p>
<h4>4.重启后验证</h4>
<pre><code class="language-bash">lsmod | grep ch341</code></pre>
<p><a href="https://www.vsay.net/content/uploadfile/202410/thum-af011728744695.png"><img src="https://www.vsay.net/content/uploadfile/202410/af011728744695.png" alt="" /></a></p>
<blockquote>
<p>@ayFun 提到的官网的串口驱动死机问题：<br />
测试在minicom下确实会卡死，测试 echo &quot;test&quot; &gt; /dev/ttyCH341USB0 也是可以正常接受到的。<br />
在screen工具下不会的，screen /dev/ttyCH341USB0 9600 完美！<br />
还有sdk自带的ch341驱动在接收的时候丢数据延迟，不知道什么原因。</p>
</blockquote>
<p>至此完毕，祝君成功！</p>]]></description>
    <pubDate>Sat, 12 Oct 2024 13:45:21 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/293.html</guid>
</item>
<item>
    <title>关于小程序上canvas移动卡顿的解决方法</title>
    <link>https://www.vsay.net/web/291.html</link>
    <description><![CDATA[<p>在使用canvas做图片裁剪的时候，<br />
偶尔出现卡顿情况，是移动事件向上冒泡导致的，<br />
添加以下代码阻止冒泡行为：</p>
<pre><code class="language-html">@touchmove.stop.prevent="()=&gt;{}"

&lt;canvas id="canvas" type="2d"  @touchstart="canvasTouch" @touchmove="canvasTouch" @touchend="canvasTouch" @touchmove.stop.prevent="()=&gt;{}"&gt;&lt;/canvas&gt;</code></pre>]]></description>
    <pubDate>Sat, 28 Sep 2024 14:28:42 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/291.html</guid>
</item>
<item>
    <title>如何在Linux上安装和配置Syncthing，实现文件同步</title>
    <link>https://www.vsay.net/mycode/290.html</link>
    <description><![CDATA[<p>一款轻量级、开源且强大的文件同步工具——Syncthing。通过它，你可以轻松在多个设备间同步文件，再也不怕文件丢失或版本不一致。</p>
<h3>一、什么是Syncthing？</h3>
<p>Syncthing 是一个自由的开源文件同步工具，支持多平台，包括Linux、macOS、Windows、Android等。它的最大亮点是完全去中心化，不依赖云端服务器，数据传输加密，极大保护了用户隐私。</p>
<h3>二、在Linux上安装Syncthing</h3>
<h4>1. github下载最新二进制包</h4>
<p>github：<a href="https://github.com/syncthing/syncthing">https://github.com/syncthing/syncthing</a><br />
releases：<a href="https://github.com/syncthing/syncthing/releases">https://github.com/syncthing/syncthing/releases</a><br />
安卓：<a href="https://github.com/syncthing/syncthing-android">https://github.com/syncthing/syncthing-android</a></p>
<p>根据自己的操作系统下载，我是ubuntu x64机器所以下载了：syncthing-linux-amd64-v1.27.12.tar.gz</p>
<p><a href="https://www.vsay.net/content/uploadfile/202409/thum-92051726966791.png"><img src="https://www.vsay.net/content/uploadfile/202409/92051726966791.png" alt="" /></a></p>
<h4>2. 安装Syncthing</h4>
<p>不建议使用apt安装，版本有点老旧。<br />
解压压缩包</p>
<pre><code class="language-bash">tar -xzvf syncthing-linux-amd64-v1.27.12.tar.gz</code></pre>
<p>解压出来将syncthing二进制文件移动到bin中</p>
<pre><code class="language-bash">mv syncthing /usr/bin/syncthing</code></pre>
<p>至此安装就完成了。</p>
<h4>3. 启动Syncthing</h4>
<p>Syncthing安装完成后，可以通过命令启动它：</p>
<pre><code class="language-bash">syncthing</code></pre>
<p>启动后，Syncthing会自动在本地的127.0.0.1:8384端口运行一个Web管理界面，打开浏览器，输入 <code>http://localhost:8384</code> 进入管理页面。<br />
如果你的Syncthing构建在服务器上，那么需要配置一下IP才行。命令行输入 <strong>syncthing -paths</strong> 找到 <strong>config.xml</strong> 这项，<code>vim</code>编辑config.xml文件内容。<br />
<a href="https://www.vsay.net/content/uploadfile/202409/e83e1726967163.png"><img src="https://www.vsay.net/content/uploadfile/202409/e83e1726967163.png" alt="" /></a></p>
<pre><code class="language-bash">vim /root/.local/state/syncthing/config.xml</code></pre>
<p>找到gui，将127.0.0.1:8384改成0.0.0.0:8384这样就可以外网访问了。<br />
<a href="https://www.vsay.net/content/uploadfile/202409/f1fb1726967280.png"><img src="https://www.vsay.net/content/uploadfile/202409/f1fb1726967280.png" alt="" /></a></p>
<p>配置完成后重新启动syncthing。</p>
<h3>三、配置Syncthing</h3>
<h4>1. 配置防火墙</h4>
<p>在使用 Syncthing 时，除了 8384 端口（用于 Web UI），你还需要确保以下端口开放：</p>
<ul>
<li>22000：用于同步数据的主要端口。</li>
<li>21027（UDP）：用于局域网发现和连接。</li>
</ul>
<h4>2. 添加同步文件夹</h4>
<p>进入Web界面后，你可以点击“添加文件夹”按钮，选择需要同步的本地文件夹。注意，每个同步的文件夹都可以设置唯一的ID和不同的权限。</p>
<h4>3. 添加同步设备</h4>
<p>点击页面中的“添加远程设备”，输入对方设备的ID（可以从对方Syncthing管理界面获取）。双方设备会自动互相配对，一旦连接成功，就可以开始同步了。这个就不详细说了，自己研究一下就知道了。</p>
<h3>四、设置开机自启动</h3>
<p>为了避免每次重启都要手动启动Syncthing，我们可以将其设置为开机自启动。</p>
<h4>1. 创建系统服务</h4>
<pre><code class="language-bash">sudo nano /etc/systemd/system/syncthing.service</code></pre>
<p>在文件中输入以下内容：</p>
<blockquote>
<p>-no-browser 是 Syncthing 的一个启动选项，表示在启动 Syncthing 时不自动打开浏览器访问管理界面。</p>
</blockquote>
<pre><code class="language-bash">[Unit]
Description=Syncthing - Open Source Continuous File Synchronization
Documentation=https://docs.syncthing.net/
After=network.target

[Service]
User=你的用户名如：ubuntu 或 root
ExecStart=/usr/bin/syncthing -no-browser

[Install]
WantedBy=multi-user.target</code></pre>
<p>保存并退出后，执行以下命令启用服务：</p>
<pre><code class="language-bash"># 重新加载systemd
sudo systemctl daemon-reload

# 启用并启动Syncthing服务
sudo systemctl enable syncthing
sudo systemctl start syncthing</code></pre>
<h3>五、常见问题与解决方案</h3>
<h4>1. <strong>无法访问Web界面</strong></h4>
<p>确保Syncthing已经启动，并且防火墙开放了8384端口。如果还是无法访问，可以尝试查看日志：</p>
<pre><code class="language-bash">   journalctl -u syncthing</code></pre>
<h4>2. <strong>设备无法配对</strong></h4>
<p>确认对方设备的ID是否正确，并且两端的Syncthing都已添加了对方设备。如果有防火墙，检查22000端口是否已开放。</p>]]></description>
    <pubDate>Sun, 22 Sep 2024 08:57:10 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/290.html</guid>
</item>
<item>
    <title>wo mic手机秒变电脑麦克风，解决你台式电脑没有麦克风的尴尬</title>
    <link>https://www.vsay.net/mycode/289.html</link>
    <description><![CDATA[<p>你有没有遇到过这种情况？开会、录音、直播的时候突然发现麦克风坏了，还是没有麦克风？此时此刻，你的手机表示：“兄弟，有我呢！”今天我要给大家安利一款神器——wo mic，让你的手机秒变麦克风！不花钱，不求人，轻松搞定各种场合，简直是懒人福音。</p>
<h3>下载 wo mic</h3>
<ol>
<li>手机端<br />
IOS用户：App Store中搜索wo mic<br />
安卓用户：<a href="https://wolicheng.com/womic/downloads/app-release-4_8.apk" title="点击下载">点击下载</a></li>
<li>电脑端<br />
仅支持windows系统：<a href="https://wolicheng.com/womic/downloads/WOMicClientSetup5_2.exe" title="点击下载">点击下载</a></li>
</ol>
<h3>手机端配置</h3>
<p>记住IP，点击启动<br />
<a href="https://www.vsay.net/content/uploadfile/202409/54c91725501935.png"><img src="https://www.vsay.net/content/uploadfile/202409/54c91725501935.png" alt="" /></a></p>
<h3>电脑端安装配置</h3>
<ol>
<li>打开安装包，一路下一步，最后重启电脑。</li>
<li>打开软件点击连接（保证手机和电脑再同一个局域网内，或者手机usb连接电脑,usb方式比较麻烦不建议）<br />
<a href="https://www.vsay.net/content/uploadfile/202409/96fa1725501745.png"><img src="https://www.vsay.net/content/uploadfile/202409/96fa1725501745.png" alt="" /></a><br />
<a href="https://www.vsay.net/content/uploadfile/202409/thum-d2d81725502061.png"><img src="https://www.vsay.net/content/uploadfile/202409/d2d81725502061.png" alt="" /></a></li>
</ol>
<p>此时电脑端会出现一个mic设备<br />
<a href="https://www.vsay.net/content/uploadfile/202409/11881725502100.png"><img src="https://www.vsay.net/content/uploadfile/202409/11881725502100.png" alt="" /></a></p>
<p>对着手机讲话就可以传输到电脑了。</p>
<p>如何将电脑的声音传到手机上？<br />
这样的神仙软件也是有的，看教程：<a href="https://www.vsay.net/mycode/206.html">https://www.vsay.net/mycode/206.html</a></p>]]></description>
    <pubDate>Thu, 05 Sep 2024 08:59:55 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/289.html</guid>
</item>
<item>
    <title>Linux 设置环境变量</title>
    <link>https://www.vsay.net/operations/288.html</link>
    <description><![CDATA[<blockquote>
<p>不同使用场景中，有效范围不同。<br />
我们来看看都有哪些场景：</p>
</blockquote>
<h3>1、在当前shell会话中export</h3>
<p>当前shell会话中export，例如：</p>
<pre><code class="language-bash">export VARIABLE_NAME=value
# 或者添加到PATH
export PATH=$PATH:/usr/local/go/bin</code></pre>
<p>这个设置只在当前Shell会话中有效，当会话结束时会失效。</p>
<h3>2、在脚本中export</h3>
<p>可以使用&quot;export&quot;命令在脚本中设置并导出环境变量，使其在当前脚本以及后续子进程中可见。这样可以确保其他脚本或命令能够使用这些导出的环境变量。</p>
<p>例如，在一个Shell脚本中，可以这样使用export命令设置环境变量：</p>
<pre><code class="language-bash">#!/bin/bash

# 设置并导出环境变量
export VARIABLE_NAME=value

# 其他脚本或命令可以访问导出的环境变量
other_script.sh</code></pre>
<p>在这个示例中，&quot;export VARIABLE_NAME=value&quot;语句将设置一个名为VARIABLE_NAME的环境变量，并将其导出，以便在脚本中和之后的其他脚本中都能够访问到。需要注意的是，导出的环境变量仅在子进程中有效，不会影响到父进程或其他已经存在的Shell会话。如果希望将环境变量导出到当前Shell会话中，可以直接运行脚本而不是使用子进程执行，如通过&quot;source&quot;命令或&quot;.&quot;运算符：</p>
<pre><code class="language-bash">source script.sh</code></pre>
<p>或者</p>
<pre><code class="language-bash">. script.sh</code></pre>
<p>这样，脚本中设置并导出的环境变量将在当前shell会话中立即生效。</p>
<h3>3、用户级永久export环境变量</h3>
<p>将环境变量添加到用户的个人配置文件中，例如/.bashrc或/.bash_profile，具体的操作如下：</p>
<p>打开用户的个人配置文件：</p>
<pre><code class="language-bash">vi ~/.bashrc</code></pre>
<p>在文件中添加环境变量的设置，例如：</p>
<pre><code class="language-bash">export VARIABLE_NAME=value</code></pre>
<p>保存文件并退出。然后重启shell会话或运行&quot;source ~/.bashrc&quot;使其生效。</p>
<h3>4、系统级永久export环境变量</h3>
<p>可以将环境变量添加到系统级配置文件中，例如/etc/profile或/etc/environment，具体的操作如下：</p>
<p>打开系统级配置文件：</p>
<pre><code class="language-bash">sudo vi /etc/profile</code></pre>
<p>在文件中添加环境变量的设置，例如：</p>
<pre><code class="language-bash">export VARIABLE_NAME=value</code></pre>
<p>保存文件并退出。然后重新登录或使用&quot;source /etc/profile&quot;使其生效。</p>]]></description>
    <pubDate>Thu, 22 Aug 2024 11:22:50 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/operations/288.html</guid>
</item>
<item>
    <title>又又又一个便宜的香港/美国免备服务器商，真正的12元一个月，149一年心动了吗</title>
    <link>https://www.vsay.net/mycode/287.html</link>
    <description><![CDATA[<p>自雨云便宜的免备服务器<a href="https://www.vsay.net/mycode/217.html" title="https://www.vsay.net/mycode/217.html">https://www.vsay.net/mycode/217.html</a><br />
又让我找到了一家便宜的运营商</p>
<div style="text-align:center;">
    <img src="https://www.vsay.net/content/uploadfile/202408/thum-113a1723083595.png" style="width:160px">
</div>
<h3>一月的价格：</h3>
<p><a href="https://www.vsay.net/content/uploadfile/202408/thum-787c1723083415.png"><img src="https://www.vsay.net/content/uploadfile/202408/787c1723083415.png" alt="" /></a></p>
<h3>一年的价格：</h3>
<p><a href="https://www.vsay.net/content/uploadfile/202408/thum-e3d91723083466.png"><img src="https://www.vsay.net/content/uploadfile/202408/e3d91723083466.png" alt="" /></a></p>
<p>官网优惠链接：<a href="https://my.htstack.com/aff.php?aff=17805">https://my.htstack.com/aff.php?aff=17805</a><br />
活动链接：<a href="https://www.htstack.com/activity/20231111.shtml?aff=17805">https://www.htstack.com/activity/20231111.shtml?aff=17805</a></p>]]></description>
    <pubDate>Thu, 08 Aug 2024 10:12:54 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/287.html</guid>
</item>
<item>
    <title>推荐一款媒体影音嗅探神器：Chrome扩展插件“猫抓（Cat-Catch）”</title>
    <link>https://www.vsay.net/mycode/286.html</link>
    <description><![CDATA[<p><strong>在如今的互联网时代，我们经常需要下载各种媒体资源，如视频、音频、图片等。然而，许多网站并不提供直接的下载链接，使得资源的获取变得异常困难。为了帮助大家解决这一问题，我推荐一款非常实用的Chrome扩展插件——“猫抓（Cat-Catch）”。</strong></p>
<h2>什么是猫抓（Cat-Catch）？</h2>
<p>“猫抓（Cat-Catch）”是一款强大的媒体嗅探工具，它可以帮助用户快速、轻松地从各种网站上嗅探并下载视频、音频、图片等媒体资源。它不仅功能强大，而且操作简便，是广大用户进行媒体资源下载的得力助手。</p>
<p><a href="https://www.vsay.net/content/uploadfile/202408/thum-24631722662624.png"><img src="https://www.vsay.net/content/uploadfile/202408/24631722662624.png" alt="" /></a></p>
<p><a href="https://www.vsay.net/content/uploadfile/202408/thum-6b981722662642.png"><img src="https://www.vsay.net/content/uploadfile/202408/6b981722662642.png" alt="" /></a></p>
<h2>功能特点</h2>
<ol>
<li>
<p><strong>支持多种媒体格式</strong>：猫抓支持嗅探并下载各种格式的视频（如MP4、FLV、AVI等）、音频（如MP3、WAV、AAC等）以及图片（如JPG、PNG、GIF等）。</p>
</li>
<li>
<p><strong>自动嗅探</strong>：当用户访问某个网页时，猫抓会自动嗅探该页面上的所有媒体资源，并将其列出，用户可以选择性下载所需的资源。</p>
</li>
<li>
<p><strong>批量下载</strong>：用户可以一次性选择多个媒体资源进行批量下载，提高了下载效率。</p>
</li>
<li>
<p><strong>支持各种网站</strong>：猫抓几乎支持所有主流视频网站、音乐网站、图片网站等，涵盖面广。</p>
</li>
<li>
<p><strong>操作简便</strong>：猫抓的界面简洁直观，用户只需点击几下鼠标，就可以完成媒体资源的嗅探和下载。</p>
</li>
</ol>
<h2>如何安装和使用猫抓（Cat-Catch）</h2>
<h3>安装步骤</h3>
<ol>
<li><strong>打开Chrome浏览器</strong>，进入Chrome网上应用店（Chrome Web Store）。</li>
<li>在搜索框中输入“猫抓”或“Cat-Catch”，找到该扩展程序。</li>
<li>点击“添加到Chrome”按钮，按照提示完成安装。你也可以通过以下链接直接访问下载页面：<br />
<a href="https://chromewebstore.google.com/detail/%E7%8C%AB%E6%8A%93/jfedfbgedapdagkghmgibemcoggfppbb">Chrome网上应用店 - 猫抓</a><br />
<a href="https://microsoftedge.microsoft.com/addons/detail/%E7%8C%AB%E6%8A%93/oohmdefbjalncfplafanlagojlakmjci">Edge浏览器插件 - 猫抓</a><br />
<a href="https://addons.mozilla.org/zh-CN/firefox/addon/cat-catch/">Firefox浏览器插件 - 猫抓</a></li>
</ol>
<h3>使用方法</h3>
<ol>
<li><strong>启动猫抓</strong>：安装完成后，点击浏览器右上角的猫抓图标，启动扩展程序。</li>
<li><strong>嗅探媒体资源</strong>：访问你想要下载媒体资源的网页，猫抓会自动嗅探并列出该页面上的所有媒体资源。</li>
<li><strong>选择并下载</strong>：在猫抓的界面中选择你需要下载的媒体资源，点击“下载”按钮，即可开始下载。</li>
</ol>
<h2>GitHub地址</h2>
<p>猫抓的开源代码托管在GitHub上，开发者和感兴趣的用户可以访问以下链接了解更多信息，参与项目开发或提交反馈：<br />
<a href="https://github.com/xifangczy/cat-catch">GitHub - 猫抓（Cat-Catch）</a></p>
<h2>实际应用案例</h2>
<h3>下载在线视频</h3>
<p>假设你在某个视频网站上看到了一个非常喜欢的视频，并且想要将其下载到本地。只需启动猫抓，访问该视频页面，猫抓会自动嗅探到视频资源，并将其列出。你只需点击下载，即可将视频保存到本地。</p>
<h3>获取网页音频</h3>
<p>有时我们在浏览网页时，会听到一些喜欢的背景音乐或音频节目。使用猫抓，同样可以轻松将这些音频资源下载到本地，方便随时播放。</p>
<h3>保存图片</h3>
<p>对于一些图片爱好者来说，猫抓也是一个非常实用的工具。你可以使用猫抓将网页上的高清图片下载下来，保存到自己的图片库中。</p>
<h2>总结</h2>
<p>“猫抓（Cat-Catch）”是一款非常强大的媒体嗅探工具，它不仅支持多种媒体格式的嗅探和下载，还具备自动嗅探、批量下载等功能。通过简单的操作，用户可以轻松获取各种网站上的媒体资源。如果你经常需要下载视频、音频或图片，猫抓无疑是一个不可或缺的好帮手。</p>]]></description>
    <pubDate>Sat, 03 Aug 2024 13:20:33 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/286.html</guid>
</item>
<item>
    <title>Android FFmpeg GUI汉化版本分享</title>
    <link>https://www.vsay.net/mycode/285.html</link>
    <description><![CDATA[<p><strong>如果你厌烦了安卓格式转换app的各种广告，那么不妨来试试纯命令对视频的操作，干净无毒无广告。</strong></p>
<h2>软件简介</h2>
<p>FFmpeg Media Encoder是一款开源的视频、音频转码软件。它包括了目前领先的音/视频编码库libavcodec，可以轻易地实现多种视频格式之间的相互转换。</p>
<h2>适用范围</h2>
<ul>
<li>图像：可以把几乎任何格式的图像转换到JPG，PNG，BMP，EPS，GIF，HDR，EXR，SVG，TGA，TIFF，WBMP，或WEBP格式。</li>
<li>视频：可以把几乎任何格式的视频转换到3GP，3G2，FLV，MKV，MP4，MPEG-2，OGG，WEBM，或WMV格式。</li>
<li>音频：可以把几乎任何格式的音频转换到MP3，OGG，WAV，WMA，AAC，FLAC，M4A等，或MMF格式。</li>
<li>文档：可以把与之有关的任何东西转换到PDF，DOC，TXT，ODT，FLASH，或HTML格式。</li>
<li>电子书：可以把电子书以MOBI，EPUB，PDF，LRF，FB2，LIT，PDB，TCR等多种格式互相转换。</li>
<li>档案：转换文件至档案，或以档案格式互相转换！支持ZIP，BZ2，7Z和GZ格式。</li>
</ul>
<h2>软件截图</h2>
<p><a href="https://www.vsay.net/content/uploadfile/202408/thum-0a561722662011.png"><img src="https://www.vsay.net/content/uploadfile/202408/0a561722662011.png" alt="" /></a></p>
<h2>下载地址</h2>
<p>本站下载：<a href="https://www.vsay.net/content/uploadfile/202408/2f641722662075.apk">FFmpegMediaEncoder_v4.0.5.apk</a><br />
蓝奏下载：<a href="https://vsay.lanzoul.com/irr9526dchgh">https://vsay.lanzoul.com/irr9526dchgh</a>  密码:fdcq</p>]]></description>
    <pubDate>Sat, 03 Aug 2024 13:09:57 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/285.html</guid>
</item>
<item>
    <title>FFmpeg常用命令指南</title>
    <link>https://www.vsay.net/mycode/284.html</link>
    <description><![CDATA[<h1>FFmpeg常用命令指南</h1>
<h2>目录</h2>
<ol>
<li><a href="#查看媒体文件信息">查看媒体文件信息</a></li>
<li><a href="#视频格式转换">视频格式转换</a></li>
<li><a href="#提取音频">提取音频</a></li>
<li><a href="#视频剪辑">视频剪辑</a></li>
<li><a href="#合并视频文件">合并视频文件</a></li>
<li><a href="#调整视频分辨率">调整视频分辨率</a></li>
<li><a href="#添加水印">添加水印</a></li>
<li><a href="#转换音频格式">转换音频格式</a></li>
<li><a href="#控制比特率">控制比特率</a></li>
<li><a href="#添加字幕">添加字幕</a></li>
<li><a href="#处理m3u8">处理m3u8</a></li>
<li><a href="#旋转视频">旋转视频</a></li>
<li><a href="#调整音量">调整音量</a></li>
<li><a href="#改变视频帧率">改变视频帧率</a></li>
<li><a href="#视频转gif">视频转GIF</a></li>
<li><a href="#音频剪辑">音频剪辑</a></li>
</ol>
<h2><a id="查看媒体文件信息"></a>查看媒体文件信息</h2>
<p>要查看视频或音频文件的详细信息，可以使用以下命令：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4</code></pre>
<p>该命令将显示文件的格式、编码信息、分辨率、比特率等详细信息。</p>
<h2><a id="视频格式转换"></a>视频格式转换</h2>
<p>将视频文件从一种格式转换为另一种格式：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 output.avi</code></pre>
<p>上面的命令将MP4文件转换为AVI文件。FFmpeg会根据输出文件的扩展名自动选择编码器。</p>
<h2><a id="提取音频"></a>提取音频</h2>
<p>从视频文件中提取音频：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -q:a 0 -map a output.mp3</code></pre>
<p>该命令将MP4视频文件中的音频提取并保存为MP3格式。</p>
<h2><a id="视频剪辑"></a>视频剪辑</h2>
<p>从视频文件中剪辑一段特定时间的视频：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -ss 00:00:10 -to 00:00:20 -c copy output.mp4</code></pre>
<p>该命令将从第10秒开始到第20秒结束的片段剪辑出来，并保存为新的MP4文件。</p>
<h2><a id="合并视频文件"></a>合并视频文件</h2>
<p>将多个视频文件合并为一个文件：<br />
首先，创建一个包含所有待合并文件的文本文件（比如<code>filelist.txt</code>），内容如下：</p>
<pre><code>file 'part1.mp4'
file 'part2.mp4'
file 'part3.mp4'</code></pre>
<p>然后使用以下命令进行合并：</p>
<pre><code class="language-bash">ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4</code></pre>
<p>该命令将<code>part1.mp4</code>、<code>part2.mp4</code>和<code>part3.mp4</code>合并为<code>output.mp4</code>。</p>
<h2><a id="调整视频分辨率"></a>调整视频分辨率</h2>
<p>改变视频的分辨率：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -vf scale=1280:720 output.mp4</code></pre>
<p>该命令将输入视频调整为1280x720的分辨率。</p>
<h2><a id="添加水印"></a>添加水印</h2>
<p>在视频上添加图片水印：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -i watermark.png -filter_complex "overlay=10:10" output.mp4</code></pre>
<p>该命令将<code>watermark.png</code>图片添加到<code>input.mp4</code>视频的左上角（10像素偏移）。</p>
<h2><a id="转换音频格式"></a>转换音频格式</h2>
<p>将音频文件从一种格式转换为另一种格式：</p>
<pre><code class="language-bash">ffmpeg -i input.wav output.mp3</code></pre>
<p>该命令将WAV文件转换为MP3格式。</p>
<h2><a id="控制比特率"></a>控制比特率</h2>
<p>指定输出文件的比特率：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -b:v 1000k -b:a 128k output.mp4</code></pre>
<p>该命令将输出视频的比特率设置为1000kbps，音频比特率设置为128kbps。</p>
<h2><a id="添加字幕"></a>添加字幕</h2>
<p>将SRT字幕文件嵌入视频：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -vf subtitles=subtitles.srt output.mp4</code></pre>
<p>该命令将<code>subtitles.srt</code>字幕文件嵌入到<code>input.mp4</code>视频中。</p>
<h2><a id="处理m3u8"></a>处理m3u8</h2>
<p>m3u8是一种HLS（HTTP Live Streaming）协议格式，可以将视频文件切片并通过HTTP进行传输。以下是处理m3u8的一些常用命令：</p>
<h3><a id="将视频切片并生成m3u8播放列表"></a>将视频切片并生成m3u8播放列表</h3>
<pre><code class="language-bash">ffmpeg -i input.mp4 -codec: copy -start_number 0 -hls_time 10 -hls_list_size 0 -f hls output.m3u8</code></pre>
<p>该命令将<code>input.mp4</code>文件切片成10秒一个片段，并生成一个<code>output.m3u8</code>播放列表。</p>
<h3><a id="从m3u8播放列表下载视频"></a>从m3u8播放列表下载视频</h3>
<pre><code class="language-bash">ffmpeg -i http://example.com/path/to/playlist.m3u8 -c copy output.mp4</code></pre>
<p>该命令从指定的m3u8播放列表下载视频并保存为<code>output.mp4</code>文件。</p>
<h3><a id="自定义HLS切片和分片大小"></a>自定义HLS切片和分片大小</h3>
<pre><code class="language-bash">ffmpeg -i input.mp4 -c:v libx264 -c:a aac -strict -2 -crf 20 -g 48 -hls_time 6 -hls_playlist_type vod -hls_segment_filename 'segment_%03d.ts' output.m3u8</code></pre>
<p>该命令将<code>input.mp4</code>文件切片成6秒一个片段，并自定义分片文件名为<code>segment_001.ts</code>，<code>segment_002.ts</code>等，同时生成一个<code>output.m3u8</code>播放列表。</p>
<h2><a id="旋转视频"></a>旋转视频</h2>
<p>将视频旋转一定角度，例如90度：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -vf "transpose=1" output.mp4</code></pre>
<p><code>transpose</code>参数的值解释：</p>
<ul>
<li><code>0</code>：顺时针旋转90度并垂直翻转</li>
<li><code>1</code>：顺时针旋转90度</li>
<li><code>2</code>：逆时针旋转90度并垂直翻转</li>
<li><code>3</code>：逆时针旋转90度</li>
</ul>
<h2><a id="调整音量"></a>调整音量</h2>
<p>提高或降低音频的音量：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -af "volume=2.0" output.mp4</code></pre>
<p>该命令将音量提高一倍。可以将<code>2.0</code>替换为其他值来调整音量。</p>
<h2><a id="改变视频帧率"></a>改变视频帧率</h2>
<p>改变视频的帧率，例如将视频帧率设置为30fps：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -r 30 output.mp4</code></pre>
<p>该命令将视频的帧率更改为30帧每秒。</p>
<h2><a id="视频转gif"></a>视频转GIF</h2>
<p>将视频转换为GIF动画：</p>
<pre><code class="language-bash">ffmpeg -i input.mp4 -vf "fps=10,scale=320:-1:flags=lanczos" output.gif</code></pre>
<p>该命令将视频转换为10fps的GIF，并将宽度缩放为320像素（高度按比例调整）。</p>
<h2><a id="音频剪辑"></a>音频剪辑</h2>
<p>剪辑音频文件的一部分：</p>
<pre><code class="language-bash">ffmpeg -i input.mp3 -ss 00:00:30 -to 00:01:00 -c copy output.mp3</code></pre>
<p>该命令将从第30秒开始到第1分钟结束的片段剪辑出来，并保存为新的MP3文件。</p>]]></description>
    <pubDate>Sat, 03 Aug 2024 12:54:13 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/284.html</guid>
</item>
<item>
    <title>推荐目前最好的web视频播放器ArtPlayer.js支持m3u8适配PC,IOS,Android</title>
    <link>https://www.vsay.net/web/283.html</link>
    <description><![CDATA[<h2>先看界面</h2>
<p><a href="https://www.vsay.net/content/uploadfile/202407/thum-d6531721696857.png"><img src="https://www.vsay.net/content/uploadfile/202407/d6531721696857.png" alt="" /></a></p>
<p>在现代 Web 开发中，一个强大且灵活的视频播放器是必不可少的。今天，我要推荐一款功能强大的 JavaScript 视频播放器库——ArtPlayer.js。</p>
<h2>什么是 ArtPlayer.js？</h2>
<p>ArtPlayer.js 是一款现代化的 JavaScript 视频播放器，旨在提供丰富的功能和良好的用户体验。它支持多种视频格式，并具有强大的插件系统和灵活的 UI 控制。</p>
<h2>ArtPlayer.js 的特点</h2>
<h3>1. 丰富的插件系统</h3>
<p>ArtPlayer.js 提供了强大的插件支持，开发者可以轻松扩展播放器的功能。例如，添加字幕插件、弹幕插件和广告插件等，增强播放器的灵活性和可定制性。</p>
<h3>2. 多格式支持</h3>
<p>ArtPlayer.js 支持 HLS（m3u8）、MP4、WebM 等多种视频格式，轻松应对各种直播和点播需求。</p>
<h3>3. 强大的 UI 控制</h3>
<p>ArtPlayer.js 提供了全面的 UI 控制按钮和菜单，包括播放/暂停、音量控制、播放速度调节和全屏切换等，用户界面美观且易于操作。</p>
<h3>4. 高性能</h3>
<p>ArtPlayer.js 经过优化，保证了高性能和流畅播放，适合播放高分辨率视频和进行复杂的 UI 操作。</p>
<h3>5. 事件系统</h3>
<p>ArtPlayer.js 拥有完整的事件系统，开发者可以监听和处理各种播放器事件，如视频加载完成、播放开始和播放结束等。</p>
<h2>如何使用 ArtPlayer.js？</h2>
<p>使用 ArtPlayer.js 非常简单。以下是一个基本的使用示例：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;ArtPlayer Example&lt;/title&gt;
    &lt;link rel="stylesheet" href="https://unpkg.com/artplayer/dist/artplayer.css"&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id="artplayer-app"&gt;&lt;/div&gt;
    &lt;script src="https://unpkg.com/artplayer/dist/artplayer.js"&gt;&lt;/script&gt;
    &lt;script&gt;
        const art = new Artplayer({
            container: '#artplayer-app',
            url: 'https://path/to/your/video.m3u8',
            type: 'm3u8',
            autoSize: true,
            playbackRate: true,
            setting: true,
            loop: true,
            flip: true,
            aspectRatio: true,
            fullscreen: true,
            miniProgressBar: true,
            playsInline: true,
            autoPlayback: true,
            theme: '#23ade5',
            lang: navigator.language.toLowerCase(),
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h2>链接</h2>
<ul>
<li><a href="https://artplayer.org">ArtPlayer.js 官网</a></li>
<li><a href="https://github.com/zhw2590582/ArtPlayer">ArtPlayer.js GitHub 仓库</a></li>
</ul>]]></description>
    <pubDate>Tue, 23 Jul 2024 09:03:27 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/283.html</guid>
</item>
<item>
    <title>yarn报错，yarn的镜像源已经过期了，因为yarn和npm用的是淘宝的镜像源，淘宝的镜像源已经过期了，要设置最新的淘宝镜像源。</title>
    <link>https://www.vsay.net/web/282.html</link>
    <description><![CDATA[<h3>查看yarn用的什么镜像源</h3>
<pre><code class="language-bash">yarn config get registry</code></pre>
<h3>查看具体的信息</h3>
<pre><code class="language-bash">yarn config list</code></pre>
<h3>删除源</h3>
<pre><code class="language-bash">yarn config delete proxy</code></pre>
<h3>设置淘宝的最新镜像源，yarn和npm都要设置最新的淘宝镜像源，不然还是报错</h3>
<pre><code class="language-bash">npm config set registry https://registry.npmmirror.com

yarn config set registry https://registry.npmmirror.com</code></pre>]]></description>
    <pubDate>Wed, 17 Jul 2024 22:48:08 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/282.html</guid>
</item>
<item>
    <title>最新版 ADB 工具下载与使用指南</title>
    <link>https://www.vsay.net/mycode/281.html</link>
    <description><![CDATA[<p>在安卓开发中，ADB（Android Debug Bridge）是一个必不可少的工具。为了方便大家下载和使用ADB工具，我整理了最新的下载链接和简要使用方法。</p>
<h2>下载ADB工具</h2>
<p>点击以下链接下载适用于你操作系统的ADB工具：</p>
<h4>Google官方下载链接：</h4>
<ul>
<li><a href="https://dl.google.com/android/repository/platform-tools-latest-windows.zip">Windows 版本</a></li>
<li><a href="https://dl.google.com/android/repository/platform-tools-latest-darwin.zip">MacOS 版本</a></li>
<li><a href="https://dl.google.com/android/repository/platform-tools-latest-linux.zip">Linux 版本</a></li>
</ul>
<h4>第三方下载链接：</h4>
<ul>
<li><a href="https://adbdownload.com/">第三方下载页</a><br />
其实也是调用的谷歌下载地址，不过他应该会更新吧，因为我不会更新的！（懒）</li>
</ul>
<h2>安装和使用</h2>
<h3>安装步骤</h3>
<ol>
<li>下载并解压缩文件。</li>
<li>将解压后的文件夹移动到一个合适的位置，例如C盘根目录。</li>
<li>
<p>将ADB工具添加到系统路径：</p>
<p><strong>Windows:</strong></p>
<pre><code class="language-bash">setx PATH "%PATH%;C:\platform-tools"</code></pre>
<p><strong>MacOS/Linux:</strong></p>
<pre><code class="language-bash">export PATH=$PATH:/path/to/platform-tools</code></pre>
</li>
</ol>
<h3>常用ADB命令</h3>
<ol>
<li>
<p><strong>查看设备：</strong></p>
<pre><code class="language-bash">adb devices</code></pre>
</li>
<li>
<p><strong>安装APK：</strong></p>
<pre><code class="language-bash">adb install path/to/your_app.apk</code></pre>
</li>
<li>
<p><strong>卸载应用：</strong></p>
<pre><code class="language-bash">adb uninstall com.example.yourapp</code></pre>
</li>
<li>
<p><strong>传输文件：</strong></p>
<pre><code class="language-bash">adb push local_path remote_path
adb pull remote_path local_path</code></pre>
</li>
<li>
<p><strong>查看日志：</strong></p>
<pre><code class="language-bash">adb logcat</code></pre>
</li>
</ol>]]></description>
    <pubDate>Wed, 10 Jul 2024 10:46:08 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/281.html</guid>
</item>
<item>
    <title>vue 做一个插件plugin 插件的创建（install）及使用方法</title>
    <link>https://www.vsay.net/web/280.html</link>
    <description><![CDATA[<p>接上篇(<a href="https://www.vsay.net/web/279.html">https://www.vsay.net/web/279.html</a> )，要做一个全局调用的dialog（web）,这种方法只能在web下使用，小程序我还没测哈。</p>
<pre><code class="language-javascript">// plugins/dialog.js
import Vue from 'vue';
import MyDialog from '@/components/MyDialog.vue';

const DialogConstructor = Vue.extend(MyDialog);

let instance;

const initInstance = () =&gt; {
  instance = new DialogConstructor({
    el: document.createElement('div'),
    data() {
      return {
        visible: false,
        message: '',
      };
    },
  });
  document.body.appendChild(instance.$el);
};

const showDialog = (options = {}) =&gt; {
  if (!instance) {
    initInstance();
  }
  instance.message = options.message || '';
  instance.visible = true;

  return new Promise((resolve, reject) =&gt; {
    instance.$on('confirm', () =&gt; {
      resolve();
    });
    instance.$on('close', () =&gt; {
      reject();
    });
  });
};

export default {
  install(Vue) {
    Vue.prototype.$dialog = showDialog;
  },
};</code></pre>]]></description>
    <pubDate>Fri, 28 Jun 2024 11:46:06 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/280.html</guid>
</item>
<item>
    <title>uniapp全局弹窗APP做全局弹窗(dialog)插件的思路</title>
    <link>https://www.vsay.net/web/279.html</link>
    <description><![CDATA[<p>由于uniapp做的app没办法操作dom，install的dom操作都无法实现全局dialog</p>
<h3>网上有三种方法</h3>
<ol>
<li>可以使用subnvue webview子窗体</li>
<li>使用 plus.nativeObj.View</li>
<li>新建一个页面进行跳转，可以实现伪弹窗（其实是打开一个背景透明的页面）<br />
前两种工有点大，就选第三种吧。</li>
</ol>
<p>先写一个<code>dialog</code>的vue页面方在<code>components</code>目录下</p>
<h3>dialog.vue</h3>
<pre><code class="language-vue">&lt;template&gt;
    &lt;view&gt;
        &lt;view class="diaglog-mask"&gt;&lt;/view&gt;
        &lt;view class="diaglog"&gt;
            &lt;view class="diaglog-title"&gt;
                {{param.title}}
            &lt;/view&gt;
            &lt;view class="diaglog-content"&gt;
                &lt;rich-text :nodes="param.content"&gt;&lt;/rich-text&gt;
            &lt;/view&gt;
            &lt;view class="diaglog-btns"&gt;
                &lt;view class="diaglog-btns-button" :style="{color:param.cancelColor}" @click="cancelClick"&gt;
                    {{param.cancelText}}
                &lt;/view&gt;
                &lt;view class="diaglog-btns-button" :style="{color:param.confirmColor}" @click="confirmClick"&gt;
                    {{param.confirmText}}
                &lt;/view&gt;
            &lt;/view&gt;
        &lt;/view&gt;
    &lt;/view&gt;
&lt;/template&gt;
&lt;script&gt;
    export default {
        name: "diaglog",
        data(){
            return {
                param:{
                    title:'',
                    content:'',
                    cancelText:'取消',
                    cancelColor:'#333',
                    confirmText:'确定',
                    confirmColor:'#1ED58F',
                }
            }
        },
        onLoad(option) {
            // 将参数转换成json
            const data = JSON.parse(option.data)
            if(data){
                this.param = {...this.param,...data}
            }
        },
        methods: {
            cancelClick() {
                uni.$emit('$dialog', false)
                uni.navigateBack()
            },
            confirmClick() {
                uni.$emit('$dialog', true)
            }
        }
    }
&lt;/script&gt;
&lt;style lang="scss"&gt;
    page {
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0);
    }

    .diaglog-mask {
        position: fixed;
        z-index: 1000;
        width: 100%;
        height: 100%;
        left: 0;
        top: 0;
        background-color: rgba(0, 0, 0, .23);
    }

    .diaglog {
        position: fixed;
        width: calc(100% - 80rpx);
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        background-color: #fff;
        padding-bottom: 0;
        box-sizing: border-box;
        z-index: 1001;
        border-radius: 20rpx;
        overflow: hidden;

        &amp;-title {
            padding-top: 60rpx;
            padding-bottom: 30rpx;
            font-weight: bold;
            font-size: 32rpx;
            color: #333333;
            text-align: center;
        }

        &amp;-content {
            font-weight: 500;
            font-size: 28rpx;
            color: #333333;
            line-height: 40rpx;
            padding: 0 40rpx;
            padding-bottom: 60rpx;
        }

        &amp;-btns {
            display: flex;
            position: relative;

            &amp;:before {
                position: absolute;
                content: '';
                width: calc(100% - 80rpx);
                left: 40rpx;
                height: 1rpx;
                background-color: #ECECEC;
            }

            &amp;-button {
                flex: 1;
                text-align: center;
                height: 100rpx;
                line-height: 100rpx;
                font-weight: bold;
                font-size: 30rpx;
                color: #333333;

                &amp;:first-child {
                    border-right: solid 1rpx #ECECEC;
                }

                &amp;:active {
                    background-color: #f1f1f1;
                }
            }
        }
    }
&lt;/style&gt;</code></pre>
<h3>在pages.json中加入页面</h3>
<pre><code class="language-json">// 全局弹窗
{
    "path": "components/diaglog/diaglog",
    "style": {
        "navigationStyle": "custom",
        "app-plus": {
            "animationType": "fade-in", // 设置fade-in淡入动画，为最合理的动画类型
            "background": "transparent", // 背景透明
            "backgroundColor": "transparent", // 背景透明
            "webviewBGTransparent": true,
            "mask": "none",
            "popGesture": "none", // 关闭IOS屏幕左边滑动关闭当前页面的功能
            "bounce": "none" // 将回弹属性关掉
        }
    }
}</code></pre>
<h3>新建plugins目录，创建dialog.js</h3>
<pre><code class="language-javascript">const install = Vue =&gt; {
    Vue.prototype.$dialog = (params) =&gt; {
        return new Promise((resolve, reject) =&gt; {
            // 防止当前页也是dialog
            if (getCurrentPages()[getCurrentPages().length - 1].route == 'components/diaglog/diaglog'){
                reject()
                return
            }
            // 跳转到dialog，并且传递title,content等参数
            uni.navigateTo({
                url: '/components/diaglog/diaglog?data=' + encodeURIComponent(JSON.stringify(
                    params))
            })
            // 注册一个回调
            uni.$once('$dialog', (state) =&gt; {
                if (state) {
                    resolve(() =&gt; {
                        uni.navigateBack()
                    })
                    return
                }
                reject()
            })
        })
    }
}
export default install;</code></pre>
<h3>在main.js中use插件</h3>
<pre><code class="language-javascript">import dialogPlugin from '@/plugins/dialog';
Vue.use(dialogPlugin);</code></pre>
<h3>使用：</h3>
<pre><code class="language-javascript">this.$dialog({
    title: '用户协议和隐私政策',
    content: '我们非常重视用户隐私政策并严格遵守相关的法律规定。请您仔细阅读&lt;span style="color:#1ED58F"&gt;《隐私政策》&lt;/span&gt;后再继续使用。如果您继续使用我们的服务，表示您已经充分阅读和理解我们协议的全部内容。并严格遵守相关的法律规定。请您仔细阅读并严格遵守相关的法律规定。请您仔细阅读。',
    cancelText: '不同意',
    confirmText: '同意',
}).then(()=&gt;{
    // 确认后
}).catch(()=&gt;{
    // 取消后
})</code></pre>
<p>看效果<br />
<a href="https://www.vsay.net/content/uploadfile/202406/a9941719546156.png"><img src="https://www.vsay.net/content/uploadfile/202406/a9941719546156.png" alt="" /></a></p>]]></description>
    <pubDate>Fri, 28 Jun 2024 11:30:52 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/279.html</guid>
</item>
<item>
    <title>openvpn 设置外网流量走本地，异地组网流量走openvpn</title>
    <link>https://www.vsay.net/mycode/278.html</link>
    <description><![CDATA[<h3>现在的需求是：</h3>
<p>本地ip是192.168.1.0，openvpn异地组网ip是10.8.0.0，除了10.8.0.0的流量都走向192.168.1.0，实现外网和异地局域网共存。</p>
<h3>走的弯路：</h3>
<p>搭建好openvpn后，外网和异地组网都可以使用，但是网络很慢，但是查询ip的时候发现ip是服务器ip。。。那么就是所有的流量都走了openvpn了，这就是网速慢的问题。</p>
<h2>调整服务端配置：</h2>
<p><strong>删除以下行</strong></p>
<ul>
<li>push &quot;redirect-gateway def1&quot;<br />
<strong>增加以下行</strong></li>
<li>route-nopull  #不对客户端做任何push路由操作</li>
<li>push &quot;route 10.8.0.0 255.255.255.0&quot;  #手动添加路由</li>
<li>client-to-client #让客户端可以访问客户端</li>
</ul>
<h2>调整客户端配置：</h2>
<p><strong>删除以下行</strong></p>
<ul>
<li>ignore-unknown-option block-outside-dns</li>
<li>block-outside-dns</li>
<li>redirect-gateway #存在该字段的行</li>
</ul>
<p><strong>到此完成，重启服务器连接测试。</strong></p>]]></description>
    <pubDate>Wed, 26 Jun 2024 14:21:25 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/278.html</guid>
</item>
<item>
    <title>如何在OpenVPN中配置255.255.255.252子网</title>
    <link>https://www.vsay.net/mycode/277.html</link>
    <description><![CDATA[<h3>在配置完固定ip后可能会出现下面的错误：</h3>
<div style="color:red">There is a problem in your selection of --ifconfig endpoints [local=10.8.0.3, remote=10.8.0.4]. The local and remote VPN endpoints must exist within the same 255.255.255.252 subnet. This is a limitation of --dev tun when used with the TAP-WIN32 driver. Try 'openvpn --show-valid-subnets' option for more info.
</div>
<p><strong>为了在OpenVPN中为客户端分配固定IP地址，并避免出现<code>“local and remote VPN endpoints must exist within the same 255.255.255.252 subnet”</code>错误，需要确保每个客户端的IP地址在同一个255.255.255.252子网中。以下是详细的解决方法：</strong></p>
<h1>如何在OpenVPN中配置255.255.255.252子网</h1>
<p>在使用OpenVPN搭建VPN时，正确配置子网是确保网络稳定性和避免IP地址冲突的关键。本文将详细介绍如何计算和配置<code>255.255.255.252</code>子网，以及在OpenVPN中如何应用这些配置。</p>
<h2>什么是255.255.255.252子网？</h2>
<p><code>255.255.255.252</code>子网掩码也称为<code>/30</code>子网，它提供了一个非常小的子网，每个子网包含4个IP地址，其中2个是可用的IP地址。这种子网非常适合在OpenVPN中为客户端分配固定的IP地址，因为它提供了足够的地址空间，并确保了本地和远程VPN端点在同一个子网内。</p>
<h2>如何计算255.255.255.252子网？</h2>
<p>要计算一个<code>255.255.255.252</code>子网，可以按照以下步骤进行：</p>
<h3>步骤1：确定子网掩码</h3>
<p><code>255.255.255.252</code>表示子网掩码，它指示每个子网包含4个IP地址。</p>
<h3>步骤2：选择基础IP地址</h3>
<p>选择一个基础IP地址作为您的子网起始点。例如，可以选择 <code>10.8.0.0</code> 作为起始地址。</p>
<h3>步骤3：列出子网</h3>
<p>从选择的基础IP地址开始，每4个IP地址为一个新的子网。以下是一些可能的子网示例：</p>
<ul>
<li>
<p><code>10.8.0.0/30</code>子网范围：<code>10.8.0.0</code> - <code>10.8.0.3</code></p>
<ul>
<li>网络地址：<code>10.8.0.0</code></li>
<li>可用IP地址：<code>10.8.0.1</code> 和 <code>10.8.0.2</code></li>
<li>广播地址：<code>10.8.0.3</code></li>
</ul>
</li>
<li>
<p><code>10.8.0.4/30</code>子网范围：<code>10.8.0.4</code> - <code>10.8.0.7</code></p>
<ul>
<li>网络地址：<code>10.8.0.4</code></li>
<li>可用IP地址：<code>10.8.0.5</code> 和 <code>10.8.0.6</code></li>
<li>广播地址：<code>10.8.0.7</code></li>
</ul>
</li>
</ul>
<p>依此类推，每个新的子网都增加4个IP地址。</p>
<p><a href="https://www.vsay.net/mycode/276.html" title="OpenVPN分配固定IP：详细指南">OpenVPN分配固定IP：详细指南</a></p>]]></description>
    <pubDate>Tue, 25 Jun 2024 11:41:27 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/277.html</guid>
</item>
<item>
    <title>OpenVPN分配固定IP：详细指南</title>
    <link>https://www.vsay.net/mycode/276.html</link>
    <description><![CDATA[<p>在使用OpenVPN时，您可能希望为每个客户端分配固定的IP地址。这在需要精确控制访问权限或进行特定网络配置时非常有用。本文将详细介绍如何在OpenVPN中为客户端分配固定IP地址。</p>
<h2>为什么要分配固定IP？</h2>
<p>分配固定IP有许多优点，包括：</p>
<ol>
<li><strong>精确的访问控制</strong>：您可以根据固定IP配置防火墙规则。</li>
<li><strong>稳定的网络环境</strong>：某些服务可能依赖于固定的IP地址。</li>
<li><strong>简化管理</strong>：易于追踪和管理客户端设备。</li>
</ol>
<h2>配置步骤</h2>
<h3>1. 配置OpenVPN服务器</h3>
<p>首先，确保您的OpenVPN服务器已正确安装并运行。以下是一个基本的服务器配置文件示例：</p>
<pre><code class="language-conf">port 1194
proto udp
dev tun
ca ca.crt
cert server.crt
key server.key
dh dh.pem
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
;# 添加下面这行
client-config-dir /etc/openvpn/ccd
route 10.8.0.0 255.255.255.0
keepalive 10 120
tls-auth ta.key 0
cipher AES-256-CBC
persist-key
persist-tun
status openvpn-status.log
log-append /var/log/openvpn.log
verb 3</code></pre>
<h3>2. 创建客户端配置目录</h3>
<p>在服务器上创建一个目录，用于存放每个客户端的配置文件：</p>
<pre><code class="language-sh">sudo mkdir -p /etc/openvpn/ccd</code></pre>
<h3>3. 配置客户端文件</h3>
<p>为每个客户端创建一个配置文件，文件名必须与客户端的通用名称（Common Name, CN）相匹配。在<code>/etc/openvpn/ccd</code>目录下创建文件。例如，如果客户端的CN是<code>client1</code>，则创建文件<code>/etc/openvpn/ccd/client1</code>，并在其中指定固定的IP地址：</p>
<pre><code class="language-sh">echo "ifconfig-push 10.8.0.10 225.225.225.0" &gt; /etc/openvpn/ccd/client1</code></pre>
<p>在此示例中，<code>10.8.0.10</code>是客户端的固定IP地址</p>
<p><strong>重点：255.255.255.252子网掩码允许每个子网中有4个IP地址，其中2个是可用的IP地址，1个是网络地址，1个是广播地址。</strong><br />
<strong>例如：</strong></p>
<ul>
<li>10.8.0.0/30子网范围：10.8.0.0 - 10.8.0.3</li>
<li>可用IP对：10.8.0.1和10.8.0.2</li>
<li>10.8.0.4/30子网范围：10.8.0.4 - 10.8.0.7</li>
<li>可用IP对：10.8.0.5和10.8.0.6<br />
确保您为每个客户端分配的IP地址对位于同一个子网范围内。</li>
</ul>
<p>具体请看详细介绍：<a href="https://www.vsay.net/mycode/277.html" title="如何在OpenVPN中配置255.255.255.252子网">如何在OpenVPN中配置255.255.255.252子网</a></p>
<h3>4. 修改服务器配置以启用客户端配置目录</h3>
<p>确保服务器配置文件中包含以下行，以启用客户端配置目录：</p>
<pre><code class="language-conf">client-config-dir /etc/openvpn/ccd</code></pre>
<p>服务端不可存在以下行</p>
<pre><code class="language-conf"># 删掉
route-nopull</code></pre>
<h3>5. 配置客户端</h3>
<p>客户端配置文件应与服务器匹配。以下是一个客户端配置文件示例：</p>
<pre><code class="language-conf">client
dev tun
proto udp
remote your-server-ip 1194
resolv-retry infinite
nobind
persist-key
persist-tun
ca ca.crt
cert client1.crt
key client1.key
tls-auth ta.key 1
cipher AES-256-CBC
verb 3</code></pre>
<h3>6. 重启OpenVPN服务</h3>
<p>完成以上配置后，重启OpenVPN服务以应用更改：</p>
<h4>在Linux上</h4>
<pre><code class="language-sh"># Ubuntu &lt; 20.04
sudo systemctl restart openvpn@server
# Ubuntu &gt;= 20.04
sudo systemctl restart openvpn-server@server.service </code></pre>
<h4>在Windows上</h4>
<p>在命令提示符（管理员权限）中：</p>
<pre><code class="language-sh">net stop openvpnservice
net start openvpnservice</code></pre>
<h2>验证配置</h2>
<p>重启OpenVPN服务后，连接客户端并验证是否获得了正确的固定IP地址。您可以在客户端上运行以下命令来检查IP地址：</p>
<pre><code class="language-sh">ifconfig</code></pre>
<p>或者：</p>
<pre><code class="language-sh">ip addr</code></pre>]]></description>
    <pubDate>Tue, 25 Jun 2024 11:29:09 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/276.html</guid>
</item>
<item>
    <title>别让CDSN毁了你的大学生涯！</title>
    <link>https://www.vsay.net/mood/275.html</link>
    <description><![CDATA[<h2>引言</h2>
<p>大学，是每个人生命中至关重要的一段旅程。它不仅是学术知识的殿堂，更是个人成长和社会交往的重要阶段。然而，在信息爆炸的时代，各种网络平台和社区如雨后春笋般涌现，给大学生活带来了便利的同时，也暗藏诸多陷阱。CDSN（China Digital Space Network）作为其中一个热门的在线社区，吸引了大量大学生的目光。然而，许多学生在追寻知识和交流的过程中，往往不知不觉地陷入了CDSN的陷阱。更有甚者，一些不法分子在CDSN上盗用他人代码，编写成所谓的“精品教程”并高价出售给大学生，严重扰乱了学术环境，误导了无数青少年。</p>
<p>本篇博客将揭示CDSN如何误导大学生，包括如何盗用他人代码并收费出售，影响他们的学业和生活，并提供一些避免陷入其中的方法，帮助大家更好地把握大学时光，实现自我价值。</p>
<h3>CDSN的诱惑与陷阱</h3>
<p>CDSN作为一个技术资源丰富、活跃度高的社区，吸引了大量学生前来学习和交流。然而，这里隐藏着许多看似诱人实则危险的陷阱。</p>
<h4>1. <strong>虚假的学习资源</strong></h4>
<p>在CDSN上，有许多标榜为高质量学习资源的帖子和教程，吸引了众多学生。然而，许多内容并非原创或者来源不明，其中不乏抄袭、盗版的情况。一些不法分子甚至将他人的劳动成果盗用、修改后高价出售，这种行为不仅侵犯了知识产权，也误导了不少急于求成的学生。</p>
<h4>2. <strong>社交与虚荣的陷阱</strong></h4>
<p>CDSN的社区活跃度很高，许多学生乐于在这里发表观点、分享经验，获取“赞”和积分。然而，这种虚拟的社交和赞誉很容易使人沉迷其中，而忽视真正的学习和成长。对于那些追求虚荣心满足感的学生来说，CDSN可能成为一个危险的陷阱，使他们远离实际的学术深度和个人成长。</p>
<h4>3. <strong>技术和思维的依赖</strong></h4>
<p>在CDSN上，技术大神们的帖子和解决方案常常令人赞叹，吸引了众多技术爱好者和学生。然而，盲目依赖他人的技术解决方案而不思考和实践，可能导致学生对于自主学习和独立思考的能力缺乏，这在长远来看对他们的职业发展有着负面影响。</p>
<h3>如何避免CDSN的陷阱？</h3>
<p>面对CDSN带来的诱惑和陷阱，学生们有必要保持清醒头脑和批判性思维：</p>
<ul>
<li>
<p><strong>辨别真伪</strong>：学习资源要选择来源可靠、内容正版的资料，避免依赖盗版和抄袭内容。</p>
</li>
<li>
<p><strong>制定学习计划</strong>：合理安排时间，明确学习目标和步骤，不被无谓的社交和虚荣心所干扰。</p>
</li>
<li>
<p><strong>培养独立思考</strong>：学会分析问题、解决问题的能力，不轻易依赖他人的解决方案。</p>
</li>
<li>
<p><strong>学术诚信</strong>：坚持诚信学习，拒绝任何形式的抄袭和盗版行为，保护自己的学术声誉和未来职业发展。</p>
</li>
</ul>
<h3>真实案例的警示</h3>
<p>不久前，就有一些报道显示，某些不法分子在CDSN上盗用他人代码和学术文章，并将其销售给急于完成作业和项目的大学生。这种行为不仅损害了原作者的权益，也为购买者带来了道德和法律风险。因此，学生们在使用任何在线资源时，务必审慎选择，避免陷入这些不法分子设下的陷阱。</p>
<h3>结语</h3>
<p>大学生活是一个关键的学习和成长阶段，我们需要足够的智慧和警觉，以应对网络平台带来的各种挑战和诱惑。CDSN作为一个资源丰富的社区，为学生提供了学习和交流的便利，但也需要学生们保持警惕，不被其表面光鲜的外表所迷惑。只有在保持独立思考和诚信原则的同时，才能真正把握好大学时光，为自己的未来奠定坚实的基础。</p>
<p>希望本文能够引起广大学生的思考和警醒，避免受到CDSN可能带来的负面影响，真正实现自我价值和成长。</p>]]></description>
    <pubDate>Wed, 19 Jun 2024 16:55:08 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mood/275.html</guid>
</item>
<item>
    <title>来说说正则表达式中的前瞻和后瞻，及经典应用示例。</title>
    <link>https://www.vsay.net/web/274.html</link>
    <description><![CDATA[<p>在正则表达式中，前瞻（lookahead）和后瞻（lookbehind）是两种高级特性，它们用于匹配在某个位置前后特定的条件，而不包括这些条件本身在结果中。</p>
<p>顾名思义，前瞻就是往前看一下，后瞻就是往后看一下（废话文学）</p>
<p>正则表达式（Regular Expressions，简称regex）是一种用于字符串匹配和操作的强大工具。在正则表达式中，前瞻（lookahead）和后瞻（lookbehind）是两种高级特性，它们用于匹配在某个位置前后特定的条件，而不包括这些条件本身在结果中。</p>
<h3>前瞻（Lookahead）</h3>
<p>前瞻用于检查在当前位置之后是否存在某个模式，但不消耗字符。前瞻分为肯定前瞻和否定前瞻。</p>
<h4>肯定前瞻又叫正向前瞻（Positive Lookahead）</h4>
<p>语法：<code>(?=pattern)</code></p>
<p>肯定前瞻在当前位置之后匹配<code>pattern</code>，但不包括<code>pattern</code>本身在结果中。例如：</p>
<ul>
<li>正则表达式：<code>\d(?=abc)</code></li>
<li>目标字符串：<code>1abc2abc3abc</code></li>
<li>结果：匹配到的结果是<code>1</code>, <code>2</code>, <code>3</code></li>
</ul>
<h3>肯定前瞻示例</h3>
<pre><code class="language-javascript">let text = "1abc2abc3abc";
let pattern = /\d(?=abc)/g;

let matches = text.match(pattern);
console.log(matches); // 输出: [ '1', '2', '3' ]</code></pre>
<p>解释：<code>\d</code>匹配一个数字，<code>(?=abc)</code>确保数字后面紧跟<code>abc</code>，但不包括<code>abc</code>在结果中。</p>
<h4>否定前瞻又叫负向前瞻（Negative Lookahead）</h4>
<p>语法：<code>(?!pattern)</code></p>
<p>否定前瞻在当前位置之后不应匹配<code>pattern</code>。例如：</p>
<ul>
<li>正则表达式：<code>\d(?!abc)</code></li>
<li>目标字符串：<code>1xyz2abc3def</code></li>
<li>结果：匹配到的结果是<code>1</code>, <code>3</code></li>
</ul>
<h3>否定前瞻示例</h3>
<pre><code class="language-javascript">let text = "1xyz2abc3def";
let pattern = /\d(?!abc)/g;

let matches = text.match(pattern);
console.log(matches); // 输出: [ '1', '3' ]</code></pre>
<p>解释：<code>\d</code>匹配一个数字，<code>(?!abc)</code>确保数字后面不紧跟<code>abc</code>。</p>
<h3>后瞻（Lookbehind）</h3>
<p>后瞻用于检查在当前位置之前是否存在某个模式，但不消耗字符。后瞻分为肯定后瞻和否定后瞻。</p>
<h4>肯定后瞻又叫正向后瞻（Positive Lookbehind）</h4>
<p>语法：<code>(?&lt;=pattern)</code></p>
<p>肯定后瞻在当前位置之前匹配<code>pattern</code>，但不包括<code>pattern</code>本身在结果中。例如：</p>
<ul>
<li>正则表达式：<code>(?&lt;=abc)\d</code></li>
<li>目标字符串：<code>abc1def2abc3</code></li>
<li>结果：匹配到的结果是<code>1</code>, <code>3</code></li>
</ul>
<h3>肯定后瞻示例</h3>
<pre><code class="language-javascript">let text = "abc1def2abc3";
let pattern = /(?&lt;=abc)\d/g;

let matches = text.match(pattern);
console.log(matches); // 输出: [ '1', '3' ]</code></pre>
<p>解释：<code>\d</code>匹配一个数字，<code>(?&lt;=abc)</code>确保数字前面紧跟<code>abc</code>，但不包括<code>abc</code>在结果中。</p>
<h4>否定后瞻又叫负向后瞻（Negative Lookbehind）</h4>
<p>语法：<code>(?&lt;!pattern)</code></p>
<p>否定后瞻在当前位置之前不应匹配<code>pattern</code>。例如：</p>
<ul>
<li>正则表达式：<code>(?&lt;!abc)\d</code></li>
<li>目标字符串：<code>xyz1abc2def3</code></li>
<li>结果：匹配到的结果是<code>1</code>, <code>3</code></li>
</ul>
<h3>否定后瞻示例</h3>
<pre><code class="language-javascript">let text = "xyz1abc2def3";
let pattern = /(?&lt;!abc)\d/g;
let matches = text.match(pattern);
console.log(matches); // 输出: [ '1', '3' ]</code></pre>
<p>解释：<code>\d</code>匹配一个数字，<code>(?&lt;!abc)</code>确保数字前面不紧跟<code>abc</code>。</p>
<h3>综合示例</h3>
<p>假设有一个字符串列表，我们希望匹配所有以美元符号开头的数字，但这些数字后面必须紧跟一个非数字字符：</p>
<pre><code class="language-javascript">let text = "The prices are $30, $50, and $70each.";
let pattern = /\$\d+(?=\D)/g;

let matches = text.match(pattern);
console.log(matches); // 输出: [ '$30', '$50' ]</code></pre>
<p>解释：<code>\$</code>匹配美元符号，<code>\d+</code>匹配一个或多个数字，<code>(?=\D)</code>确保这些数字后面紧跟一个非数字字符，但不包括这个非数字字符在结果中。</p>
<h3>经典应用</h3>
<p>好的，这里提供一些经典的示例，展示如何在JavaScript中使用正则表达式的前瞻和后瞻来解决实际问题。</p>
<h3>1. 匹配以特定后缀结尾的单词</h3>
<p>假设你需要匹配所有以&quot;ing&quot;结尾的单词，但不包括&quot;ing&quot;在结果中：</p>
<pre><code class="language-javascript">let text = "I am running and jumping, not walking or singing.";
let pattern = /\b\w+(?=ing\b)/g;

let matches = text.match(pattern);
console.log(matches); // 输出: [ 'runn', 'jump', 'walk', 'sing' ]</code></pre>
<p>解释：<code>\b\w+</code>匹配一个单词，<code>(?=ing\b)</code>确保单词后面是&quot;ing&quot;，但不包括&quot;ing&quot;在结果中。</p>
<h3>2. 提取所有以特定前缀开头的单词</h3>
<p>假设你需要提取所有以&quot;pre&quot;开头的单词，但不包括&quot;pre&quot;在结果中：</p>
<pre><code class="language-javascript">let text = "This is a prerequisite and a preview of the presentation.";
let pattern = /(?&lt;=\bpre)\w+\b/g;

let matches = text.match(pattern);
console.log(matches); // 输出: [ 'requisite', 'view', 'sentation' ]</code></pre>
<p>解释：<code>(?&lt;=\bpre)</code>确保单词前面是&quot;pre&quot;，<code>\w+\b</code>匹配单词的其余部分。</p>
<h3>3. 查找不在引号中的单词</h3>
<p>假设你需要查找不在引号（单引号或双引号）中的单词：</p>
<pre><code class="language-javascript">let text = `He said "Hello" and then he said 'Goodbye'.`;
let pattern = /\b\w+\b(?![^'"]*['"])/g;

let matches = text.match(pattern);
console.log(matches); // 输出: [ 'He', 'said', 'and', 'then', 'he', 'said' ]</code></pre>
<p>解释：<code>\b\w+\b</code>匹配一个单词，<code>(?![^'"]*['"])</code>确保单词后面不跟随任意数量的非引号字符然后是引号。</p>
<h3>4. 提取所有数字并忽略负数</h3>
<p>假设你需要提取所有数字，但忽略负数：</p>
<pre><code class="language-javascript">let text = "The temperatures are -5, 10, 15, and -20 degrees.";
let pattern = /(?&lt;!-)\b\d+\b/g;

let matches = text.match(pattern);
console.log(matches); // 输出: [ '10', '15' ]</code></pre>
<p>解释：<code>(?&lt;!-)</code>确保数字前面没有负号，<code>\b\d+\b</code>匹配数字。</p>
<h3>5. 提取邮件地址的用户名部分</h3>
<p>假设你需要提取邮件地址的用户名部分（&quot;@&quot;之前的部分）：</p>
<pre><code class="language-javascript">let text = "Contact us at support@example.com or sales@company.com.";
let pattern = /\b[A-Za-z0-9._%+-]+(?=@)/g;

let matches = text.match(pattern);
console.log(matches); // 输出: [ 'support', 'sales' ]</code></pre>
<p>解释：<code>[A-Za-z0-9._%+-]+</code>匹配邮件地址的用户名部分，<code>(?=@)</code>确保其后跟随&quot;@&quot;，但不包括&quot;@&quot;在结果中。</p>
<p>这些示例展示了如何使用前瞻和后瞻来解决实际问题，帮助你更灵活地处理字符串匹配任务。希望这些示例对你有帮助！如果你有其他特定需求或问题，请随时告诉我。</p>
<h3>总结</h3>
<p>前瞻和后瞻在正则表达式中提供了灵活的匹配能力，可以用于复杂的模式匹配需求。理解它们的语法和应用场景，将有助于更高效地处理字符串匹配问题。</p>]]></description>
    <pubDate>Tue, 18 Jun 2024 17:42:38 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/274.html</guid>
</item>
<item>
    <title>element el-form 二级数组多维数组的rules验证配置方法，遇到数组如何配置rules</title>
    <link>https://www.vsay.net/web/273.html</link>
    <description><![CDATA[<p>开发中遇到form动态表单rules的验证问题，官方手册中并没有讲到<br />
网上也没有正确的解决方案<br />
摸索了一下成功了，记录一下</p>
<h3>先说需求</h3>
<p>需求是params是动态添加的，当有字段添加进去后，生成input</p>
<h3>form表单数组</h3>
<pre><code class="language-javascript">form:{
    name:'name', //名称
    params:{
        // tel:'',
        // sex:''
    } //参数
}</code></pre>
<h3>rules验证规则</h3>
<pre><code class="language-javascript">rules:{
    name:{
        required: true,
        message: `名称不能为空`,
        trigger: "blur",
    },
    params:{
        // 类似与下面这样
        // tel:{
        //  required: true,
        //  message: `tel不能为空`,
        //  trigger: "blur",
        // },
        // sex:{
        //  required: true,
        //  message: `sex不能为空`,
        //  trigger: "blur",
        // }
    }
}</code></pre>
<p><strong>重点：</strong><br />
rules结构必须与form一致，要做二级rules则也需要有params属性存在</p>
<h3>html</h3>
<p>需求是params是动态添加的，当有字段添加进去后，生成input</p>
<pre><code class="language-html">&lt;el-form :rules="rules" :model="form" ref="form"&gt;
    &lt;el-form-item :prop="'params.'+index" :label="'参数：'+index" v-for="(item,index) in form.params" :key="index"&gt;
        &lt;el-input type="text" v-model="form.params[index]"&gt;&lt;/el-input&gt;
    &lt;/el-form-item&gt;
&lt;/el-form&gt;</code></pre>
<p><strong>重点：</strong><br />
el-form-item的prop属性必须是form中的数组或者对象的key+.+子key</p>
<h3>js</h3>
<pre><code class="language-javascript">// 表单验证
this.$refs["form"].validate((valid) =&gt; {
    if (valid) {
        // 验证成功
    }
}</code></pre>
<p>用此方法，不管是数组或者对象都可以做验证<br />
<a href="https://www.vsay.net/content/uploadfile/202406/thum-b6961718698978.png"><img src="https://www.vsay.net/content/uploadfile/202406/b6961718698978.png" alt="" /></a></p>
<h3>对象方式完整代码：</h3>
<pre><code class="language-javascript">&lt;template&gt;
  &lt;div style="padding: 20px; width: 500px"&gt;
    &lt;el-row&gt;
      &lt;el-input type="text" v-model="addtext"&gt;&lt;/el-input&gt;
      &lt;el-button type="primary" @click="add"&gt;加一行&lt;/el-button&gt;
    &lt;/el-row&gt;
    &lt;el-form :rules="rules" :model="form" ref="form"&gt;
      &lt;el-form-item prop="name" label="名称"&gt;
        &lt;el-input type="text" v-model="form.name"&gt;&lt;/el-input&gt;
      &lt;/el-form-item&gt;
      &lt;el-form-item
        :prop="'params.' + index"
        :label="'参数：' + index"
        v-for="(item, index) in form.params"
        :key="index"
      &gt;
        &lt;el-input type="text" v-model="form.params[index]"&gt;&lt;/el-input&gt;
      &lt;/el-form-item&gt;
      &lt;el-form-item&gt;
        &lt;el-button type="primary" @click="onSubmit"&gt;提交&lt;/el-button&gt;
      &lt;/el-form-item&gt;
    &lt;/el-form&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  data() {
    return {
      addtext: "",
      form: {
        name: "name", //名称
        params: {}, //参数
      },
      rules: {
        name: {
          required: true,
          message: `名称不能为空`,
          trigger: "blur",
        },
        params: {},
      },
    };
  },
  methods: {
    add() {
      if (!this.addtext) return;
      // 必须用set方法
      this.$set(this.form.params, this.addtext, "");
      // 加入rules
      this.$set(this.rules.params, this.addtext, [
        {
          required: true,
          message: `${this.addtext}不能为空`,
          trigger: "blur",
        },
      ]);
      this.addtext = ''
    },
    onSubmit() {
      this.$refs["form"].validate((valid) =&gt; {
        console.log(valid);
      });
    },
  },
};
&lt;/script&gt;

&lt;style lang="scss" scoped&gt;
&lt;/style&gt;</code></pre>
<p>数组也是一个效果，不过是对象的key边成了数字<br />
<a href="https://www.vsay.net/content/uploadfile/202406/thum-bdb81718699367.png"><img src="https://www.vsay.net/content/uploadfile/202406/bdb81718699367.png" alt="" /></a></p>
<h3>数组方式完整代码：</h3>
<pre><code class="language-javascript">&lt;template&gt;
  &lt;div style="padding: 20px; width: 500px"&gt;
    &lt;el-row&gt;
      &lt;el-button type="primary" @click="add"&gt;加一行&lt;/el-button&gt;
    &lt;/el-row&gt;
    &lt;el-form :rules="rules" :model="form" ref="form"&gt;
      &lt;el-form-item prop="name" label="名称"&gt;
        &lt;el-input type="text" v-model="form.name"&gt;&lt;/el-input&gt;
      &lt;/el-form-item&gt;
      &lt;el-form-item
        :prop="'params.' + index"
        :label="'参数：' + index"
        v-for="(item, index) in form.params"
        :key="index"
      &gt;
        &lt;el-input type="text" v-model="form.params[index]"&gt;&lt;/el-input&gt;
      &lt;/el-form-item&gt;
      &lt;el-form-item&gt;
        &lt;el-button type="primary" @click="onSubmit"&gt;提交&lt;/el-button&gt;
      &lt;/el-form-item&gt;
    &lt;/el-form&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  data() {
    return {
      addtext: "",
      form: {
        name: "name", //名称
        params: [], //参数
      },
      rules: {
        name: {
          required: true,
          message: `名称不能为空`,
          trigger: "blur",
        },
        params: [],
      },
    };
  },
  methods: {
    add() {
      // 加入rules
      this.$set(this.rules.params, this.form.params.length, [
        {
          required: true,
          message: `${this.form.params.length}不能为空`,
          trigger: "blur",
        },
      ]);
      // 必须用set方法
      this.$set(this.form.params, this.form.params.length, "");
      this.addtext = ''
    },
    onSubmit() {
      this.$refs["form"].validate((valid) =&gt; {
        console.log(valid);
      });
    },
  },
};
&lt;/script&gt;

&lt;style lang="scss" scoped&gt;
&lt;/style&gt;</code></pre>]]></description>
    <pubDate>Tue, 18 Jun 2024 15:50:54 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/273.html</guid>
</item>
<item>
    <title>php curl详解及完整的一个curl类库</title>
    <link>https://www.vsay.net/php/272.html</link>
    <description><![CDATA[<h2>目录</h2>
<ul>
<li><a href="#m1">一、cUrl详解</a></li>
<li><a href="#m2">二、cURL类库</a></li>
<li><a href="#m2">三、类库使用示例</a></li>
</ul>
<div id="m1"></div>
<p><br></p>
<h2>一、cUrl详解</h2>
<p>PHP 中使用 <code>cURL</code> 的更详细说明，包括各种高级用法、错误处理、文件上传、认证、以及更多的示例。</p>
<h3>1. 初始化 cURL 会话</h3>
<p><code>curl_init()</code> 用于初始化一个新的 cURL 会话并返回一个 cURL 句柄：</p>
<pre><code class="language-php">$ch = curl_init();</code></pre>
<h3>2. 设置 cURL 选项</h3>
<p>使用 <code>curl_setopt()</code> 来设置不同的选项。以下是一些常用选项及其解释：</p>
<ul>
<li>
<p><strong>CURLOPT_URL</strong>: 设置请求的 URL。</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_URL, "https://example.com");</code></pre>
</li>
<li>
<p><strong>CURLOPT_RETURNTRANSFER</strong>: 返回响应内容，而不是直接输出。</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);</code></pre>
</li>
<li>
<p><strong>CURLOPT_POST</strong>: 发送 POST 请求。</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_POST, true);</code></pre>
</li>
<li>
<p><strong>CURLOPT_POSTFIELDS</strong>: 发送的 POST 数据。</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array('key1' =&gt; 'value1', 'key2' =&gt; 'value2')));</code></pre>
</li>
<li>
<p><strong>CURLOPT_HTTPHEADER</strong>: 设置自定义请求头。</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Authorization: Bearer your-token-here'));</code></pre>
</li>
<li>
<p><strong>CURLOPT_USERAGENT</strong>: 设置用户代理字符串。</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; PHP cURL)");</code></pre>
</li>
<li>
<p><strong>CURLOPT_TIMEOUT</strong>: 设置请求的超时时间（秒）。</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_TIMEOUT, 30);</code></pre>
</li>
<li>
<p><strong>CURLOPT_FOLLOWLOCATION</strong>: 是否跟随重定向。</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);</code></pre>
</li>
</ul>
<h3>3. 执行 cURL 会话并获取响应</h3>
<p>使用 <code>curl_exec()</code> 执行会话并获取响应内容：</p>
<pre><code class="language-php">$response = curl_exec($ch);</code></pre>
<h3>4. 错误处理</h3>
<p>使用 <code>curl_errno()</code> 和 <code>curl_error()</code> 检查和获取错误信息：</p>
<pre><code class="language-php">if ($response === false) {
    echo 'Curl error: ' . curl_error($ch);
}</code></pre>
<h3>5. 关闭 cURL 会话</h3>
<p>完成后，使用 <code>curl_close()</code> 关闭 cURL 会话：</p>
<pre><code class="language-php">curl_close($ch);</code></pre>
<h3>GET 请求示例</h3>
<p>这是一个完整的 GET 请求并处理响应和错误：</p>
<pre><code class="language-php">$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

if ($response === false) {
    echo 'Curl error: ' . curl_error($ch);
} else {
    echo 'Response: ' . $response;
}

curl_close($ch);</code></pre>
<h3>POST 请求示例</h3>
<p>这是一个完整的 POST 请求示例，包括发送数据和处理响应：</p>
<pre><code class="language-php">$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com/api");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array('key1' =&gt; 'value1', 'key2' =&gt; 'value2')));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

if ($response === false) {
    echo 'Curl error: ' . curl_error($ch);
} else {
    echo 'Response: ' . $response;
}

curl_close($ch);</code></pre>
<h3>文件上传示例</h3>
<p>以下是如何通过 <code>cURL</code> 上传文件的示例：</p>
<pre><code class="language-php">$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com/upload");
curl_setopt($ch, CURLOPT_POST, true);

$file = new CURLFile('/path/to/your/file.jpg', 'image/jpeg', 'file.jpg');
$data = array('file' =&gt; $file);

curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

if ($response === false) {
    echo 'Curl error: ' . curl_error($ch);
} else {
    echo 'Response: ' . $response;
}

curl_close($ch);</code></pre>
<h3>设置请求头</h3>
<p>你可以使用 <code>CURLOPT_HTTPHEADER</code> 来设置自定义请求头：</p>
<pre><code class="language-php">$headers = array(
    'Content-Type: application/json',
    'Authorization: Bearer your-token-here'
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);</code></pre>
<h3>GET 请求带参数示例</h3>
<p>使用查询字符串发送 GET 请求参数：</p>
<pre><code class="language-php">$params = array(
    'param1' =&gt; 'value1',
    'param2' =&gt; 'value2'
);
$url = "https://example.com?" . http_build_query($params);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

if ($response === false) {
    echo 'Curl error: ' . curl_error($ch);
} else {
    echo 'Response: ' . $response;
}

curl_close($ch);</code></pre>
<h3>处理响应</h3>
<p>获取 HTTP 状态码和响应头：</p>
<pre><code class="language-php">// 获取 HTTP 状态码
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// 获取所有响应头
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
$headers = curl_exec($ch);

// 关闭会话
curl_close($ch);

// 输出状态码和响应头
echo "HTTP Code: $http_code\n";
echo "Headers: $headers";</code></pre>
<h3>带有认证的请求</h3>
<h4>Basic Auth</h4>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_USERPWD, "username:password");</code></pre>
<h4>Bearer Token</h4>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Bearer your-token-here'));</code></pre>
<h3>高级示例：完整的错误处理</h3>
<pre><code class="language-php">$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

if ($response === false) {
    $error_msg = curl_error($ch);
    echo 'Curl error: ' . $error_msg;
} else {
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($http_code == 200) {
        echo 'Response: ' . $response;
    } else {
        echo "HTTP Error: $http_code\n";
    }
}

curl_close($ch);</code></pre>
<h3>设置代理</h3>
<p>如果需要通过代理发送请求：</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_PROXY, 'http://proxyserver:port');
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'username:password');</code></pre>
<h3>设置 SSL 选项</h3>
<p>如果需要处理 SSL/TLS 证书：</p>
<pre><code class="language-php">// 不验证证书
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

// 设置 CA 证书路径
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem');</code></pre>
<h3>带有 Cookies 的请求</h3>
<p>如果需要在请求中包含 Cookies：</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_COOKIE, "name1=value1; name2=value2");</code></pre>
<div id="m2"></div>
<p><br></p>
<h2>二、完整类库</h2>
<p>当然，以下是添加了详细注释的 PHP <code>cURL</code> 类库，包括对 <code>cURL</code> 选项的注释：</p>
<pre><code class="language-php">&lt;?php

class CurlRequest
{
    private $ch;        // cURL 句柄
    private $response;  // cURL 响应内容
    private $error;     // cURL 错误信息
    private $httpCode;  // HTTP 状态码
    private $headers;   // 请求头数组

    /**
     * 构造函数，初始化 cURL 会话和请求头数组
     */
    public function __construct()
    {
        $this-&gt;ch = curl_init();
        $this-&gt;headers = [];
    }

    /**
     * 设置 cURL 选项
     *
     * @param array $options cURL 选项数组
     */
    private function setOptions(array $options)
    {
        curl_setopt_array($this-&gt;ch, $options);
    }

    /**
     * 设置自定义请求头
     *
     * @param array $headers 请求头数组
     * @return $this 当前实例
     */
    public function setHeaders(array $headers)
    {
        $this-&gt;headers = $headers;
        return $this;
    }

    /**
     * 发送 GET 请求
     *
     * @param string $url 请求的 URL
     * @return $this 当前实例
     */
    public function get($url)
    {
        $options = [
            CURLOPT_URL =&gt; $url,                     // 请求的 URL
            CURLOPT_RETURNTRANSFER =&gt; true,          // 将响应结果返回为字符串，而不是直接输出
            CURLOPT_HTTPHEADER =&gt; $this-&gt;headers,    // 自定义请求头
            CURLOPT_FOLLOWLOCATION =&gt; true,          // 启用时会将服务器返回的 "Location: " 放在 header 中递归的返回给服务器
        ];
        $this-&gt;setOptions($options);

        $this-&gt;execute();
        return $this;
    }

    /**
     * 发送 POST 请求
     *
     * @param string $url 请求的 URL
     * @param array $data 发送的 POST 数据
     * @param bool $json 是否以 JSON 格式发送数据
     * @return $this 当前实例
     */
    public function post($url, $data = [], $json = false)
    {
        $options = [
            CURLOPT_URL =&gt; $url,                     // 请求的 URL
            CURLOPT_POST =&gt; true,                   // 启用 POST 请求
            CURLOPT_RETURNTRANSFER =&gt; true,         // 将响应结果返回为字符串，而不是直接输出
            CURLOPT_HTTPHEADER =&gt; $this-&gt;headers,   // 自定义请求头
            CURLOPT_FOLLOWLOCATION =&gt; true,         // 启用时会将服务器返回的 "Location: " 放在 header 中递归的返回给服务器
        ];

        if ($json) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data); // 发送 JSON 数据
            $this-&gt;headers[] = 'Content-Type: application/json'; // 设置请求头为 JSON
        } else {
            $options[CURLOPT_POSTFIELDS] = http_build_query($data); // 发送表单数据
        }

        $this-&gt;setOptions($options);
        $this-&gt;execute();
        return $this;
    }

    /**
     * 上传文件
     *
     * @param string $url 请求的 URL
     * @param string $filePath 上传文件的路径
     * @param string|null $fileName 上传文件的名称
     * @param string $fieldName 表单字段名称
     * @return $this 当前实例
     * @throws Exception 如果文件不存在
     */
    public function upload($url, $filePath, $fileName = null, $fieldName = 'file')
    {
        if (!file_exists($filePath)) {
            throw new Exception('File does not exist: ' . $filePath);
        }

        $file = new CURLFile($filePath);
        if ($fileName) {
            $file-&gt;setPostFilename($fileName);
        }

        $data = [$fieldName =&gt; $file];
        $options = [
            CURLOPT_URL =&gt; $url,                     // 请求的 URL
            CURLOPT_POST =&gt; true,                   // 启用 POST 请求
            CURLOPT_POSTFIELDS =&gt; $data,            // 发送的文件数据
            CURLOPT_RETURNTRANSFER =&gt; true,         // 将响应结果返回为字符串，而不是直接输出
            CURLOPT_HTTPHEADER =&gt; $this-&gt;headers,   // 自定义请求头
            CURLOPT_FOLLOWLOCATION =&gt; true,         // 启用时会将服务器返回的 "Location: " 放在 header 中递归的返回给服务器
        ];

        $this-&gt;setOptions($options);
        $this-&gt;execute();
        return $this;
    }

    /**
     * 设置代理
     *
     * @param string $proxy 代理地址
     * @param string|null $proxyUserPwd 代理的用户名和密码
     * @return $this 当前实例
     */
    public function setProxy($proxy, $proxyUserPwd = null)
    {
        $options = [
            CURLOPT_PROXY =&gt; $proxy,    // 设置代理服务器地址
        ];

        if ($proxyUserPwd) {
            $options[CURLOPT_PROXYUSERPWD] = $proxyUserPwd; // 设置代理的用户名和密码
        }

        $this-&gt;setOptions($options);
        return $this;
    }

    /**
     * 设置 SSL 选项
     *
     * @param bool $verifyPeer 是否验证对等证书
     * @param int $verifyHost 是否验证主机名
     * @return $this 当前实例
     */
    public function setSSL($verifyPeer = true, $verifyHost = 2)
    {
        $options = [
            CURLOPT_SSL_VERIFYPEER =&gt; $verifyPeer,   // 是否验证对等证书
            CURLOPT_SSL_VERIFYHOST =&gt; $verifyHost,   // 是否验证主机名
        ];
        $this-&gt;setOptions($options);
        return $this;
    }

    /**
     * 执行 cURL 请求
     */
    private function execute()
    {
        $this-&gt;response = curl_exec($this-&gt;ch);     // 执行 cURL 请求并获取响应
        $this-&gt;error = curl_error($this-&gt;ch);       // 获取 cURL 错误信息
        $this-&gt;httpCode = curl_getinfo($this-&gt;ch, CURLINFO_HTTP_CODE); // 获取 HTTP 状态码
    }

    /**
     * 获取响应内容
     *
     * @return string|null 响应内容
     */
    public function getResponse()
    {
        return $this-&gt;response;
    }

    /**
     * 获取错误信息
     *
     * @return string|null 错误信息
     */
    public function getError()
    {
        return $this-&gt;error;
    }

    /**
     * 获取 HTTP 状态码
     *
     * @return int HTTP 状态码
     */
    public function getHttpCode()
    {
        return $this-&gt;httpCode;
    }

    /**
     * 关闭 cURL 会话
     */
    public function close()
    {
        curl_close($this-&gt;ch);
    }
}</code></pre>
<div id="m3"></div>
<p><br></p>
<h2>三、类库使用示例</h2>
<h3>类库说明</h3>
<p>这个类库封装了更复杂的 <code>cURL</code> 操作，提供了以下方法：</p>
<ul>
<li><code>setHeaders(array $headers)</code>: 设置自定义请求头。</li>
<li><code>get($url)</code>: 发送 GET 请求。</li>
<li><code>post($url, $data = [], $json = false)</code>: 发送 POST 请求。支持发送 JSON 数据。</li>
<li><code>upload($url, $filePath, $fileName = null, $fieldName = 'file')</code>: 上传文件。</li>
<li><code>setProxy($proxy, $proxyUserPwd = null)</code>: 设置代理。</li>
<li><code>setSSL($verifyPeer = true, $verifyHost = 2)</code>: 设置 SSL 选项。</li>
<li><code>getResponse()</code>: 获取响应内容。</li>
<li><code>getError()</code>: 获取错误信息。</li>
<li><code>getHttpCode()</code>: 获取 HTTP 状态码。</li>
<li><code>close()</code>: 关闭 cURL 会话。</li>
</ul>
<h3>使用示例</h3>
<h4>GET 请求示例</h4>
<pre><code class="language-php">$curl = new CurlRequest();
$response = $curl-&gt;setHeaders(['Accept: application/json'])
                 -&gt;get("https://jsonplaceholder.typicode.com/posts/1")
                 -&gt;getResponse();
if ($curl-&gt;getError()) {
    echo "GET Error: " . $curl-&gt;getError();
} else {
    echo "GET Response: " . $response;
}
$curl-&gt;close();</code></pre>
<h4>POST 请求示例</h4>
<pre><code class="language-php">$curl = new CurlRequest();
$postData = ['title' =&gt; 'foo', 'body' =&gt; 'bar', 'userId' =&gt; 1];
$response = $curl-&gt;setHeaders(['Content-Type: application/json'])
                 -&gt;post("https://jsonplaceholder.typicode.com/posts", $postData, true)
                 -&gt;getResponse();
if ($curl-&gt;getError()) {
    echo "POST Error: " . $curl-&gt;getError();
} else {
    echo "POST Response: " . $response;
}
$curl-&gt;close();</code></pre>
<h4>文件上传示例</h4>
<pre><code class="language-php">$curl = new CurlRequest();
$response = $curl-&gt;upload("https://example.com/upload", "/path/to/your/file.jpg", "uploaded_file.jpg")
                 -&gt;getResponse();
if ($curl-&gt;getError()) {
    echo "Upload Error: " . $curl-&gt;getError();
} else {
    echo "Upload Response: " . $response;
}
$curl-&gt;close();</code></pre>
<h4>设置代理示例</h4>
<pre><code class="language-php">$curl = new CurlRequest();
$response = $curl-&gt;setProxy('http://proxyserver:port', 'username:password')
                 -&gt;get("https://example.com")
                 -&gt;getResponse();
if ($curl-&gt;getError()) {
    echo "Proxy Error: " . $curl-&gt;getError();
} else {
    echo "Proxy Response: " . $response;
}
$curl-&gt;close();</code></pre>
<h4>设置 SSL 选项示例</h4>
<pre><code class="language-php">$curl = new CurlRequest();
$response = $curl-&gt;setSSL(false)
                 -&gt;get("https://self-signed.badssl.com/")
                 -&gt;getResponse();
if ($curl-&gt;getError()) {
    echo "SSL Error: " . $curl-&gt;getError();
} else {
    echo "SSL Response: " . $response;
}
$curl-&gt;close();</code></pre>
<p>拿上直接用吧宝子</p>]]></description>
    <pubDate>Sun, 16 Jun 2024 16:05:31 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/php/272.html</guid>
</item>
<item>
    <title>ffmpeg将m3u8转换成mp4</title>
    <link>https://www.vsay.net/mycode/271.html</link>
    <description><![CDATA[<p>有两种方法可以实现，<br />
第一种：直接使用ffmpeg转成mp4 （缺点：ffmpeg下载m3u8太慢了）<br />
第二种：自行下载m3u8到本地，然后由ffmpeg转，下载ts的时候可以使用多线程，速度大大的提升了。</p>
<h3>一、直接使用ffmpeg转mp4 缺点：慢</h3>
<p>命令：</p>
<pre><code class="language-bash">ffmpeg -y -allowed_extensions ALL -protocol_whitelist "file,crypto,http,https,tcp" -i "https://xxx/xxx.m3u8" -c copy "./output.mp4"</code></pre>
<ul>
<li><strong>-y 如果遇到文件覆盖或者其他需要确认的地方会自动帮你确认</strong></li>
<li><strong>-allowed_extensions ALL 允许所有文件扩展名</strong></li>
<li><strong>-protocol_whitelist &quot;file,crypto,http,https,tcp&quot; 允许你指定哪些协议可以被用于输入或输出操作</strong><br />
file - 本地文件系统<br />
crypto - 加密协议<br />
http - 超文本传输协议<br />
https - 安全超文本传输协议<br />
tcp - 传输控制协议<br />
udp - 用户数据报协议<br />
rtsp - 实时流协议<br />
rtmp - 实时消息传输协议<br />
srt - 安全可靠传输协议</li>
<li><strong>-i &quot;<a href="https://xxx/xxx.m3u8">https://xxx/xxx.m3u8</a>&quot;</strong> m3u8地址，即输入来源地址</li>
<li>-c copy 对编码不做任何修改，直接复刻过来</li>
<li>&quot;./output.mp4&quot; 本地保存路径</li>
</ul>
<h3>二、自己下载m3u8的TS切片，然后本地转换</h3>
<p>下载m3u8的切片就不详说了，主要原理就是打开.m3u8文件，解析ts和key的路径，保存在本地目录中。<br />
注意：保存本地的时候，修改m3u8中的key和ts的路径！！！<br />
使用本地转换命令：</p>
<pre><code class="language-bash">ffmpeg -y -allowed_extensions ALL -protocol_whitelist "file,crypto" -i "/root/index.m3u8" -c copy "/root/index.mp4"</code></pre>
<p>-protocol_whitelist 就只需要设置file和crypto，本地目录不需要http和tcp</p>]]></description>
    <pubDate>Sun, 16 Jun 2024 15:24:43 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/mycode/271.html</guid>
</item>
<item>
    <title>一个简单的nodejs多任务线程池的实现类</title>
    <link>https://www.vsay.net/web/270.html</link>
    <description><![CDATA[<p>不确定npm有么有类似的项目，由于本人只做一个小爬虫，所以做了这个线程池类<br />
比较简单，记录一下，以便重复使用！</p>
<h3>ThreadPool</h3>
<pre><code class="language-javascript">new ThreadPool(name ,concurrency, progressCallback);</code></pre>
<ul>
<li>name 线程池名称，可随意设置</li>
<li>同时执行的任务数</li>
<li>任务进度回调</li>
<li>progressCallback 接受两个参数completed 已完成任务 total 任务数</li>
</ul>
<h3>enqueue</h3>
<p>装载任务<br />
示例：</p>
<pre><code class="language-javascript">for (let i = 0; i &lt; 5; i++) {
    pool.enqueue(sampleTask, 3); // 每个任务最多重试 3 次
}</code></pre>
<h3>waitForAll</h3>
<p>等待任务完成<br />
示例：</p>
<pre><code class="language-javascript">await pool.waitForAll();</code></pre>
<p>完整示例：</p>
<pre><code class="language-javascript">// 使用示例:
const pool = new ThreadPool(2, (completed, total) =&gt; {
    console.log(`进度: ${completed}/${total}`);
});

async function sampleTask() {
    // 模拟一个偶尔失败的任务
    if (Math.random() &lt; 0.5) throw new Error('任务失败');
    console.log('任务成功完成');
}

(async () =&gt; {
    for (let i = 0; i &lt; 5; i++) {
        pool.enqueue(sampleTask, 3); // 每个任务最多重试 3 次
    }
    await pool.waitForAll();
    console.log('所有任务完成');
})();</code></pre>
<p>看代码：</p>
<pre><code class="language-javascript">class ThreadPool {
    constructor(concurrency, progressCallback) {
        this.concurrency = concurrency;
        this.running = 0;
        this.queue = [];
        this.completedTasks = 0;
        this.totalTasks = 0;
        this.progressCallback = progressCallback || (() =&gt; { });
        this.resolveDone = null;
        this.donePromise = new Promise(resolve =&gt; this.resolveDone = resolve);
    }

    async runTask(task, retries) {
        this.running++;
        let attempt = 0;
        while (attempt &lt;= retries) {
            try {
                await task();
                break; // 任务成功，退出循环
            } catch (error) {
                attempt++;
                if (attempt &gt; retries) {
                    console.error('任务在最大重试次数后仍然失败:', error);
                } else {
                    console.warn(`任务失败，正在重试... (第 ${attempt}/${retries} 次)`);
                }
            }
        }
        this.running--;
        this.completedTasks++;
        this.progressCallback(this.completedTasks, this.totalTasks);
        this.fillQueue();
    }

    fillQueue() {
        while (this.running &lt; this.concurrency &amp;&amp; this.queue.length &gt; 0) {
            const { task, retries } = this.queue.shift();
            this.runTask(task, retries);
        }
        if (this.running === 0 &amp;&amp; this.queue.length === 0) {
            this.resolveDone();
        }
    }

    async enqueue(task, retries = 0) {
        if (typeof task !== 'function' || task.constructor.name !== 'AsyncFunction') {
            throw new Error('任务必须是一个异步函数');
        }

        this.totalTasks++;
        const taskObject = { task, retries };
        if (this.running &lt; this.concurrency) {
            this.runTask(task, retries);
        } else {
            this.queue.push(taskObject);
        }
    }

    async waitForAll() {
        if (this.running === 0 &amp;&amp; this.queue.length === 0) {
            return;
        }
        await this.donePromise;
        // 重置 donePromise 以便将来使用
        this.donePromise = new Promise(resolve =&gt; this.resolveDone = resolve);
    }
}</code></pre>]]></description>
    <pubDate>Sun, 16 Jun 2024 15:13:16 +0800</pubDate>
    <author>huyiwei</author>
    <guid>https://www.vsay.net/web/270.html</guid>
</item></channel>
</rss>