<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>魔王の博客</title><description>高兴的使用astro构建</description><link>https://blog.loli.wang/</link><language>en-us</language><item><title>[记录] Ubuntu 安装 dify</title><link>https://blog.loli.wang/blog/2026-01-11-installdify/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2026-01-11-installdify/doc/</guid><description>[记录] Ubuntu 安装 dify</description><pubDate>Sun, 11 Jan 2026 15:27:29 GMT</pubDate><content:encoded>&lt;h4&gt;安装Dify 前需要做的准备&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;  # 安装Git 
  sudo apt install git -y
  # 检查是否安装成功
  git --version
  # 安装 Docker
  sudo mkdir -p /etc/apt/keyrings
  curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  # 安装 Docker Engine
  sudo apt update
  sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
  # 检查docker 安装
  docker --version
  docker compose version

 


&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;安装dify&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt; # 拉取 Dify 官方仓库 
  cd /opt
  sudo git clone https://github.com/langgenius/dify.git
  # 复制环境变量并改名
  cp .env.example .env
  # 拉取镜像并启动
  docker compose up -d 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;http://服务器IP  直接可访问&lt;/p&gt;
&lt;h3&gt;常用维护指令&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 停止服务
docker compose down
# 重新启动
docker compose up -d
# 更新 Dify
cd /opt/dify
git pull

cd docker
docker compose pull
docker compose up -d

# 完全重置
docker compose down -v

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2026-01-11-installdify/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2026-01-11-installdify/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[学习] 从训练到推理</title><link>https://blog.loli.wang/blog/2026-01-06-agent004/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2026-01-06-agent004/doc/</guid><description>[学习] 从训练到推理</description><pubDate>Tue, 06 Jan 2026 15:27:29 GMT</pubDate><content:encoded>&lt;h1&gt;从训练到推理&lt;/h1&gt;
&lt;h2&gt;一、预训练阶段，模型到底在做什么？&lt;/h2&gt;
&lt;p&gt;在整个大模型生命周期中，&lt;strong&gt;预训练（Pre-training）是最基础、最核心、也是最容易被误解的阶段&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在这个阶段，大语言模型并不是在学习事实、规则、世界观或推理方法，而是在完成一个&lt;strong&gt;极其单一、却被重复了数万亿次的任务&lt;/strong&gt;，即：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;给定一段上下文，预测下一个 Token 的概率分布。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 预训练任务的唯一目标&lt;/h3&gt;
&lt;p&gt;所有输入文本——无论是小说、法律条文、论文、代码、网页、聊天记录——在进入模型之前，都会被统一处理为 &lt;strong&gt;Token 序列&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Token₁, Token₂, Token₃, … → 预测 Token₄
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模型不会被告知：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这是不是事实&lt;/li&gt;
&lt;li&gt;这是不是规则&lt;/li&gt;
&lt;li&gt;这是不是一个“正确答案”&lt;/li&gt;
&lt;li&gt;这是不是在“推理”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它只是在反复学习一件事：在这种上下文形态下，最可能接下来写什么。&lt;/p&gt;
&lt;h3&gt;2. 什么叫「预训练」？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;预训练的定义可以精确表述为：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在不区分任务、不区分领域、不引入人类偏好约束的情况下，对大规模通用语料进行无监督或弱监督的下一 Token 预测训练。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;几个关键词非常重要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不区分任务&lt;/strong&gt;：问答、对话、代码、说明书在模型眼里没有本质区别。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不区分领域&lt;/strong&gt;：数学、法律、文学只是不同的统计分布。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;弱监督&lt;/strong&gt;：监督信号只来自“真实下一个 Token 是什么”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 模型在预训练阶段“真正学到”的是什么？&lt;/h3&gt;
&lt;p&gt;当这种预测在：海量文本数据、巨大参数规模、深层 Transformer 结构、多轮梯度下降中被不断重复时，模型&lt;strong&gt;并没有显式学会规则&lt;/strong&gt;，但却隐式形成了大量结构性能力。&lt;/p&gt;
&lt;p&gt;它逐渐学会了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;语言的统计结构&lt;/strong&gt;：包括词序、语法、常见搭配。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文本模式的展开形态&lt;/strong&gt;：例如“提问 → 分析 → 回答”或“定义 → 解释 → 示例”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;问题到解法的高频路径&lt;/strong&gt;：如数学题的解题模板、代码的常见写法、论文与报告的组织结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些能力并不是被“教会”的，而是&lt;strong&gt;在概率空间中自然涌现（emerge）出来的&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;4. 预训练之后&lt;/h3&gt;
&lt;p&gt;为了让预训练后的模型变成更加好用，通常还会有以下两个阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SFT (Supervised Fine-Tuning) 有监督微调&lt;/strong&gt;：给模型看几万组高质量的 &lt;code&gt;[指令] -&amp;gt; [回答]&lt;/code&gt; 范本。教它学会：当人类提问时，你应该提供答案，而不是继续接龙。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RLHF (Reinforcement Learning from Human Feedback) 人类反馈强化学习&lt;/strong&gt;：让人类给模型的多个回答打分，告诉它哪些回答更安全、更准确、更有礼貌。这是给模型注入“价值观”的过程。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;二、训练过程是怎样的&lt;/h2&gt;
&lt;p&gt;在上面讲了模型在预训练阶段不断“预测下一个 Token”，并通过这种反复的训练逐渐形成统计规律和模式能力。那么，这个“不断预测、不断优化”的过程，背后到底是怎么发生的呢？这里就涉及到 &lt;strong&gt;Loss、梯度和参数更新&lt;/strong&gt; 等概念。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;1. Loss ——模型“犯错的程度”&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;每次模型预测下一个 Token，它都可能猜对，也可能猜错。Loss 就是用来量化这个猜测有多糟糕的指标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Loss 高 → 预测离真实 Token 很远，模型“犯大错”&lt;/li&gt;
&lt;li&gt;Loss 低 → 预测接近真实 Token，模型“比较聪明”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;直觉上，Loss 就像模型的&lt;strong&gt;自我反馈表&lt;/strong&gt;：它告诉模型“这一步做得好不好”。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;2. 梯度 ——模型“改进的方向”&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Loss 告诉模型哪里做错了，但不会直接告诉它该怎么改。这时，梯度就起作用了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;梯度告诉模型：&lt;strong&gt;如果把参数往这个方向微调，Loss 会下降得更快&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;梯度的大小表示“改进有多急迫”。&lt;/li&gt;
&lt;li&gt;梯度的方向表示“改哪条路能最快变聪明”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;梯度就像模型在海量信息中寻找“下一步微调的指南针”。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;3. 参数更新 ——模型“试错修正”的动作&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;有了梯度信息，模型就可以&lt;strong&gt;调整自己的参数&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小幅度更新 → 细微改善&lt;/li&gt;
&lt;li&gt;大幅度更新 → 风险大，但可能学得快&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，参数更新就像模型在“沿着 Loss 形成的山谷不断下坡”：Loss 是高度，梯度是坡度，参数更新就是每一步迈下去，让模型慢慢靠近“预测最准确”的谷底。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;4. 训练的整体过程&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;把三者串起来，你可以把预训练阶段想象成这样一个循环：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;预测&lt;/strong&gt; → 模型根据当前参数猜下一个 Token。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算 Loss&lt;/strong&gt; → 模型知道自己猜得好不好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算梯度&lt;/strong&gt; → 模型知道沿哪个方向调整参数能改进。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数更新&lt;/strong&gt; → 模型沿梯度方向微调自己。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重复无数次&lt;/strong&gt; → 模型逐渐掌握语言模式、问题到解法的路径。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个循环发生在&lt;strong&gt;海量文本 + 巨大参数 + 深层 Transformer&lt;/strong&gt; 中，被重复数十亿次，最终模型形成了 emergent 能力。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;实战:训练过程模拟&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import torch
import torch.nn as nn
import torch.optim as optim

# 1. 数据准备
data = torch.arange(0, 10).float().unsqueeze(1)  # shape (10,1)
# 假设目标就是 data + 1
target = data + 1 

# 2. 模型定义
model = nn.Linear(1, 1)

# 3. Loss 函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01) 

# 4. 训练循环
epochs = 200 
for epoch in range(epochs):
    optimizer.zero_grad() 

    output = model(data)
    loss = criterion(output, target)
    loss.backward()
    optimizer.step()

    if (epoch+1) % 40 == 0:
        print(f&quot;Epoch {epoch+1:03d}: Loss = {loss.item():.4f}&quot;)

# 5. 测试预测
test_data = torch.tensor([[5.0]]) 
pred = model(test_data).item()
print(&quot;-&quot; * 30)
print(f&quot;预测结果: 输入 5.0 → 预测 {pred:.2f} (目标值: 6.0)&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;输出结果：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Epoch 040: Loss = 0.1391
Epoch 080: Loss = 0.0884
Epoch 120: Loss = 0.0562
Epoch 160: Loss = 0.0357
Epoch 200: Loss = 0.0227
------------------------------
预测结果: 输入 5.0 → 预测 5.94 (目标值: 6.0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;过程说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;loss = criterion(output, target)&lt;/code&gt; 告诉模型预测有多差，也即模型犯错的程度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loss.backward()&lt;/code&gt; 计算 Loss 对模型参数的偏导数，告诉模型“沿哪个方向调整参数才能让 Loss 降低”。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;optimizer.step()&lt;/code&gt; 根据梯度调整权重，就像模型沿 Loss 山谷下坡，离最佳预测更近。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;for epoch in range(epochs)&lt;/code&gt; 模拟模型在海量文本上不断重复预测、计算 Loss、更新参数，最终学会了规律。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;三、训练阶段 vs 推理阶段&lt;/h2&gt;
&lt;p&gt;在训练阶段，模型通过&lt;strong&gt;不断试错、自我修正&lt;/strong&gt;来学习规律。每次预测都会计算 Loss（模型“犯错的程度”），梯度指明调整方向，参数更新沿梯度方向微调权重。这个循环反复进行，模型能力逐渐形成。&lt;/p&gt;
&lt;p&gt;而推理阶段，模型已经完成训练，参数固定，不再更新。输入流向输出，模型使用已经学到的能力来完成任务或生成文本，不再进行自我修正。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对比如下：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;对比维度&lt;/th&gt;
&lt;th&gt;训练阶段（Training）&lt;/th&gt;
&lt;th&gt;推理阶段（Inference）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;定义&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;模型通过 Loss → 梯度 → 参数更新循环学习规律和能力&lt;/td&gt;
&lt;td&gt;模型使用训练中学到的能力进行任务或生成文本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;参数状态&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;可更新，梯度指导参数调整&lt;/td&gt;
&lt;td&gt;固定，不再更新，使用已有参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;数据流向&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;双向循环：预测 → Loss → 梯度 → 参数更新 → 再预测&lt;/td&gt;
&lt;td&gt;单向流：输入 → 模型 → 输出，Loss 不回传&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;速度与资源&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;慢，资源消耗大，需要显存存储梯度、优化器状态&lt;/td&gt;
&lt;td&gt;快，资源消耗低，只进行前向计算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;核心目标&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提升模型能力，学会语言规律、问题解决模式&lt;/td&gt;
&lt;td&gt;使用模型能力，完成具体任务或生成文本&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;我们平时使用的 ChatGPT、Gemini、Qwen、DeepSeek 等模型都是处于推理阶段。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/flingjie/Agent-100-Days/blob/main/week1/04.%E4%BB%8E%E8%AE%AD%E7%BB%83%E5%88%B0%E6%8E%A8%E7%90%86.ipynb&quot;&gt;原文链接 Agent-100-Days 从训练到推理&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>[学习] Token、Embedding 与向量空间</title><link>https://blog.loli.wang/blog/2026-01-06-agent002/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2026-01-06-agent002/doc/</guid><description>[学习] Token、Embedding 与向量空间</description><pubDate>Tue, 06 Jan 2026 15:27:25 GMT</pubDate><content:encoded>&lt;h1&gt;Token、Embedding 与向量空间&lt;/h1&gt;
&lt;p&gt;在上一节中讲到，LLM 的本质是&lt;strong&gt;一个在给定上下文条件下，预测下一个 token 概率分布的函数&lt;/strong&gt;。那么 Token 到底是什么，为什么 LLM 要以 token 为单位，以及由此而来的 Embedding 和向量空间的概念。&lt;/p&gt;
&lt;h2&gt;一、什么是 Token，为什么 LLM 的基本单位是 Token&lt;/h2&gt;
&lt;p&gt;要真正理解 Token，必须回到计算机处理文字的底层逻辑。不能只停留在当下的 AI 浪潮中，而需要&lt;strong&gt;回到一个更根本的问题&lt;/strong&gt;：&lt;strong&gt;计算机究竟是如何“看到”文字的？&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;1. 追根溯源：从二进制到字符&lt;/h3&gt;
&lt;p&gt;计算机的世界里，&lt;strong&gt;唯一真实存在的只有二进制&lt;/strong&gt;。无论是文字、图片还是代码，最终都必须被表示为 0 和 1。因此，计算机并不能直接理解 &lt;code&gt;A&lt;/code&gt;、&lt;code&gt;你&lt;/code&gt;、&lt;code&gt;+&lt;/code&gt; 等字符，它真正能处理的只有类似 &lt;code&gt;01000001&lt;/code&gt; 这样的形式。&lt;/p&gt;
&lt;h3&gt;2. 编码（Encoding）：字符进入计算机的第一步&lt;/h3&gt;
&lt;p&gt;为了让计算机“识别”人类的文字，出现了 &lt;strong&gt;编码（Encoding）&lt;/strong&gt; 的概念——&lt;strong&gt;用数字去代表字符&lt;/strong&gt;。最早被广泛使用的是 &lt;strong&gt;ASCII 编码&lt;/strong&gt;：每个字符用 8 位二进制数表示，覆盖英文字符、数字和少量符号。例如：&lt;code&gt;A&lt;/code&gt; → 十进制 &lt;code&gt;65&lt;/code&gt;，&lt;code&gt;B&lt;/code&gt; → 十进制 &lt;code&gt;66&lt;/code&gt;。ASCII 解决了英文世界的问题，但它无法表示中文、日文等非拉丁字符。&lt;/p&gt;
&lt;h3&gt;3. Unicode：让“所有字符”都能被编码&lt;/h3&gt;
&lt;p&gt;为了解决多语言问题，&lt;strong&gt;Unicode&lt;/strong&gt; 体系诞生了（常见实现如 UTF-8）。它的目标很明确：&lt;strong&gt;为世界上每一个字符分配一个唯一编号&lt;/strong&gt;。在这一阶段，计算机已经可以显示中文、存储多语言文本并正确传输字符序列。&lt;/p&gt;
&lt;h3&gt;4. Token 的出现：从“编码”走向“统计压缩”&lt;/h3&gt;
&lt;p&gt;Token 的意义，正是在这里发生了质变。与 ASCII / Unicode 不同，&lt;strong&gt;Token 不再只是编码方案&lt;/strong&gt;，而是一种基于语料统计的 &lt;strong&gt;压缩与建模方式&lt;/strong&gt;：&lt;strong&gt;把经常一起出现的字符序列，打包成一个更高层级的单位&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;换句话说：编码关心的是“这个字符用哪个数字表示？”Token 关心的是：“哪些字符经常一起出现，值得被当成一个整体？”&lt;/p&gt;
&lt;h3&gt;5. 一个直观对比：同一句话在不同层级下的表示&lt;/h3&gt;
&lt;p&gt;比如“人工智能”这几个字在编码和 Token 的不同表示：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层级&lt;/th&gt;
&lt;th&gt;表示方式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASCII / Unicode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;人&lt;/code&gt; &lt;code&gt;工&lt;/code&gt; &lt;code&gt;智&lt;/code&gt; &lt;code&gt;能&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token&lt;/td&gt;
&lt;td&gt;&lt;code&gt;人工&lt;/code&gt; &lt;code&gt;智能&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以看到，层级越低 → 越接近机器，序列越长；层级越高 → 越接近人类语义，结构越清晰。&lt;/p&gt;
&lt;h3&gt;6. 为什么 LLM 的基本单位是 Token&lt;/h3&gt;
&lt;p&gt;当前主流 LLM 的核心架构是 Transformer，而 Transformer 在底层本质上只支持 &lt;strong&gt;加法、乘法和矩阵运算&lt;/strong&gt;。这意味着：&lt;strong&gt;任何进入模型的东西，必须先被表示为数字&lt;/strong&gt;，模型无法直接理解或计算人类语言中的字符串、字符或词语。&lt;/p&gt;
&lt;p&gt;因此，文本在进入模型之前，必须先被切分为 &lt;strong&gt;Token&lt;/strong&gt;，再映射为对应的 &lt;strong&gt;Token ID&lt;/strong&gt;，并进一步转换为向量表示（Embedding），才能参与注意力计算、矩阵运算以及梯度反向传播。在这一计算范式下，Token 不仅是一种文本表示方式，而是 Transformer &lt;strong&gt;唯一能够感知和操作的语言单位&lt;/strong&gt;。模型所展现出的理解、推理与生成能力，全部涌现自 Token 级别的数值计算之上。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、Embedding 是什么，它解决了什么问题&lt;/h2&gt;
&lt;h3&gt;1. Embedding 是什么&lt;/h3&gt;
&lt;p&gt;在 LLM 中，Token 本身只是一个 &lt;strong&gt;离散 ID（整数）&lt;/strong&gt;，例如 &lt;code&gt;12345&lt;/code&gt;。这个数字对模型没有任何语义含义，ID 之间的大小、距离也毫无意义。Embedding 的作用，就是把这些离散、无序的 Token ID 映射到一个 &lt;strong&gt;连续的高维向量空间&lt;/strong&gt; 中，使模型能够用数学方式刻画它们之间的&lt;strong&gt;相似性、方向性和组合关系&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在这个向量空间里，语义相近的 Token 会拥有相近的向量表示，不同语义则体现为不同的方向或距离，从而让注意力机制和矩阵运算“看见”语言结构与语义关系。简单来说，它是将离散的数字 ID 转化为一个&lt;strong&gt;高维连续向量&lt;/strong&gt;（由数百或数千个实数组成的数组）的过程。&lt;/p&gt;
&lt;p&gt;以“人工智能”为例，过程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;原始文本&lt;/strong&gt;：“人工智能”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token 化&lt;/strong&gt;：切分为 &lt;code&gt;[人工, 智能]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token ID&lt;/strong&gt;：转换为离散数字 &lt;code&gt;[3421, 5678]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedding 层&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;3421&lt;/code&gt; → &lt;code&gt;[0.12, -0.45, 0.88, ...]&lt;/code&gt; (1536 个数字)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;5678&lt;/code&gt; → &lt;code&gt;[0.34, 0.11, -0.92, ...]&lt;/code&gt; (1536 个数字)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2. 它解决了什么核心问题？&lt;/h3&gt;
&lt;p&gt;在 Embedding 出现之前，计算机通过 &lt;strong&gt;One-hot Encoding（独热编码）&lt;/strong&gt; 来处理文字。这种传统方式存在两个致命缺陷，而 Embedding 完美解决了它们：&lt;/p&gt;
&lt;h4&gt;A. 解决“语义孤岛”问题（语义关联性）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;旧问题&lt;/strong&gt;：在独热编码中，任何两个词的向量乘积都为 0。计算机认为“猫”和“狗”的距离，与“猫”和“手机”的距离是一样远的。它无法理解词语之间的含义联系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedding 方案&lt;/strong&gt;：它将词语映射到一个&lt;strong&gt;语义空间&lt;/strong&gt;。在这个空间里，意思相近的词（如“猫”和“小猫”）在几何距离上会靠得很近，而无关的词则很远。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;B. 解决“维度灾难”问题（存储效率）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;旧问题&lt;/strong&gt;：如果词表有 10 万个词，每个词都需要一个 10 万维的向量，且里面全是 0，极度浪费空间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedding 方案&lt;/strong&gt;：它通过“降维打击”，用一个固定长度（如 GPT-3 的 12288 维）的&lt;strong&gt;稠密向量&lt;/strong&gt;来表达。这个向量不仅省空间，还能承载极其丰富的语义细节。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Embedding 的特性：语义运算&lt;/h3&gt;
&lt;p&gt;Embedding 最神奇的地方在于，它让语言具备了&lt;strong&gt;数学运算&lt;/strong&gt;的可能性。在一个训练良好的 Embedding 空间中，你可以发现类似“类比”的逻辑关系。这说明模型已经“理解”了：&lt;strong&gt;女王之于女性，等同于国王之于男性。&lt;/strong&gt; 这种性别、时态、甚至是逻辑上的关系，都被编码进了这些数字里。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(注：此处 Notebook 中包含一段 3Blue1Brown 的视频展示代码)&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;实战: 生成 Embedding&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from sentence_transformers import SentenceTransformer
import pandas as pd

# 选择模型
model = SentenceTransformer(&quot;all-MiniLM-L6-v2&quot;)

# 生成embedding
model.encode(&apos;dog&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;输出结果：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;array([-5.31470478e-02,  1.41944205e-02,  7.14570703e-03,  6.86086714e-02,
       -7.84803182e-02,  1.01674581e-02,  1.02283150e-01, -1.20648630e-02,
        ...
        1.11444503e-01,  2.98568588e-02,  2.39054970e-02,  1.10093102e-01],
      dtype=float32)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;三、相似性在向量空间中如何体现&lt;/h2&gt;
&lt;p&gt;LLM 里的“相似”是指&lt;strong&gt;向量方向相近&lt;/strong&gt;。常用指标是 &lt;strong&gt;Cosine Similarity（余弦相似度）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cos(θ) → 1&lt;/code&gt;：非常相似&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cos(θ) → 0&lt;/code&gt;：无关&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cos(θ) → -1&lt;/code&gt;：相反&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;(注：此处 Notebook 中包含一段关于词向量相似度的视频展示代码)&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;实战: 计算相似度&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np

def cosine_similarity(a, b):
    a = np.array(a)
    b = np.array(b)
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
    
pairs = [
    (&quot;北京&quot;, &quot;上海&quot;),
    (&quot;北京&quot;, &quot;东京&quot;),
    (&quot;北京&quot;, &quot;苹果&quot;),
    (&quot;Python&quot;, &quot;Java&quot;),
    (&quot;Python&quot;, &quot;香蕉&quot;)
]

for a, b in pairs:
    sim = cosine_similarity(model.encode(a), model.encode(b))
    print(f&quot;{a} vs {b}: {sim:.3f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;输出结果：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;北京 vs 上海: 0.378
北京 vs 东京: 0.836
北京 vs 苹果: 0.173
Python vs Java: 0.450
Python vs 香蕉: 0.072
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;四、类比在向量空间中如何体现&lt;/h2&gt;
&lt;p&gt;Embedding 不仅能表示“相似”，还能隐式表达&lt;strong&gt;关系本身&lt;/strong&gt;。在高维向量空间中，&lt;strong&gt;两个词向量的差值，并不是随机噪声，而往往对应一种稳定的语义方向&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;经典例子是：&lt;code&gt;国王 - 男人 + 女人 ≈ 女王&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这并不是模型“会算术”，而是因为 &lt;code&gt;国王 - 男人&lt;/code&gt; 抽取出了“&lt;strong&gt;王权但不含性别&lt;/strong&gt;”的语义方向，再加上 &lt;code&gt;女人&lt;/code&gt;，就把性别维度切换为女性，得到的向量自然靠近 &lt;code&gt;女王&lt;/code&gt;。因此可以说明，关系本身可以被表示为向量方向，某些语义关系在不同词之间是&lt;strong&gt;可迁移、可复用的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(注：此处 Notebook 中包含一段关于词向量类比的视频展示代码)&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;实战: 类比&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np

# 假设这是从预训练模型中提取的 4 维 Embedding 向量（实际模型通常是 768 或 1536 维）
# 每一维可能隐含代表：[权力, 性别(男为正,女为负), 生物性, ... ]
embeddings = {
    &quot;king&quot;:   np.array([0.9,  0.8,  1.0, 0.1]),
    &quot;man&quot;:    np.array([0.1,  0.9,  1.0, 0.0]),
    &quot;woman&quot;:  np.array([0.1, -0.9,  1.0, 0.0]),
    &quot;queen&quot;:  np.array([0.9, -0.8,  1.0, 0.1]),
    &quot;apple&quot;:  np.array([0.0,  0.0,  0.0, 0.9])  # 干扰项
}

def find_closest(target_vec, word_dict):
    best_word = None
    max_sim = -1
    for word, vec in word_dict.items():
        sim = np.dot(target_vec, vec) / (np.linalg.norm(target_vec) * np.linalg.norm(vec))
        if sim &amp;gt; max_sim:
            max_sim = sim
            best_word = word
    return best_word, max_sim

# 1. 执行向量运算：国王 - 男人 + 女人
result_vec = embeddings[&quot;king&quot;] - embeddings[&quot;man&quot;] + embeddings[&quot;woman&quot;]

# 2. 在空间中寻找离结果最近的词
closest_word, similarity = find_closest(result_vec, embeddings)

print(f&quot;运算结果向量指向的词是: {closest_word}&quot;)
print(f&quot;相似度得分: {similarity:.4f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;输出结果：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;运算结果向量指向的词是: queen
相似度得分: 0.9947
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;五、联想是如何发生的&lt;/h2&gt;
&lt;p&gt;当你向 LLM 输入一个词或一句话时，模型并不会像人类一样“联想到相关事物”，而是输入会把模型的注意力和概率分布推向某个高密度的语义区域。&lt;/p&gt;
&lt;p&gt;例如，当你输入 &lt;code&gt;医院&lt;/code&gt;，在向量空间中，与“医院”语义最接近、共现频率最高的 Token 会被优先激活，因此模型更容易继续生成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;医生&lt;/li&gt;
&lt;li&gt;病人&lt;/li&gt;
&lt;li&gt;手术&lt;/li&gt;
&lt;li&gt;治疗&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这看起来像是“联想”，但本质上并不是主动思考，而是在一个已经被大量语料反复强化的高密度区域中继续采样下一个 Token。换句话说，模型并不是在“想到医生”，而是在统计意义上，&lt;strong&gt;“医生”是此刻概率最高的延续结果&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;实战: 联想&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np

# 1. 定义词表与简化的 Embedding 向量
# 每一维可能代表 [生命健康, 科技, 生活琐事]
vocab = {
    &quot;医院&quot;: np.array([0.9, 0.1, 0.2]),
    &quot;医生&quot;: np.array([0.85, 0.2, 0.3]),
    &quot;病人&quot;: np.array([0.8, 0.0, 0.4]),
    &quot;手术&quot;: np.array([0.95, 0.3, 0.1]),
    &quot;代码&quot;: np.array([0.1, 0.9, 0.1]),
    &quot;咖啡&quot;: np.array([0.2, 0.1, 0.8])
}

def simulate_association(input_word):
    if input_word not in vocab:
        return &quot;词不在词表中&quot;
    
    input_vec = vocab[input_word]
    probabilities = {}

    # 2. 计算输入词与词表中所有词的相似度（激活强度）
    for word, vec in vocab.items():
        if word == input_word: continue
        similarity = np.dot(input_vec, vec) / (np.linalg.norm(input_vec) * np.linalg.norm(vec))
        # 将相似度转化为激活概率（简化版的 Softmax）
        probabilities[word] = np.exp(similarity * 10) 

    # 3. 归一化概率
    total_prob = sum(probabilities.values())
    for word in probabilities:
        probabilities[word] /= total_prob

    sorted_probs = sorted(probabilities.items(), key=lambda x: x[1], reverse=True)
    return sorted_probs

# 模拟输入“医院”
associations = simulate_association(&quot;医院&quot;)

print(&quot;--- 输入词：医院 ---&quot;)
print(&quot;模型后续 Token 的激活概率分布：&quot;)
for word, prob in associations:
    print(f&quot;Token: [{word}] -&amp;gt; 出现概率: {prob:.4f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;输出结果：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--- 输入词：医院 ---
模型后续 Token 的激活概率分布：
Token: [医生] -&amp;gt; 出现概率: 0.3717
Token: [手术] -&amp;gt; 出现概率: 0.3290
Token: [病人] -&amp;gt; 出现概率: 0.2972
Token: [咖啡] -&amp;gt; 出现概率: 0.0018
Token: [代码] -&amp;gt; 出现概率: 0.0002
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/flingjie/Agent-100-Days/blob/main/week1/02.Token%E3%80%81Embedding%E4%B8%8E%E5%90%91%E9%87%8F%E7%A9%BA%E9%97%B4.ipynb&quot;&gt;原文链接 Agent-100-Days Token、Embedding 与向量空间&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>[学习] 大语言模型到底在干什么</title><link>https://blog.loli.wang/blog/2026-01-06-agent001/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2026-01-06-agent001/doc/</guid><description>[学习] 大语言模型到底在干什么</description><pubDate>Tue, 06 Jan 2026 15:27:24 GMT</pubDate><content:encoded>&lt;h1&gt;大语言模型到底在干什么&lt;/h1&gt;
&lt;p&gt;在开发 Agent 之前，我们需要先理解一个最核心的组件：大语言模型（Large Language Model, LLM）。
早期的模型只是“复读机”，经历了从“统计概率”到“深度学习”，再到“通用人工智能”原型的三次跨越后，现在的 LLM 具备了世界模型（World Model）的雏形。&lt;/p&gt;
&lt;p&gt;主要发展过程如下：&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;1. 萌芽期：统计与神经网络（2013 - 2017）&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关键技术&lt;/strong&gt;：Word2Vec, RNN, LSTM。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特征&lt;/strong&gt;：这一时期的模型主要用于翻译和情感分析。虽然能处理序列，但“记性”不好，难以理解长文本，更无法进行复杂的逻辑推理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;局限&lt;/strong&gt;：模型规模小，且必须针对特定任务（如下棋、翻译）进行专项训练。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;2. 转折点：Transformer 的诞生（2017 - 2019）&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;里程碑&lt;/strong&gt;：Google 发布论文 &lt;em&gt;Attention is All You Need&lt;/em&gt;，提出了 &lt;strong&gt;Transformer&lt;/strong&gt; 架构。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特征&lt;/strong&gt;：引入了“自注意力机制（Self-Attention）”，让模型能够并行处理数据并捕捉长距离的语义联系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代表作&lt;/strong&gt;：BERT（理解力强）和 GPT-1/2（生成能力初现）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;3. 爆发期：大规模预训练与涌现能力（2020 - 2022）&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;里程碑&lt;/strong&gt;：GPT-3 的发布。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特征&lt;/strong&gt;：人们发现，当模型参数达到千亿级别时，会产生**“涌现能力（Emergent Abilities）”**——模型开始具备零样本学习（Zero-shot）和上下文学习（In-context Learning）能力，不再需要为每个小任务微调。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;社会化&lt;/strong&gt;：ChatGPT 的出现标志着 LLM 正式具备了强大的指令遵循和多轮对话能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;4. 现状：从 ChatBot 迈向 Agent（2023 至今）&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心逻辑&lt;/strong&gt;：模型不再仅仅是“聊天工具”，而是作为&lt;strong&gt;推理引擎&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特征&lt;/strong&gt;：随着推理能力（Reasoning，如 OpenAI o1, DeepSeek-R1）和多模态能力（Vision）的突破，模型可以自主规划步骤、调用工具、观察反馈并修正行为。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;趋势&lt;/strong&gt;：长文本支持（Long Context）、低延迟流式输出以及与现实世界接口的深度整合。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面将从开发者的视角出发，了解和学习LLM相关的知识&lt;/p&gt;
&lt;h2&gt;一、什么是 LLM（大语言模型）&lt;/h2&gt;
&lt;p&gt;简单来说，LLM 是一种通过在海量文本数据上进行训练，学会了“预测下一个Token”的深度学习模型。&lt;/p&gt;
&lt;h3&gt;1. 从三个维度理解 LLM&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;“大” (Large)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;这里的“大”通常指两个方面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;参数量大&lt;/strong&gt;：模型内部拥有数十亿甚至上万亿个可调节的参数（如 GPT-4, Llama 3）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据量大&lt;/strong&gt;：训练数据涵盖了互联网上的书籍、代码、论文、对话等几乎人类所有的公开知识。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;“语言” (Language)本&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;对于 LLM 来说，语言不仅是人类的谈话，还包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;程序代码&lt;/strong&gt;（逻辑的体现）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数学公式&lt;/strong&gt;（严谨性的体现）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结构化数据&lt;/strong&gt;（如 JSON、XML，是 Agent 调用工具的基石）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;“模型” (Model)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;LLM 本质上是一个极其复杂的概率模型。当你给它一个开头（Prompt）时，它在计算：&lt;strong&gt;“基于我读过的所有书，接下来的哪一个字最合理？”&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;2. LLM 的核心技术：Transformer 架构&lt;/h3&gt;
&lt;p&gt;目前几乎所有主流的 LLM（GPT, Claude, Gemini, DeepSeek）都基于 &lt;strong&gt;Transformer&lt;/strong&gt; 架构。它最核心的创新是 &lt;strong&gt;自注意力机制 (Self-Attention)&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;并行处理&lt;/strong&gt;：不同于早期的模型必须逐字阅读，Transformer 可以同时“观察”整句话。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下文联系&lt;/strong&gt;：它能理解长句子中不同词语之间的深层关联。例如在“苹果公司发布了新手机，它很受欢迎”中，模型能准确知道“它”指的是“手机”还是“苹果公司”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.大语言模型的本质&lt;/h3&gt;
&lt;p&gt;从本质看，&lt;strong&gt;LLM 是一个在给定上下文条件下，预测下一个 token 概率分布的函数。&lt;/strong&gt;
当你向 LLM 输入一段文本时，例如：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“今天北京的天气很……”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;模型并不是在“理解天气”，而是在内部执行这样一件事：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;P(下一个 token | 已有的所有 token)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;它会对词表中的所有 token 计算一个概率分布，例如：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;token&lt;/th&gt;
&lt;th&gt;概率&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;冷&lt;/td&gt;
&lt;td&gt;0.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;好&lt;/td&gt;
&lt;td&gt;0.27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;热&lt;/td&gt;
&lt;td&gt;0.18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;不错&lt;/td&gt;
&lt;td&gt;0.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🍎&lt;/td&gt;
&lt;td&gt;0.00001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;然后根据采样策略（temperature、top-p 等），&lt;strong&gt;选出一个 token&lt;/strong&gt;，拼接到已有文本后面，再继续预测下一个。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from IPython.display import HTML, display

display(HTML(&quot;&quot;&quot;
&amp;lt;figure&amp;gt;
  &amp;lt;video width=&quot;600&quot; controls&amp;gt;
    &amp;lt;source src=&quot;./raw/transformer.mp4&quot; type=&quot;video/mp4&quot;&amp;gt;
  &amp;lt;/video&amp;gt;
  &amp;lt;figcaption style=&quot;font-size: 12px; color: gray; margin-top: 4px;&quot;&amp;gt;
    （来源：3Blue1Brown）
  &amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&quot;&quot;&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实战：观察token粒度&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import tiktoken

def analyze_tokens(model_name, text_list):
    # 加载对应的编码器
    enc = tiktoken.encoding_for_model(model_name)
    
    print(f&quot;--- 使用模型: {model_name} ---&quot;)
    print(f&quot;{&apos;文本内容&apos;:&amp;lt;30} | {&apos;字符数&apos;:&amp;lt;5} | {&apos;Token数&apos;:&amp;lt;5} | {&apos;切分结果&apos;}&quot;)
    print(&quot;-&quot; * 80)
    
    for text in text_list:
        tokens = enc.encode(text)
        # 将 token id 转换回文字，方便查看切分细节
        token_strings = [enc.decode([t]) for t in tokens]
        
        print(f&quot;{text:&amp;lt;30} | {len(text):&amp;lt;8} | {len(tokens):&amp;lt;8} | {token_strings}&quot;)

# 准备测试样本
samples = [
    &quot;你好，今天天气不错。&quot;,        # 普通中文句子
    &quot;深度学习卷积神经网络&quot;,        # 专业术语
    &quot;Using LangChain 部署 Agent&quot;, # 中英混合
    &quot;龘龘靐齉爩&quot;                  # 生僻字（极端情况）
]

analyze_tokens(&quot;gpt-4o&quot;, samples)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行上述代码，你会发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;中文句子&lt;/strong&gt;：通常 1 个汉字占据 1 个 Token，但标点符号也占 1 个。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;专业术语&lt;/strong&gt;：由于“卷积”、“神经”等词承载频率极高，它们有时会被合并为一个 Token，或者切分得非常整齐。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中英混合&lt;/strong&gt;：英文单词通常按空格或子词（Subword）切分。比如 &lt;code&gt;Using&lt;/code&gt; 是 1 个 Token，但复杂的英文单词可能被拆分。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、「预测下一个 token」的真实含义&lt;/h2&gt;
&lt;h3&gt;什么是 token&lt;/h3&gt;
&lt;p&gt;首先，需要知道的是，token 不是字、词或者句子。&lt;/p&gt;
&lt;p&gt;而是模型词表中的&lt;strong&gt;最小计算单位&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如，“人工智能”的token是 人 / 工 / 智能，unbelievable的token是unbelievable | un / believe / able。&lt;/p&gt;
&lt;p&gt;模型从不“看到句子”，它只看到 &lt;strong&gt;token 序列&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;预测的不是“内容”，而是概率&lt;/h3&gt;
&lt;p&gt;模型真正输出的是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  token_A: 0.42,
  token_B: 0.31,
  token_C: 0.09,
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终的“回答”，只是从这个分布中&lt;strong&gt;采样&lt;/strong&gt;出来的结果。&lt;/p&gt;
&lt;h2&gt;实战: 模拟一个“假模型”&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;context = &quot;北京是中国的&quot;

fake_next_token_probs = {
    &quot;首都&quot;: 0.55,
    &quot;城市&quot;: 0.15,
    &quot;中心&quot;: 0.10,
    &quot;经济&quot;: 0.05,
    &quot;苹果&quot;: 0.01
}

fake_next_token_probs
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## 简单采样实验

import random

tokens = list(fake_next_token_probs.keys())
probs = list(fake_next_token_probs.values())

def sample_once():
    return random.choices(tokens, probs)[0]

[sample_once() for _ in range(10)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就是为什么同一个问题每次回答略有不同&lt;/p&gt;
&lt;h2&gt;三、为什么一个“预测器”能产生看起来像智能的行为&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;关键原因语言本身就高度压缩了人类的知识、逻辑和行为模式&lt;/strong&gt;,LLM学到&lt;strong&gt;人类在什么情况下会说什么样的话&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;LLM学到了在语料中反复出现的&lt;strong&gt;问题 → 解决步骤 → 结论&lt;/strong&gt;, 在代码中大量存在的&lt;strong&gt;代码 → 注释 → 修复&lt;/strong&gt;等等。&lt;/p&gt;
&lt;p&gt;但LLM没有学的事实、规则等人类拥有的这些东西&lt;/p&gt;
&lt;h3&gt;实战: 破坏“智能感”&lt;/h3&gt;
&lt;p&gt;提出一个“人类觉得毫无难度”的问题，比如“现在是什么时间？”, 在**没有任何外部工具（如系统时间、浏览器、函数调用）**的情况下，LLM 往往会：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;给出一个&lt;strong&gt;模糊或泛化的回答&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“我无法获取实时时间，但你可以查看设备上的时间。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;或给出一个&lt;strong&gt;看起来合理但本质回避的问题转移&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“时间取决于你所在的时区，目前可以通过系统时钟确认。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在部分场景中，甚至会&lt;strong&gt;直接“猜一个时间”&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/flingjie/Agent-100-Days/blob/main/week1/01.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E5%88%B0%E5%BA%95%E5%9C%A8%E5%B9%B2%E4%BB%80%E4%B9%88.ipynb&quot;&gt;原文链接 Agent-100-Days 大语言模型到底在干什么&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>[转] MCP 到底是个什么鬼？</title><link>https://blog.loli.wang/blog/2025-12-26-mcp/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2025-12-26-mcp/doc/</guid><description>MCP 到底是个什么鬼？</description><pubDate>Fri, 26 Dec 2025 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;盘点今年最火的 AI 概念，MCP 必须算是一个，这家伙来势汹汹，各路营销号更是加班加点推波助澜，可是，静下心来想一想，我们真的知道 MCP 是个什么东西么？很多人可能并不是很清楚，只有一个模糊的印象：这玩意好像是让 LLM/Agent 拥有调用外部工具能力的一种...东西吧...&lt;/p&gt;
&lt;p&gt;不信你去问问周围的人，1000 个人的心中可能至少有 800 个 MCP 的版本... 而且，多数可能都是似是而非的、模糊的，甚至是错误的。&lt;/p&gt;
&lt;p&gt;之所以这么混乱，是因为乌七八糟的营销号看多了，哦不，是因为没理顺 MCP 和这几个东西之间的关系：LLM 、Agent （含各种 agent 框架）、Function calling&lt;/p&gt;
&lt;p&gt;如果你非常清晰，这篇文章其实就不需要看了，否则，看完这篇文章你大概率会神清气爽：哦，就这啊！&lt;/p&gt;
&lt;p&gt;要理解 MCP,我们要问自己一些非常朴素的问题:为什么会有 MCP?它是在什么背景下出现的?没有有它会怎么样?有了它又怎么样?怎么用呢?用了之后有什么好处?&lt;/p&gt;
&lt;h2&gt;一、从 Function Calling 说起&lt;/h2&gt;
&lt;p&gt;别急,我们从 function calling 开始说起&lt;/p&gt;
&lt;p&gt;大语言模型（ LLM ）本身只能“耍嘴皮子”——生成文本，无法直接“动手”调用外部的工具解决一些实际问题（比如一些实时数据查询、数学计算等），所以 openai 在发布 gpt-4-turbo 时推出了 Function Calling 的能力，注意，它不是真的 call 了什么 function ，而是让模型可以&lt;strong&gt;生成结构化的调用指令（ JSON ）&lt;/strong&gt;，什么意思呢，大致是这样的一个过程：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;用户提问+工具列表 → LLM 返回工具调用指令（如需） → 应用根据 LLM 的指令实际调用工具 → 结果再传给 LLM → LLM 返回最终答案 → 用户看到结果&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个流程里面没有 MCP 啥事，也还没有出现什么 MCP 。&lt;/p&gt;
&lt;p&gt;你说，这不是很简单么，我的应用程序就按照这个流程写代码就行了，也没多少行代码啊。&lt;/p&gt;
&lt;p&gt;当然,简单的应用不会有什么问题,就这么玩很丝滑,但是一旦你的应用变的复杂了,工具变得多了,问题就慢慢来了,由于工具逻辑写在你的 App 代码里,所以:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;换 LLM 供应商了?可能要改代码吧？虽然多数 LLM 供应商都支持 openai 的标准,但是也有不一样的,比如 Google 的 Gemini 用 &quot;Function Declaration&quot;,Anthropic 用 &quot;Tool use&quot;&lt;/li&gt;
&lt;li&gt;工具逻辑/参数变了?你得改代码吧?因为你得维护传给 LLM 的工具参数嘛(名字+工具描述+参数)&lt;/li&gt;
&lt;li&gt;想加新工具?你还得改代码并重新部署吧?&lt;/li&gt;
&lt;li&gt;其他项目想复用你的工具,你做过的所有事情,他也得来一遍吧?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;二、MCP 的思想&lt;/h2&gt;
&lt;p&gt;总而言之,只要工具维护在你的应用程序里面,和 LLM 交互的这一整套来来回回的流程都得你自己维护。很自然的,你就会想办法把工具的这套东西独立出来,一个朴素而基本的思想是这样的:&lt;/p&gt;
&lt;p&gt;为什么不搞一个独立的服务，就类似于一个 api 的服务，我应用程序通过这个 api 服务来 list 工具，然后我只要把这些传给 LLM 就行了，执行的时候，我也 call api 来真正调用工具。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;恭喜你，其实这就是 mcp 思想的本质：某种服务 ↔ AI 应用 ↔ LLM 。你看，其实就是解耦，平淡无奇。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;注意，到这里你应该已经发现了：“某种服务”和 LLM 并没有直接的关系，说白了，MCP 和 LLM 其实没有任何的直接关系，MCP 不是直接为 LLM 服务的，LLM 也对 MCP 没有任何的感知，桥接这两者的实际上是你的应用程序（ MCP 的文档里面的 HOST 指的就是应用程序，比如 Claude 这样的 app ，或者你自己开发的 agentic APP ）&lt;/p&gt;
&lt;p&gt;接下来，你可能就真的这么做了，搞了一个 http 服务，上面有几种接口：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;listing 服务接口：负责维护所有支持的工具的 API 的描述&lt;/li&gt;
&lt;li&gt;具体的工具接口：负责调用各种外部的 API 、函数、命令行等等，再弄一个标准的返回结构&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一切都非常的丝滑,你的 APP 终于不需要耦合工具了,舒服,直到有一天...你发现隔壁老王也搞了一个类似的工具集,你一看，他的工具也不错啊,你也想接进来用,于是你的 APP 代码开始出现坏味道了,又开始写了一堆的 adapter 代码,久而久之,这和原来在 APP 内维护工具的做法有什么区别呢?没有任何区别啊,你又走回了老路...&lt;/p&gt;
&lt;p&gt;这时候,你就开始想了:谁要是能把 adapter 这个事情给统一了该多好,让工具的开发者都在同一个框架下开发并维护工具,这样接口都是统一的,不就不需要写那么多适配器代码了么?&lt;/p&gt;
&lt;h2&gt;三、MCP 的本质&lt;/h2&gt;
&lt;p&gt;恭喜你,这就是 MCP 干的事情。&lt;/p&gt;
&lt;p&gt;MCP 这个名字起的真的是一言难尽： &lt;strong&gt;Model Context Protocol&lt;/strong&gt;，乍一看，模型上下文协议，你肯定会想，跟模型有关系、还跟上下文有关系，还是个协议，结果你看了半天，它是个搞工具调用的，你就自我怀疑了：他跟模型好像没有产生直接关系啊？它跟上下文也没有产生直接关系啊？顶多算是个间接关系、远房亲戚吧？不不不，这么厉害的东西不会这么简单的，是不是我理解错了？&lt;/p&gt;
&lt;p&gt;其实你理解的一点都没错，事实就是：MCP 这名字起的确实是华而不实、牵强附会、硬蹭热点、假装高级，一股制造概念的营销骚气。要我看啊，给它改一个名字，你就秒懂了，叫它 TCP （ Tool Calling Protocol ，工具调用协议），你看，多精准。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;其实可以站在 Anthropic 角度解释下它的起名逻辑：由于工具调用及其结果会被纳入到 LLM Model 的对话上下文（ Context ）中，从而影响后续的对话，而且确实它是一种应用层的用于交换数据的协议（ Protocol ），所以，叫它 MCP ，有什么毛病嘛？！呵~呵~🙂，你开心就好~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本质上 MCP 就是定义了一个远程工具调用的“应用层协议”，是一个应用层的通讯标准/约定，所以你看他的技术栈就明白了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;应用层: FastMCP 装饰器 (@mcp.tool, @mcp.resource)
    ↓
协议层: JSON-RPC 2.0 消息格式
    ↓
传输层: stdio/sse/streamable-http (三选一)
    ↓
网络层: asyncio + aiohttp/标准 IO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你看，他其实就是在应用层做了一些封装和创新，以达到：让任何人写出来的 Tool-Service （就是 MCP server ）能够被其他人的 Tool-Client （就是 MCP client ）调用，而调用方无需写很多乱七八糟的胶水代码。就这么朴实无华。你他看它官方编程框架里面的名字是不是都无比眼熟，fastMCP ，是不是感觉到 fastAPI 那股子熟悉感？我猜官方 FastMCP 可能也是借鉴了 FastAPI 的设计理念和实现方式吧（我印象中 fastapi 的 fastMCP 是先出来的），本质就是给 API 套了一层马甲（约定了输入输出等微创新，当然 mcp 也支持本地 stdio 啦），仅此而已。&lt;/p&gt;
&lt;p&gt;结果很多营销号连基本原理都没搞清楚，就被 Anthropic 带着节奏跑，山呼海啸：震惊，LLM 长出手脚来了，可以干活了，Agent 市场要翻天覆地了！ MCP 王炸！&lt;/p&gt;
&lt;p&gt;图画的一个比一个抽象,解释的一个比一个魔幻...&lt;/p&gt;
&lt;p&gt;当然了,这一波炒作下来也不是什么坏事,确实有很多 API 被&quot;标准化&quot;了,给 LLM 应用开发者带来了不少的便利。&lt;/p&gt;
&lt;h2&gt;四、MCP 是如何工作的?&lt;/h2&gt;
&lt;p&gt;MCP 它是怎么工作的呢?大致上,分成这么几个步骤:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MCP-Server ，就是工具提供者，使用 mcp 的标准，借助 mcp 框架写一个 server ，server 这个名字我也想吐槽一下，总给人感觉它是一种需要启动起来的“远程服务”，其实它可以是一个远程的服务，也可以是一个本地的代码（当然也会被 mcp 框架偷偷的“启动起来”）&lt;/li&gt;
&lt;li&gt;APP ，也就是工具的调用者，使用 mcp 的标准，用 MCP-Client 先连接上 mcp server ，询问它有哪些具体的工具可以使用，mcp server 就会返回一个清单&lt;/li&gt;
&lt;li&gt;APP ，将工具信息按需乖乖的处理成 LLM 的 function calling 等参数，加上 Message ，开始和 LLM 进行对话，对话的流程是这样的：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;用户发出：帮我查一下北京天气，然后推荐适合的穿衣搭配
    ↓
第 1 轮：APP 将 Message + function calling 参数发送给 LLM
    ↓
第 2 轮：LLM 返回天气查询工具调用的指令
    ↓
第 3 轮：APP 使用 mcp-client 调用天气查询，返回温度数据，再把这个数据发送给 LLM
    ↓
第 4 轮：LLM 分析温度后，返回穿衣推荐工具调用的指令
    ↓
第 5 轮：APP 使用 mcp-client 调用穿衣推荐工具，再把这个数据发送给 LLM
    ↓
第 6 轮：LLM 返回最终答案
    ↓
用户收到：今天的气温是零下 10 度，建议你穿貂
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你看，MCP 它只把事情做了一半，剩下的另一半还得应用开发者自己做，什么意思呢，在这个流程里面&lt;/p&gt;
&lt;p&gt;MCP-Server ↔ AI 应用（ MCP-Client ） ↔ LLM&lt;/p&gt;
&lt;p&gt;AI 应用(MCP-Client)和 LLM 的交互的过程并不是 MCP 能覆盖到的,AI 应用和 LLM 实际上要经过多轮的来来回回交互才能获得最终的结果,这个事情还是得应用开发者自己维护,好在,一些 agent 框架也在这里做了不少工作,帮助开发者简化了这个流程,让开发者感觉到一种错觉:LLM 和 MCP-Server 之间是互联互通的。这是好事,毕竟,应用的开发者应该专注于应用逻辑的开发,而不是跟底层的这些通讯机制死磕。&lt;/p&gt;
&lt;p&gt;好啦,这就是 MCP 真实的原本的样子,希望看完这篇文章,可以让你对 MCP 祛魅,也理顺 MCP 和 LLM 、Agent （含各种 agent 框架）、Function calling 之间的关系。&lt;/p&gt;
&lt;h2&gt;五、如何使用 MCP?&lt;/h2&gt;
&lt;p&gt;理解了 MCP 的本质后,你可能会想,有没有一个现成的框架能让我快速上手,避免重复造轮子呢?有一部分的 LLM 应用的编程框架已经集成了。&lt;/p&gt;
&lt;p&gt;这里我为自己开发的 Chak 项目做个推广哈，我开发了一个极简风格的 LLM chat sdk —— Chak&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🚀 *&lt;strong&gt;GitHub: &lt;a href=&quot;https://github.com/zhixiangxue/chak-ai*&quot;&gt;https://github.com/zhixiangxue/chak-ai*&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它是一个极简的多模型 LLM 客户端，内置了上下文管理和工具调用，极简，开箱即用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用 Chak ，你无需关心 mcp 各种花里胡啥的实现和概念，你只需要 2 步就能让你的 llm 拥有调用工具的能力，一切都是透明的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from chak import Conversation
from chak.mcp import Server

# 从 MCP 服务器加载工具（可以筛选工具）
tools = await Server(url=&quot;...&quot;).tools()

# 把工具和 LLM 关联起来，剩下的，你就专注于收发消息就行了
conv = Conversation(&quot;openai/gpt-4o&quot;, tools=tools)
response = await conv.asend(&quot;What&apos;s the weather in San Francisco?&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这里我写了很多的例子，注释很详细，动手运行一遍，保证你秒懂： &lt;a href=&quot;https://github.com/zhixiangxue/chak-ai/tree/main/examples&quot;&gt;https://github.com/zhixiangxue/chak-ai/tree/main/examples&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当然，如果你有兴趣可以看下源码，看看 chak 是如何利用 mcp 调用工具&amp;amp;协调 LLM 工作的。欢迎提 issue ，欢迎交流，更欢迎贡献~&lt;/p&gt;
&lt;h2&gt;六、最后&lt;/h2&gt;
&lt;p&gt;MCP 的应用还有很多问题业界也都在探索解决，典型的比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工具路由：如何把“对的工具”在“对的时间”给到“对的 LLM”？&lt;/li&gt;
&lt;li&gt;调试和可观测：工具调用特别多的时候，如何排查问题？是工具的问题还是 LLM 的问题？&lt;/li&gt;
&lt;li&gt;生态碎片化：表面上好像有了很多的 mcp 服务，但是谁为质量负责呢？ mcp 的生态能否形成利益的正循环？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MCP 才刚刚开始，MCP 确实是为 AI 的世界定义了“USB 协议”，让工具的即插即用成为了可能。但协议之上，“电脑主板厂商”（ AI 应用/Agent 编程框架）的适配度够么？ USB 的“外设”（各种 MCP Server ）足够丰富、健壮、易用么？生态会持续的形成良性循环么？考验才刚刚开始，至少，从目前看来，有一说一，一般。&lt;/p&gt;
&lt;p&gt;以上，全文。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://v2ex.com/t/1174927&quot;&gt;原文链接&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>curl 一些常用的指令</title><link>https://blog.loli.wang/blog/2025-07-28-curldocs/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2025-07-28-curldocs/doc/</guid><description>curl 一些常用的指令</description><pubDate>Mon, 28 Jul 2025 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; 命令是一个用于从 URL 获取数据的工具，它可以从 HTTP、HTTPS、FTP、SFTP 等协议中获取数据。&lt;/p&gt;
&lt;p&gt;它支持非常多的协议：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DICT、FILE、FTP、FTPS、GOPHER、GOPHERS、HTTP、HTTPS、IMAP、IMAPS、LDAP、LDAPS、MQTT、POP3、POP3S、RTMP、RTMPS、RTSP、SCP、SFTP、SMB、SMBS、SMTP、SMTPS、TELNET、TFTP、WS 和 WSS
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;curl 命令的常见用法：&lt;/h3&gt;
&lt;h4&gt;1. 获取网页内容&lt;/h4&gt;
&lt;p&gt;这是 &lt;code&gt;curl&lt;/code&gt; 最基础的用法，它会将指定 URL 的内容直接输出到终端。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl [https://example.com](https://example.com)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 保存网页内容到文件&lt;/h4&gt;
&lt;p&gt;如果你希望将获取到的内容保存下来，而不是直接输出，可以使用 &lt;code&gt;-o&lt;/code&gt; 或 &lt;code&gt;-O&lt;/code&gt; 选项。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-o&lt;/code&gt; (小写)&lt;/strong&gt;: 将内容保存到你指定的文件名。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -o page.html [https://example.com](https://example.com)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-O&lt;/code&gt; (大写)&lt;/strong&gt;: 使用 URL 中的文件名来保存内容。例如，下面的命令会创建一个名为 &lt;code&gt;download.zip&lt;/code&gt; 的文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -O [https://example.com/files/download.zip](https://example.com/files/download.zip)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3. 查看 HTTP 响应头&lt;/h4&gt;
&lt;p&gt;在调试时，查看服务器返回的响应头信息非常有用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-i&lt;/code&gt; 或 &lt;code&gt;--include&lt;/code&gt;&lt;/strong&gt;: 在输出中包含 HTTP 响应头和响应体。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -i [https://example.com](https://example.com)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-I&lt;/code&gt; (大写) 或 &lt;code&gt;--head&lt;/code&gt;&lt;/strong&gt;: 只获取并显示 HTTP 响应头。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -I [https://example.com](https://example.com)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;4. 发送不同方法的请求&lt;/h4&gt;
&lt;p&gt;你可以使用 &lt;code&gt;-X&lt;/code&gt; 选项来指定 HTTP 请求的方法，如 &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; 等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;发送 POST 请求&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST [https://api.example.com/data](https://api.example.com/data)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;发送 DELETE 请求&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X DELETE [https://api.example.com/data/item/123](https://api.example.com/data/item/123)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;5. 发送数据&lt;/h4&gt;
&lt;p&gt;在与 API 交互时，经常需要发送数据。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;发送表单数据 (&lt;code&gt;-d&lt;/code&gt; 或 &lt;code&gt;--data&lt;/code&gt;)&lt;/strong&gt;
这会模拟一个标准的 HTML 表单提交，默认的 &lt;code&gt;Content-Type&lt;/code&gt; 是 &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -d &quot;name=John Doe&amp;amp;project=curl&quot; [https://example.com/form](https://example.com/form)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;发送 JSON 数据&lt;/strong&gt;
对于现代 API，发送 JSON 数据是常态。你需要手动设置 &lt;code&gt;Content-Type&lt;/code&gt; 头，并传递 JSON 字符串。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST -H &quot;Content-Type: application/json&quot; -d &apos;{&quot;key&quot;:&quot;value&quot;, &quot;id&quot;:123}&apos; [https://api.example.com/items](https://api.example.com/items)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;6. 添加自定义请求头 (&lt;code&gt;-H&lt;/code&gt; 或 &lt;code&gt;--header&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;在需要身份验证或传递特定元数据时，自定义请求头至关重要。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -H &quot;Authorization: Bearer your_api_token&quot; -H &quot;X-Custom-Header: MyValue&quot; [https://api.example.com/user](https://api.example.com/user)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;7. 跟随重定向 (&lt;code&gt;-L&lt;/code&gt; 或 &lt;code&gt;--location&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;默认情况下，&lt;code&gt;curl&lt;/code&gt; 不会跟随 HTTP 3xx 重定向。使用 &lt;code&gt;-L&lt;/code&gt; 选项可以使其请求重定向后的最终 URL。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 如果 [http://google.com](http://google.com) 重定向到 [https://www.google.com](https://www.google.com), -L 会获取最终页面的内容
curl -L [http://google.com](http://google.com)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;8. 上传文件&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; 同样可以轻松处理文件上传。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用 &lt;code&gt;-F&lt;/code&gt; 或 &lt;code&gt;--form&lt;/code&gt; (multipart/form-data)&lt;/strong&gt;
这会模拟一个包含文件上传的表单提交。使用 &lt;code&gt;@&lt;/code&gt; 符号指定文件路径。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -F &quot;file=@/path/to/your/image.png&quot; -F &quot;username=testuser&quot; [https://example.com/upload](https://example.com/upload)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用 &lt;code&gt;-T&lt;/code&gt; 或 &lt;code&gt;--upload-file&lt;/code&gt;&lt;/strong&gt;
对于 &lt;code&gt;PUT&lt;/code&gt; 或 &lt;code&gt;FTP&lt;/code&gt; 等协议，可以直接上传文件内容作为请求体。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -T /path/to/your/file.txt ftp://[ftp.example.com/remote/](https://ftp.example.com/remote/)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;9. 断点续传 (&lt;code&gt;-C -&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;在下载大文件时，如果网络中断，此功能非常有用。&lt;code&gt;-C -&lt;/code&gt; 告诉 &lt;code&gt;curl&lt;/code&gt; 自动寻找断点并从那里继续下载。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 如果下载中断，再次运行相同命令即可恢复下载
curl -C - -O [https://releases.ubuntu.com/22.04.4/ubuntu-22.04.4-desktop-amd64.iso](https://releases.ubuntu.com/22.04.4/ubuntu-22.04.4-desktop-amd64.iso)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;10. 处理 Cookies&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; 可以方便地存储和发送 cookies，用于维持会话。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-c, --cookie-jar &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/strong&gt;: 将服务器返回的 Cookies 保存到文件中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-b, --cookie &amp;lt;file|string&amp;gt;&lt;/code&gt;&lt;/strong&gt;: 从文件或字符串中读取 Cookies 并发送。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;!-- end list --&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. 登录并将 session cookies 保存到 cookie.txt
curl -c cookie.txt -d &quot;user=admin&amp;amp;pass=secret123&quot; [https://example.com/login](https://example.com/login)

# 2. 使用保存的 cookies 访问需要授权的页面
curl -b cookie.txt [https://example.com/dashboard](https://example.com/dashboard)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;11. 静默模式 (&lt;code&gt;-s&lt;/code&gt; 或 &lt;code&gt;--silent&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;在脚本中使用 &lt;code&gt;curl&lt;/code&gt; 时，你可能不希望看到进度条或错误信息。&lt;code&gt;-s&lt;/code&gt; 会隐藏这些输出。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 该命令将只输出响应体，没有其他任何信息
content=$(curl -s [https://api.example.com/data](https://api.example.com/data))
echo $content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; 是一个极其强大的工具，它的功能远不止于此。通过 &lt;code&gt;man curl&lt;/code&gt; 命令可以查看其完整的文档和所有可用选项，探索更多高级用法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;

以后补充实际场景的使用。&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>让浏览器发出声音 SpeechSynthesisUtterance </title><link>https://blog.loli.wang/blog/2025-07-28-speechsynthesisutterance/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2025-07-28-speechsynthesisutterance/doc/</guid><description>让浏览器发出声音 SpeechSynthesisUtterance</description><pubDate>Mon, 28 Jul 2025 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;Web Speech API&lt;/h2&gt;
&lt;p&gt;Web Speech API 是一个 JavaScript API，用于让你的语音数据集成到web应用中。&lt;/p&gt;
&lt;p&gt;语音识别通过接口使用(语音转化文字)   &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition&quot;&gt;SpeechRecognition&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;语音合成通过接口使用(文本转换语音播报)   [SpeechSynthesisUtterance]   (https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance)&lt;/p&gt;
&lt;h2&gt;目前只使了文本转语音&lt;/h2&gt;
&lt;p&gt;因为目前有个需求是需要把文字转成语音，在以前我会考虑把一些云厂商的tts封装好加入到程序内，但是最近发现这个api已经很完善了，考虑直接用了。&lt;/p&gt;
&lt;p&gt;但是在使用途中遇到一些坑，使用定时器的时候，发现发现抓取的时间是空的，最开始需要等待一段时间后才能正常被文字转换语音&lt;/p&gt;
&lt;p&gt;在网上看了解决方案，使用借鉴的案例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 引用 SpeechSynthesisUtterance 讓瀏覽器說話 - Front-End - Let&apos;s Write
// https://www.letswrite.tw/speech-synthesis-utterance/#speechsynthesisutterance

var synth = window.speechSynthesis;
function setVoices() {
  return new Promise((resolve, reject) =&amp;gt; {
  let timer;
  timer = setInterval(() =&amp;gt; {
    if(synth.getVoices().length !== 0) {
      resolve(synth.getVoices());
      clearInterval(timer);
    }
  }, 10);
 })
}
setVoices().then(voices =&amp;gt; console.log(voices));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;感谢大佬解决我的问题。&lt;/p&gt;
&lt;p&gt;测试效果视频&lt;/p&gt;
&lt;p&gt;&amp;lt;video src=&quot;http://img.blog.loli.wang/2025-07-28-speechSynthesisUtterance/01.mp4&quot; controls=&quot;controls&quot; width=&quot;100%&quot; height=&quot;100%&quot;&amp;gt;&amp;lt;/video&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Node版本管理fnm的使用体验</title><link>https://blog.loli.wang/blog/2025-03-28-nodemanagefnm/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2025-03-28-nodemanagefnm/doc/</guid><description>Node版本管理fnm的使用体验</description><pubDate>Fri, 28 Mar 2025 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;之前有一篇文章介绍过 node 版本管理工具 volta 的使用 &lt;a href=&quot;https://blog.loli.wang/blog/2023-11-27-volatsetup/doc/index.html&quot;&gt;Node 版本管理 Volta 的使用&lt;/a&gt; 这次偶然发现nodejs官方使用的示例变为了 fnm，用来体验下。&lt;/p&gt;
&lt;h2&gt;fnm 的介绍&lt;/h2&gt;
&lt;p&gt;fnm 是使用 Rust 编写的 Node 版本管理工具，它允许您安装多个版本的 Node，并轻松切换它们。&lt;/p&gt;
&lt;p&gt;优势：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- 🌎 跨平台支持（macOS、Windows、Linux）
- ✨ 单文件，轻松安装，即时启动
- 🚀 以速度为中心
- 📂 适用于.node-version和.nvmrc文件
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# mac/linux
  curl -fsSL https://fnm.vercel.app/install | bash

# Winget (Windows)
  winget install Schniz.fnm

# scoop (Windows)
  scoop install fnm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-03-28-nodeManageFnm/01.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;简单使用&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 安装 node 22版本最新版本
fnm install 22 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载速度很快，只需要12秒。就可以下载完成。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-03-28-nodeManageFnm/03.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 
&amp;gt;@FOR /f &quot;tokens=*&quot; %i IN (&apos;fnm env&apos;) DO @%i

# 切换版本
fnm use v22.14.0

# 查看版本

node -v

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-03-28-nodeManageFnm/04.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样就切换成了22.14.0版本。&lt;/p&gt;
&lt;h2&gt;高阶使用 (Monorepo 使用方案)&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;在项目的根目录（或任意子目录）添加 .node-version 或 .nvmrc 文件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;
monorepo/
├── .node-version （指定整个仓库的 node 版本）
├── package.json
├── packages/
│   ├── app1/
│   │   └── .node-version ← 子项目 app1 的 Node 版本
│   └── app2/
│       └── .node-version ← 子项目 app2 的 Node 版本

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当你写好配置文件后&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 
@FOR /f &quot;tokens=*&quot; %i IN (&apos;fnm env&apos;) DO @%i


# 切换版本
fnm use 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到 我们当前是切换成功了，我们然后切换到到子项目看看&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-03-28-nodeManageFnm/05.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;p&gt;子项目也切换成功。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-03-28-nodeManageFnm/06.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;fnm 是一个非常不错的 Node 版本管理工具，它允许您安装多个版本的 Node，并轻松切换它们。 它具有许多优点，如跨平台支持、单文件安装、快速速度和适用于 .node-version 和 .nvmrc 文件的兼容性。&lt;/p&gt;
</content:encoded></item><item><title>React 插件推荐 《React-pdf》 浏览器动态渲染PDF</title><link>https://blog.loli.wang/blog/2025-03-15-reactpdftools/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2025-03-15-reactpdftools/doc/</guid><description>React 插件推荐 《React-pdf》 浏览器动态渲染PDF</description><pubDate>Sat, 15 Mar 2025 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;为什么推荐&lt;/h2&gt;
&lt;p&gt;最近工作上总是有让我很头疼的问题，需要让我显示一个报表单，并且还可以下载成为 PDF。简单来说，就是要实现在线预览和下载功能。正好以前看到过 &lt;code&gt;react-pdf&lt;/code&gt; 这个插件，这次正好用上了。&lt;/p&gt;
&lt;p&gt;前端生成 PDF 并且能动态渲染的插件其实非常少见。像 &lt;code&gt;canvas + html2pdf&lt;/code&gt; 这种方式，生成的其实是图片格式的 PDF，无法编辑，且存在像素模糊的问题。而 &lt;code&gt;react-pdf&lt;/code&gt; 可以真正地渲染出可编辑的 PDF 文件。&lt;/p&gt;
&lt;h2&gt;插件介绍&lt;/h2&gt;
&lt;p&gt;官网 ： &lt;a href=&quot;https://react-pdf.org/&quot;&gt;react-pdf&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;react-pdf&lt;/code&gt; 允许我们使用 React 组件的方式来构建 PDF 文档。它基于 &lt;code&gt;@react-pdf/renderer&lt;/code&gt; 库，可以直接在浏览器端生成 PDF，并支持动态内容更新。&lt;/p&gt;
&lt;h2&gt;插件安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# npm
npm install @react-pdf/renderer --save

# yarn
yarn add @react-pdf/renderer

# pnpm
pnpm add @react-pdf/renderer

# bun
bun install @react-pdf/renderer
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;基本使用&lt;/h2&gt;
&lt;p&gt;首先，我们需要引入 &lt;code&gt;react-pdf&lt;/code&gt; 提供的核心组件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { Document, Page, Text, View, StyleSheet, PDFDownloadLink, PDFViewer, Font } from &quot;@react-pdf/renderer&quot;;

// @ts-ignore
import MK from &apos;@/assets/font/MK.ttf&apos;;

// Register font
Font.register({ family: &apos;pinru&apos;, src: MK });

const data = [
  { id: 1, name: &apos;张三&apos;, age: 20, score: 85 },
  { id: 2, name: &apos;李四&apos;, age: 22, score: 90 },
  { id: 2, name: &apos;李四&apos;, age: 22, score: 90 },
  { id: 2, name: &apos;李四&apos;, age: 22, score: 90 },
  { id: 2, name: &apos;李四&apos;, age: 22, score: 90 },
  { id: 2, name: &apos;李四&apos;, age: 22, score: 90 },
];

// 定义样式
const styles = StyleSheet.create({
  page: {
    fontFamily: &apos;pinru&apos;,
    fontSize: 10,
    padding: 20,
    lineHeight: 2,
  },
  header: { textAlign: &apos;center&apos;, fontSize: 18, marginBottom: 10 },
  table: { display: &apos;table&apos;, width: &apos;100%&apos;, borderStyle: &apos;solid&apos;, borderWidth: 1, borderColor: &apos;#000&apos; },
  tableRow: { flexDirection: &apos;row&apos; },
  tableColHeader: { width: &apos;25%&apos;, borderStyle: &apos;solid&apos;, borderWidth: 1, backgroundColor: &apos;#eee&apos;, padding: 5, textAlign: &apos;center&apos; },
  tableCol: { width: &apos;25%&apos;, borderStyle: &apos;solid&apos;, borderWidth: 1, padding: 5, textAlign: &apos;center&apos; },
});
const MyDocument = () =&amp;gt; (
  &amp;lt;PDFViewer width=&quot;100%&quot; height=&quot;100%&quot;&amp;gt;
    &amp;lt;Document&amp;gt;
      &amp;lt;Page size=&quot;A4&quot; style={styles.page}&amp;gt;
        &amp;lt;Text style={styles.header}&amp;gt;报表单&amp;lt;/Text&amp;gt;
        &amp;lt;View style={styles.table}&amp;gt;
          {/* 表头 */}
          &amp;lt;View style={styles.tableRow}&amp;gt;
            &amp;lt;Text style={styles.tableColHeader}&amp;gt;ID&amp;lt;/Text&amp;gt;
            &amp;lt;Text style={styles.tableColHeader}&amp;gt;姓名&amp;lt;/Text&amp;gt;
            &amp;lt;Text style={styles.tableColHeader}&amp;gt;年龄&amp;lt;/Text&amp;gt;
            &amp;lt;Text style={styles.tableColHeader}&amp;gt;成绩&amp;lt;/Text&amp;gt;
          &amp;lt;/View&amp;gt;
          {/* 数据行 */}
          {data.map((item, index) =&amp;gt; (
            &amp;lt;View style={styles.tableRow} key={index}&amp;gt;
              &amp;lt;Text style={styles.tableCol}&amp;gt;{item.id}&amp;lt;/Text&amp;gt;
              &amp;lt;Text style={styles.tableCol}&amp;gt;{item.name}&amp;lt;/Text&amp;gt;
              &amp;lt;Text style={styles.tableCol}&amp;gt;{item.age}&amp;lt;/Text&amp;gt;
              &amp;lt;Text style={styles.tableCol}&amp;gt;{item.score}&amp;lt;/Text&amp;gt;
            &amp;lt;/View&amp;gt;
          ))}
        &amp;lt;/View&amp;gt;
      &amp;lt;/Page&amp;gt;
    &amp;lt;/Document&amp;gt;
  &amp;lt;/PDFViewer&amp;gt;
);

const App = () =&amp;gt; (
  &amp;lt;div className=&quot;h-screen w-screen&quot;&amp;gt;
    &amp;lt;h2&amp;gt;React PDF 示例&amp;lt;/h2&amp;gt;
    &amp;lt;MyDocument /&amp;gt;
  &amp;lt;/div&amp;gt;
);

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;演示效果&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-03-15-reactPdfTools/01.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;代码解析&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Document&lt;/code&gt;：表示 PDF 文档的根组件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Page&lt;/code&gt;：定义 PDF 的页面，可以包含多个 &lt;code&gt;Page&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Text&lt;/code&gt;：用于添加文本内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;View&lt;/code&gt;：类似于 &lt;code&gt;div&lt;/code&gt;，用于布局。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StyleSheet.create&lt;/code&gt;：用于定义 PDF 组件的样式。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PDFDownloadLink&lt;/code&gt;：提供 PDF 下载功能。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;进阶使用&lt;/h2&gt;
&lt;h3&gt;1. 生成多页 PDF&lt;/h3&gt;
&lt;p&gt;如果需要创建多页 PDF，可以这样实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const MultiPageDocument = () =&amp;gt; (
  &amp;lt;Document&amp;gt;
    {[1, 2, 3].map((pageNum) =&amp;gt; (
      &amp;lt;Page key={pageNum} size=&quot;A4&quot; style={styles.page}&amp;gt;
        &amp;lt;View style={styles.section}&amp;gt;
          &amp;lt;Text&amp;gt;第 {pageNum} 页&amp;lt;/Text&amp;gt;
        &amp;lt;/View&amp;gt;
      &amp;lt;/Page&amp;gt;
    ))}
  &amp;lt;/Document&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 在页面中直接渲染 PDF 预览&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;react-pdf&lt;/code&gt; 还提供了 &lt;code&gt;PDFViewer&lt;/code&gt; 组件，可以直接在页面中预览 PDF：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { PDFViewer } from &quot;@react-pdf/renderer&quot;;

const PDFPreview = () =&amp;gt; (
  &amp;lt;PDFViewer width=&quot;100%&quot; height=&quot;500px&quot;&amp;gt;
    &amp;lt;MyDocument /&amp;gt;
  &amp;lt;/PDFViewer&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 处理复杂的报表&lt;/h3&gt;
&lt;p&gt;对于包含表格的 PDF，可以使用 &lt;code&gt;react-pdf-table&lt;/code&gt; 或者手动布局：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;View style={{ flexDirection: &quot;row&quot;, borderBottom: &quot;1pt solid #000&quot; }}&amp;gt;
  &amp;lt;Text style={{ flex: 1 }}&amp;gt;列 1&amp;lt;/Text&amp;gt;
  &amp;lt;Text style={{ flex: 1 }}&amp;gt;列 2&amp;lt;/Text&amp;gt;
  &amp;lt;Text style={{ flex: 1 }}&amp;gt;列 3&amp;lt;/Text&amp;gt;
&amp;lt;/View&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;react-pdf&lt;/code&gt; 是一个非常强大的 PDF 生成工具，可以帮助前端开发者以组件化的方式构建 PDF 文档。它解决了传统 &lt;code&gt;html2canvas&lt;/code&gt; 方式存在的模糊问题，并提供了丰富的自定义能力。&lt;/p&gt;
&lt;p&gt;适用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;动态生成 PDF 文档&lt;/li&gt;
&lt;li&gt;在线预览和下载&lt;/li&gt;
&lt;li&gt;生成多页 PDF&lt;/li&gt;
&lt;li&gt;复杂的表格、报表&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>最近写流程图和流程设计器的初始化demo</title><link>https://blog.loli.wang/blog/2025-03-13-zjdemo/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2025-03-13-zjdemo/doc/</guid><description>最近写流程图和流程设计器的初始化demo</description><pubDate>Thu, 13 Mar 2025 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;这个月不知道发生了什么。&lt;/h2&gt;
&lt;p&gt;这个月不知道发生了什么，多出来几个图表的功能，图表还是不是简单的展示图表，令人纠结，为了防止以后 无法回忆起当时做了什么，记录写个demo&lt;/p&gt;
&lt;h2&gt;1.流程图 Antv X6&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import React, { useEffect, useRef, useState } from &quot;react&quot;;
import { Graph, Shape } from &quot;@antv/x6&quot;;
import dagre from &quot;dagre&quot;;
import { fetch } from &apos;@/api/order&apos;;
import &quot;./workMap.scss&quot;;
import { useQuery } from &quot;@tanstack/react-query&quot;;
import { message } from &quot;antd&quot;;

const NODE_WIDTH = 160;
const NODE_HEIGHT = 80;

const colorMap = {
    &quot;SO No.&quot;: {
        textColor: &quot;#FAD27B&quot;,
        bgColor: &quot;#FAD27B&quot;,
    },
    &quot;Schedule No.&quot;: {
        textColor: &quot;#AAD09D&quot;,
        bgColor: &quot;#AAD09D&quot;,
    },
    &quot;Task No.&quot;: {
        textColor: &quot;#89CCD5&quot;,
        bgColor: &quot;#89CCD5&quot;,
    },
    &quot;Operation No.&quot;: {
        textColor: &quot;#8382BE&quot;,
        bgColor: &quot;#8382BE&quot;,
    },
    &quot;Purchase No.&quot;: {
        textColor: &quot;#FA7B7B&quot;,
        bgColor: &quot;#FA7B7B&quot;,
    },
    &quot;Billing No.&quot;: {
        textColor: &quot;#DC7DB0&quot;,
        bgColor: &quot;#DC7DB0&quot;,
    }
} as any;

//  TO : Ocean TA: Air  TT :Trucking TC: Customs TW: Warehouse TE： Express TB: Billing
const taskTitleMatch = (type: string, label: string) =&amp;gt; {
    if (type === &quot;Task No.&quot;) {
        // 运输方式映射表
        const transportMap = {
            TO: &apos;Ocean&apos;,
            TA: &apos;Air&apos;,
            TT: &apos;Trucking&apos;,
            TC: &apos;Customs&apos;,
            TW: &apos;Warehouse&apos;,
            TE: &apos;Express&apos;,
            TB: &apos;Billing&apos;
        } as any;
        // 提取label前2个字符作为运输方式代码
        const transportCode = label.slice(0, 2);
        // 返回匹配的运输方式全称，若无则返回原始label
        return `${transportMap[transportCode]} ${type}` ;
    } else {
        return type
    }
}


Shape.HTML.register({
    shape: &quot;custom-html&quot;,
    width: NODE_WIDTH,
    height: NODE_HEIGHT,
    html(node) {
        const div = document.createElement(&quot;div&quot;);
        div.className = &quot;custom-html&quot;;
        const { label, type } = node.data;
        // 根据 type 获取颜色配置，如果没有则使用默认颜色
        const { textColor, bgColor } = colorMap[type]

        div.innerHTML = `&amp;lt;div class=&quot;content&quot; style=&quot; background-color: ${bgColor}; border: 1px solid ${bgColor};&quot;&amp;gt;
        &amp;lt;div class=&quot;labelBox&quot;&amp;gt;
            &amp;lt;div&amp;gt;${taskTitleMatch(type, label)}&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
        &amp;lt;div class=&quot;textBox&quot; style=&quot;color:#000&quot;&amp;gt;
            &amp;lt;div&amp;gt;${label}&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
    &amp;lt;/div&amp;gt;`;
        return div;
    },
});

interface IProps {
    type: &apos;so&apos; | &apos;po&apos;,
    orderNo: string
}
const OrgChartGraph: React.FC&amp;lt;IProps&amp;gt; = ({ type, orderNo }) =&amp;gt; {
    const containerRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);
    const [nodes, setNodes] = useState&amp;lt;any[]&amp;gt;([]);
    const [edges, setEdges] = useState&amp;lt;any[]&amp;gt;([]);
    const [data, setData] = useState&amp;lt;any[]&amp;gt;([]);

    const { refetch, isLoading } = useQuery(
        [&apos;fetch&apos;],
        () =&amp;gt;
            fetch({
                orderNo
            }),
        {
            enabled: false,
            onSuccess(data: any) {
                if (data.code == 200) {
                    const source = data.data;
                    setData(source)
                    const { nodes, edges } = transformData(source);
                    setNodes(nodes)
                    setEdges(edges)
                } else {
                    message.error(data.message)
                }
            },
        },
    )

    function transformData(data: any[]) {
        const nodes = [] as any[];
        const edges = [] as any[];
        function traverse(node: { orderNo: any; type: any; createUser: any; child: any[]; }, parent: { orderNo: any; } | null) {
            // 将当前节点添加到 nodes 中
            nodes.push({
                id: node.orderNo,       // 使用 orderNo 作为节点 id
                label: node.orderNo,    // label 可按需求修改，比如显示 orderNo
                type: node.type,        // 用于后续 colorMap 的映射
                createUser: node.createUser,
            });

            // 如果存在父节点，则生成一条边，从父节点指向当前节点
            if (parent) {
                edges.push({
                    source: parent.orderNo,
                    target: node.orderNo,
                });
            }

            // 递归处理子节点
            if (node.child &amp;amp;&amp;amp; node.child.length &amp;gt; 0) {
                node.child.forEach(child =&amp;gt; traverse(child, node));
            }
        }

        // 处理根节点（注意接口返回的数据为数组）
        data.forEach(rootNode =&amp;gt; traverse(rootNode, null));
        return { nodes, edges };
    }


    useEffect(() =&amp;gt; {
        refetch()
    }, []);

    useEffect(() =&amp;gt; {
        const graph = new Graph({
            container: containerRef.current!,
            interacting: {
                nodeMovable: false, // 禁止节点拖动
                edgeMovable: false, // 禁止连线拖动
            },
            panning: {
                enabled: true,
            },
            mousewheel: {
                enabled: true,
                modifiers: [&apos;ctrl&apos;, &apos;meta&apos;],
            },
        });
        
        nodes.forEach((node) =&amp;gt; {
            graph.addNode({
                id: node.id,
                shape: &quot;custom-html&quot;,
                width: NODE_WIDTH,
                height: NODE_HEIGHT,
                data: node,
            });
        });

        // 创建边
        edges.forEach((edge) =&amp;gt; {
            graph.addEdge({
                source: edge.source,
                target: edge.target,
                attrs: {
                    line: {
                        strokeWidth: 2,
                        stroke: &quot;#A2B1C3&quot;,
                        sourceMarker: null,
                        targetMarker: null,
                    },
                },
            });
        });

        // 自定义布局函数
        const layoutGraph = () =&amp;gt; {
            const dir = &quot;LR&quot; as string;
            const g = new dagre.graphlib.Graph();
            g.setGraph({ rankdir: dir, nodesep: 16, ranksep: 50 });
            g.setDefaultEdgeLabel(() =&amp;gt; ({}));

            graph.getNodes().forEach((node) =&amp;gt; {
                const { width, height } = node.getSize();
                g.setNode(node.id, { width, height });
            });

            graph.getEdges().forEach((edge) =&amp;gt; {
                const sourceId = edge.getSourceCellId();
                const targetId = edge.getTargetCellId();
                g.setEdge(sourceId, targetId);
            });

            dagre.layout(g);

            g.nodes().forEach((id) =&amp;gt; {
                const node = graph.getCellById(id) as any;
                if (node) {
                    const pos = g.node(id);
                    node.position(pos.x, pos.y);
                }
            });

            graph.getEdges().forEach((edge) =&amp;gt; {
                const source = edge.getSourceNode();
                const target = edge.getTargetNode();
                if (!source || !target) return;

                const sourceBBox = source.getBBox();
                const targetBBox = target.getBBox();

                if ((dir === &quot;LR&quot; || dir === &quot;RL&quot;) &amp;amp;&amp;amp; sourceBBox.y !== targetBBox.y) {
                    const gap =
                        dir === &quot;LR&quot;
                            ? targetBBox.x - sourceBBox.x - sourceBBox.width
                            : -sourceBBox.x + targetBBox.x + targetBBox.width;
                    const fix = dir === &quot;LR&quot; ? sourceBBox.width : 0;
                    const x = sourceBBox.x + fix + gap / 2;
                    edge.setVertices([
                        { x, y: sourceBBox.center.y },
                        { x, y: targetBBox.center.y },
                    ]);
                } else if ((dir === &quot;TB&quot; || dir === &quot;BT&quot;) &amp;amp;&amp;amp; sourceBBox.x !== targetBBox.x) {
                    const gap =
                        dir === &quot;TB&quot;
                            ? targetBBox.y - sourceBBox.y - sourceBBox.height
                            : -sourceBBox.y + targetBBox.y + targetBBox.height;
                    const fix = dir === &quot;TB&quot; ? sourceBBox.height : 0;
                    const y = sourceBBox.y + fix + gap / 2;
                    edge.setVertices([
                        { x: sourceBBox.center.x, y },
                        { x: targetBBox.center.x, y },
                    ]);
                } else {
                    edge.setVertices([]);
                }
            });

            //   graph.centerContent();

        };

        layoutGraph();

    }, [data])

    return (
        &amp;lt;div&amp;gt;
            {/* 各颜色节点代表不同状态 */}
            &amp;lt;div className=&quot;flex flex-wrap gap-4 mb-4 ml-2&quot;&amp;gt;
                {Object.entries(colorMap).map(([key, { bgColor } ]:any) =&amp;gt; (
                    &amp;lt;div key={key} className=&quot;flex items-center mr-4&quot;&amp;gt;
                        &amp;lt;span className=&quot;text-sm  mr-2 text-[#999999]&quot;&amp;gt;{key}&amp;lt;/span&amp;gt; &amp;lt;div
                            className=&quot;w-6 h-3&quot;
                            style={{ backgroundColor: bgColor }}&amp;gt;&amp;lt;/div&amp;gt;
                       
                    &amp;lt;/div&amp;gt;
                ))}
            &amp;lt;/div&amp;gt;
            &amp;lt;div className=&quot;html-basic-app&quot;&amp;gt;
                &amp;lt;div
                    className=&quot;app-content&quot;
                    ref={containerRef}
                    style={{ width: &quot;100%&quot;, height: &quot;500px&quot; }}
                /&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

export default OrgChartGraph;


&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;

.html-basic-app {
  display: flex;
  padding: 0;
  font-family: sans-serif;


  .app-content {
    flex: 1;
    margin-right: 8px;
    margin-left: 8px;
  }

  .custom-html {
    width: 100%;
    height: 100%;

    .content {
      border-radius: 5px;

      .labelBox {
        color: #FFF;
        font-weight: bold;
        font-size: 12px;
        text-indent: 1em;
        line-height: 30px;
      }

      .textBox {
        background-color: white;
        width: 100%;
        border-radius: 0 0 5px 5px;
        font-weight: 500;
        text-indent: 10px;
        font-size: 12px;
        line-height: 30px;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;演示效果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-03-13-zjdemo/01.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;2. 拖拽流程图&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# core.tsx
import { Graph, Shape } from &apos;@antv/x6&apos;
import { Stencil } from &apos;@antv/x6-plugin-stencil&apos;
import { Transform } from &apos;@antv/x6-plugin-transform&apos;
import { Selection } from &apos;@antv/x6-plugin-selection&apos;
import { Snapline } from &apos;@antv/x6-plugin-snapline&apos;
import { Keyboard } from &apos;@antv/x6-plugin-keyboard&apos;
import { Clipboard } from &apos;@antv/x6-plugin-clipboard&apos;
import { History } from &apos;@antv/x6-plugin-history&apos;
import { EventEmitter } from &apos;events&apos;;


export let graph: Graph | null = null;

export const emitter = new EventEmitter;

export const initGraph = (container: HTMLElement) =&amp;gt; {
    graph = new Graph({
        container,
        grid: true,
        mousewheel: {
            enabled: true,
            zoomAtMousePosition: true,
            modifiers: &apos;ctrl&apos;,
            minScale: 0.5,
            maxScale: 3,
        },
        connecting: {
            router: &apos;manhattan&apos;,
            connector: {
                name: &apos;rounded&apos;,
                args: { radius: 8 },
            },
            anchor: &apos;center&apos;,
            connectionPoint: &apos;anchor&apos;,
            allowBlank: false,
            snap: { radius: 20 },
            createEdge() {
                return new Shape.Edge({
                    attrs: {
                        line: {
                            stroke: &apos;#A2B1C3&apos;,
                            strokeWidth: 2,
                            targetMarker: {
                                name: &apos;block&apos;,
                                width: 12,
                                height: 8,
                            },
                        },
                    },
                    zIndex: 0,
                })
            },
            validateConnection({ targetMagnet }) {
                return !!targetMagnet
            },
        },
        highlighting: {
            magnetAdsorbed: {
                name: &apos;stroke&apos;,
                args: {
                    attrs: {
                        fill: &apos;#5F95FF&apos;,
                        stroke: &apos;#5F95FF&apos;,
                    },
                },
            },
        },
    })

    // graph.on(&apos;node:click&apos;, () =&amp;gt; {
    //     alert(&quot;第一个点击触发&quot;)
    // })

    // 使用插件
    graph
        .use(
            new Transform({
                resizing: true,
                rotating: true,
            }),
        )
        .use(
            new Selection({
                rubberband: true,
                showNodeSelectionBox: true,
            }),
        )
        .use(new Snapline())
        .use(new Keyboard())
        .use(new Clipboard())
        .use(new History())

    // 初始化 stencil
    const stencil = new Stencil({
        title: &apos;流程图&apos;,
        target: graph,
        stencilGraphWidth: 200,
        stencilGraphHeight: 180,
        collapsable: true,
        groups: [
            { title: &apos;基础流程图&apos;, name: &apos;group1&apos; },
            {
                title: &apos;系统设计图&apos;,
                name: &apos;group2&apos;,
                graphHeight: 250,
                layoutOptions: { rowHeight: 70 },
            },
        ],
        layoutOptions: {
            columns: 2,
            columnWidth: 80,
            rowHeight: 55,
        },
    })
    // 将 stencil 的容器挂载到对应 DOM 节点上
    document.getElementById(&apos;stencil&apos;)!.appendChild(stencil.container)

    onNodeClick();
    // 快捷键绑定及事件
    graph.bindKey([&apos;meta+c&apos;, &apos;ctrl+c&apos;], () =&amp;gt; {
        const cells = graph!.getSelectedCells()
        if (cells.length) graph!.copy(cells)
        return false
    })
    graph.bindKey([&apos;meta+x&apos;, &apos;ctrl+x&apos;], () =&amp;gt; {
        const cells = graph!.getSelectedCells()
        if (cells.length) graph!.cut(cells)
        return false
    })
    graph.bindKey([&apos;meta+v&apos;, &apos;ctrl+v&apos;], () =&amp;gt; {
        if (!graph!.isClipboardEmpty()) {
            const cells = graph!.paste({ offset: 32 })
            graph!.cleanSelection()
            graph!.select(cells)
        }
        return false
    })
    graph.bindKey([&apos;meta+z&apos;, &apos;ctrl+z&apos;], () =&amp;gt; {
        if (graph!.canUndo()) graph!.undo()
        return false
    })
    graph.bindKey([&apos;meta+shift+z&apos;, &apos;ctrl+shift+z&apos;], () =&amp;gt; {
        if (graph!.canRedo()) graph!.redo()
        return false
    })
    graph.bindKey([&apos;meta+a&apos;, &apos;ctrl+a&apos;], () =&amp;gt; {
        const nodes = graph!.getNodes()
        if (nodes) graph!.select(nodes)
    })
    graph.bindKey(&apos;backspace&apos;, () =&amp;gt; {
        const cells = graph!.getSelectedCells()
        if (cells.length) graph!.removeCells(cells)
    })
    graph.bindKey([&apos;ctrl+1&apos;, &apos;meta+1&apos;], () =&amp;gt; {
        const zoom = graph!.zoom()
        if (zoom &amp;lt; 1.5) graph!.zoom(0.1)
    })
    graph.bindKey([&apos;ctrl+2&apos;, &apos;meta+2&apos;], () =&amp;gt; {
        const zoom = graph!.zoom()
        if (zoom &amp;gt; 0.5) graph!.zoom(-0.1)
    })

    // 鼠标移入移出时显示/隐藏连接桩
    const showPorts = (ports: NodeListOf&amp;lt;SVGElement&amp;gt;, show: boolean) =&amp;gt; {
        for (let i = 0, len = ports.length; i &amp;lt; len; i += 1) {
            ports[i].style.visibility = show ? &apos;visible&apos; : &apos;hidden&apos;
        }
    }
    graph.on(&apos;node:mouseenter&apos;, () =&amp;gt; {
        const container = document.getElementById(&apos;graph-container&apos;)!
        const ports = container.querySelectorAll(&apos;.x6-port-body&apos;) as NodeListOf&amp;lt;SVGElement&amp;gt;
        showPorts(ports, true)
    })
    graph.on(&apos;node:mouseleave&apos;, () =&amp;gt; {
        const container = document.getElementById(&apos;graph-container&apos;)!
        const ports = container.querySelectorAll(&apos;.x6-port-body&apos;) as NodeListOf&amp;lt;SVGElement&amp;gt;
        showPorts(ports, false)
    })

    // 定义连接桩及端口
    const ports = {
        groups: {
            top: {
                position: &apos;top&apos;,
                attrs: {
                    circle: {
                        r: 4,
                        magnet: true,
                        stroke: &apos;#5F95FF&apos;,
                        strokeWidth: 1,
                        fill: &apos;#fff&apos;,
                        style: { visibility: &apos;hidden&apos; },
                    },
                },
            },
            right: {
                position: &apos;right&apos;,
                attrs: {
                    circle: {
                        r: 4,
                        magnet: true,
                        stroke: &apos;#5F95FF&apos;,
                        strokeWidth: 1,
                        fill: &apos;#fff&apos;,
                        style: { visibility: &apos;hidden&apos; },
                    },
                },
            },
            bottom: {
                position: &apos;bottom&apos;,
                attrs: {
                    circle: {
                        r: 4,
                        magnet: true,
                        stroke: &apos;#5F95FF&apos;,
                        strokeWidth: 1,
                        fill: &apos;#fff&apos;,
                        style: { visibility: &apos;hidden&apos; },
                    },
                },
            },
            left: {
                position: &apos;left&apos;,
                attrs: {
                    circle: {
                        r: 4,
                        magnet: true,
                        stroke: &apos;#5F95FF&apos;,
                        strokeWidth: 1,
                        fill: &apos;#fff&apos;,
                        style: { visibility: &apos;hidden&apos; },
                    },
                },
            },
        },
        items: [
            { group: &apos;top&apos; },
            { group: &apos;right&apos; },
            { group: &apos;bottom&apos; },
            { group: &apos;left&apos; },
        ],
    }

    // 注册自定义节点
    Graph.registerNode(
        &apos;custom-rect&apos;,
        {
            inherit: &apos;rect&apos;,
            width: 66,
            height: 36,
            attrs: {
                body: {
                    strokeWidth: 1,
                    stroke: &apos;#5F95FF&apos;,
                    fill: &apos;#EFF4FF&apos;,
                },
                text: {
                    fontSize: 12,
                    fill: &apos;#262626&apos;,
                },
            },
            ports: { ...ports },
        },
        true,
    )

    Graph.registerNode(
        &apos;custom-polygon&apos;,
        {
            inherit: &apos;polygon&apos;,
            width: 66,
            height: 36,
            attrs: {
                body: {
                    strokeWidth: 1,
                    stroke: &apos;#5F95FF&apos;,
                    fill: &apos;#EFF4FF&apos;,
                },
                text: {
                    fontSize: 12,
                    fill: &apos;#262626&apos;,
                },
            },
            ports: {
                ...ports,
                items: [{ group: &apos;top&apos; }, { group: &apos;bottom&apos; }],
            },
        },
        true,
    )

    Graph.registerNode(
        &apos;custom-circle&apos;,
        {
            inherit: &apos;circle&apos;,
            width: 45,
            height: 45,
            attrs: {
                body: {
                    strokeWidth: 1,
                    stroke: &apos;#5F95FF&apos;,
                    fill: &apos;#EFF4FF&apos;,
                },
                text: {
                    fontSize: 12,
                    fill: &apos;#262626&apos;,
                },
            },
            ports: { ...ports },
        },
        true,
    )

    Graph.registerNode(
        &apos;custom-image&apos;,
        {
            inherit: &apos;rect&apos;,
            width: 52,
            height: 52,
            markup: [
                { tagName: &apos;rect&apos;, selector: &apos;body&apos; },
                { tagName: &apos;image&apos; },
                { tagName: &apos;text&apos;, selector: &apos;label&apos; },
            ],
            attrs: {
                body: { stroke: &apos;#5F95FF&apos;, fill: &apos;#5F95FF&apos; },
                image: {
                    width: 26,
                    height: 26,
                    refX: 13,
                    refY: 16,
                },
                label: {
                    refX: 3,
                    refY: 2,
                    textAnchor: &apos;left&apos;,
                    textVerticalAnchor: &apos;top&apos;,
                    fontSize: 12,
                    fill: &apos;#fff&apos;,
                },
            },
            ports: { ...ports },
        },
        true,
    )

    // 创建 stencil 节点（基础流程图）
    const r1 = graph.createNode({
        shape: &apos;custom-rect&apos;,
        label: &apos;开始&apos;,
        attrs: { body: { rx: 20, ry: 26 } },
    })
    const r2 = graph.createNode({
        shape: &apos;custom-rect&apos;,
        label: &apos;过程&apos;,
    })
    const r3 = graph.createNode({
        shape: &apos;custom-rect&apos;,
        label: &apos;可选过程&apos;,
        attrs: { body: { rx: 6, ry: 6 } },
    })
    const r4 = graph.createNode({
        shape: &apos;custom-polygon&apos;,
        label: &apos;决策&apos;,
        attrs: { body: { refPoints: &apos;0,10 10,0 20,10 10,20&apos; } },
    })
    const r5 = graph.createNode({
        shape: &apos;custom-polygon&apos;,
        label: &apos;数据&apos;,
        attrs: { body: { refPoints: &apos;10,0 40,0 30,20 0,20&apos; } },
    })
    const r6 = graph.createNode({
        shape: &apos;custom-circle&apos;,
        label: &apos;连接&apos;,
    })
    stencil.load([r1, r2, r3, r4, r5, r6], &apos;group1&apos;)

    // 创建 stencil 图标节点（系统设计图）
    const imageShapes = [
        {
            label: &apos;Client&apos;,
            image: &apos;https://gw.alipayobjects.com/zos/bmw-prod/687b6cb9-4b97-42a6-96d0-34b3099133ac.svg&apos;,
        },
        {
            label: &apos;Http&apos;,
            image: &apos;https://gw.alipayobjects.com/zos/bmw-prod/dc1ced06-417d-466f-927b-b4a4d3265791.svg&apos;,
        },
        {
            label: &apos;Api&apos;,
            image: &apos;https://gw.alipayobjects.com/zos/bmw-prod/c55d7ae1-8d20-4585-bd8f-ca23653a4489.svg&apos;,
        },
        {
            label: &apos;Sql&apos;,
            image: &apos;https://gw.alipayobjects.com/zos/bmw-prod/6eb71764-18ed-4149-b868-53ad1542c405.svg&apos;,
        },
        {
            label: &apos;Clound&apos;,
            image: &apos;https://gw.alipayobjects.com/zos/bmw-prod/c36fe7cb-dc24-4854-aeb5-88d8dc36d52e.svg&apos;,
        },
        {
            label: &apos;Mq&apos;,
            image: &apos;https://gw.alipayobjects.com/zos/bmw-prod/2010ac9f-40e7-49d4-8c4a-4fcf2f83033b.svg&apos;,
        },
    ]
    const imageNodes = imageShapes.map((item) =&amp;gt;
        graph!.createNode({
            shape: &apos;custom-image&apos;,
            label: item.label,
            attrs: { image: { &apos;xlink:href&apos;: item.image } },
        }),
    )
    stencil.load(imageNodes, &apos;group2&apos;)

}


export const onNodeClick = () =&amp;gt; {
    graph!.on(&apos;node:click&apos;, ({ node }) =&amp;gt; {
        // 通知流程展开
        emitter.emit(&apos;flowDrawerShow&apos;,node);
        console.log(graph!.toJSON());
    })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# index.tsx
import React, { createContext, useEffect } from &apos;react&apos;
import FlowWithDrawer from &apos;@/components/FlowWithDrawer&apos;

import { initGraph } from &apos;@/core/graph&apos;

const FlowTools: React.FC = () =&amp;gt; {
  useEffect(() =&amp;gt; {
    initGraph(document.getElementById(&apos;graph-container&apos;)!)
  }, [])

  return (
      &amp;lt;div&amp;gt;
        &amp;lt;div id=&quot;container&quot; className=&quot;flex h-screen&quot;&amp;gt;
          &amp;lt;div id=&quot;stencil&quot; className=&quot;w-[200px] border-r border-gray-300 overflow-auto&quot;&amp;gt;&amp;lt;/div&amp;gt;
          &amp;lt;div id=&quot;graph-container&quot; className=&quot;flex-1 relative&quot;&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;FlowWithDrawer/&amp;gt;
      &amp;lt;/div&amp;gt;
  )
}

export default FlowTools

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# tailwind.css

@tailwind base;
@tailwind components;
@tailwind utilities;

#container {
    display: flex;
    border: 1px solid #dfe3e8;
}

#stencil {
    width: 180px;
    height: 100%;
    position: relative;
    border-right: 1px solid #dfe3e8;
}

#graph-container {
    width: calc(100% - 180px);
    height: 100%;
}

.x6-widget-stencil {
    background-color: #fff;
}

.x6-widget-stencil-title {
    background-color: #fff;
}

.x6-widget-stencil-group-title {
    background-color: #fff !important;
}

.x6-widget-transform {
    margin: -1px 0 0 -1px;
    padding: 0px;
    border: 1px solid #239edd;
}

.x6-widget-transform&amp;gt;div {
    border: 1px solid #239edd;
}

.x6-widget-transform&amp;gt;div:hover {
    background-color: #3dafe4;
}

.x6-widget-transform-active-handle {
    background-color: #3dafe4;
}

.x6-widget-transform-resize {
    border-radius: 0;
}

.x6-widget-selection-inner {
    border: 1px solid #239edd;
}

.x6-widget-selection-box {
    opacity: 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-03-13-zjdemo/02.png&quot; alt=&quot;0&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2025-03-13-zjdemo/03.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;3. bpmn 图表&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# core.ts
import BpmnJS from &apos;bpmn-js/lib/Modeler&apos;;
import test from &apos;@/xml/test.bpmn&apos;


export let modeler:BpmnJS;

export const initBpmn = (container: HTMLElement) =&amp;gt; {
    if (modeler) return;
    // 创建 BpmnJS 实例
    modeler = new BpmnJS({
        container
    });

    modeler.importXML(test)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# index.tsx

import React, { useEffect, useRef } from &quot;react&quot;;
import { initBpmn } from &quot;@/core/core&quot;;
import &quot;bpmn-js/dist/assets/diagram-js.css&quot;;
import &quot;bpmn-js/dist/assets/bpmn-font/css/bpmn.css&quot;;

const BpmnTools: React.FC = () =&amp;gt; {

  useEffect(() =&amp;gt; {
      initBpmn(document.getElementById(&quot;bpmnCanvas&quot;)!);
  }, []);

  return (
    &amp;lt;div className=&quot;app&quot;&amp;gt;
      &amp;lt;div
        id=&quot;bpmnCanvas&quot;
        className=&quot;bpmn-viewer&quot;
        style={{ width: &quot;100%&quot;, height: &quot;100vh&quot; }}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default BpmnTools;

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# test.bpmn
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;definitions xmlns=&quot;http://www.omg.org/spec/BPMN/20100524/MODEL&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:flowable=&quot;http://flowable.org/bpmn&quot; xmlns:bpmndi=&quot;http://www.omg.org/spec/BPMN/20100524/DI&quot; xmlns:omgdc=&quot;http://www.omg.org/spec/DD/20100524/DC&quot; xmlns:omgdi=&quot;http://www.omg.org/spec/DD/20100524/DI&quot; typeLanguage=&quot;http://www.w3.org/2001/XMLSchema&quot; expressionLanguage=&quot;http://www.w3.org/1999/XPath&quot; targetNamespace=&quot;http://www.flowable.org/processdef&quot;&amp;gt;
  &amp;lt;process id=&quot;self_support_order_sales&quot; name=&quot;self_support_order_sales&quot; isExecutable=&quot;true&quot;&amp;gt;
    &amp;lt;documentation&amp;gt;self_support_order_sales&amp;lt;/documentation&amp;gt;
    &amp;lt;startEvent id=&quot;startEvent1&quot; name=&quot;开始&quot;&amp;gt;&amp;lt;/startEvent&amp;gt;
    &amp;lt;userTask id=&quot;sid-2C6198F8-7872-416A-A5D7-520922753E6F&quot; name=&quot;客户订单待接收&quot; flowable:assignee=&quot;${task_user}&quot; xmlns:buttonparam=&quot;http://buttonparam.com.cn&quot; buttonparam:buttonparam=&quot;[{&amp;amp;quot;id&amp;amp;quot;:5,&amp;amp;quot;name&amp;amp;quot;:&amp;amp;quot;Reject&amp;amp;quot;,&amp;amp;quot;alias&amp;amp;quot;:&amp;amp;quot;Reject&amp;amp;quot;,&amp;amp;quot;cssname&amp;amp;quot;:&amp;amp;quot; &amp;amp;quot;,&amp;amp;quot;type&amp;amp;quot;:&amp;amp;quot;1&amp;amp;quot;,&amp;amp;quot;createTime&amp;amp;quot;:&amp;amp;quot;2021-03-16 12:04:38&amp;amp;quot;,&amp;amp;quot;updateTime&amp;amp;quot;:&amp;amp;quot;2021-08-24 09:44:57&amp;amp;quot;,&amp;amp;quot;$$hashKey&amp;amp;quot;:&amp;amp;quot;object:1312&amp;amp;quot;},{&amp;amp;quot;id&amp;amp;quot;:13,&amp;amp;quot;name&amp;amp;quot;:&amp;amp;quot;Confirm&amp;amp;quot;,&amp;amp;quot;alias&amp;amp;quot;:&amp;amp;quot;Confirm&amp;amp;quot;,&amp;amp;quot;cssname&amp;amp;quot;:&amp;amp;quot;primary&amp;amp;quot;,&amp;amp;quot;type&amp;amp;quot;:&amp;amp;quot;1&amp;amp;quot;,&amp;amp;quot;createTime&amp;amp;quot;:&amp;amp;quot;2021-03-17 16:39:09&amp;amp;quot;,&amp;amp;quot;updateTime&amp;amp;quot;:&amp;amp;quot;2021-08-24 09:45:45&amp;amp;quot;,&amp;amp;quot;$$hashKey&amp;amp;quot;:&amp;amp;quot;object:1315&amp;amp;quot;}]&quot; xmlns:nodetype=&quot;http://nodetype.com.cn&quot; nodetype:nodetype=&quot;&quot; xmlns:mail_param=&quot;http://mailparam.com.cn&quot; mail_param:mail_param=&quot;&amp;amp;quot;&amp;amp;quot;&quot;&amp;gt;
      &amp;lt;extensionElements&amp;gt;
        &amp;lt;modeler:initiator-can-complete xmlns:modeler=&quot;http://flowable.org/modeler&quot;&amp;gt;&amp;lt;![CDATA[false]]&amp;gt;&amp;lt;/modeler:initiator-can-complete&amp;gt;
      &amp;lt;/extensionElements&amp;gt;
    &amp;lt;/userTask&amp;gt;
    &amp;lt;sequenceFlow id=&quot;sid-1EB675F0-8B1F-42E3-B105-AFD704C4F6B9&quot; sourceRef=&quot;startEvent1&quot; targetRef=&quot;sid-2C6198F8-7872-416A-A5D7-520922753E6F&quot;&amp;gt;&amp;lt;/sequenceFlow&amp;gt;
    &amp;lt;userTask id=&quot;sid-43EB1A4D-AB9D-4E8E-954B-44B48738DE7C&quot; name=&quot;客户订单已驳回&quot; flowable:assignee=&quot;${task_user}&quot; xmlns:buttonparam=&quot;http://buttonparam.com.cn&quot; buttonparam:buttonparam=&quot;&amp;amp;quot;&amp;amp;quot;&quot; xmlns:nodetype=&quot;http://nodetype.com.cn&quot; nodetype:nodetype=&quot;&quot; xmlns:mail_param=&quot;http://mailparam.com.cn&quot; mail_param:mail_param=&quot;&amp;amp;quot;&amp;amp;quot;&quot;&amp;gt;
      &amp;lt;extensionElements&amp;gt;
        &amp;lt;modeler:initiator-can-complete xmlns:modeler=&quot;http://flowable.org/modeler&quot;&amp;gt;&amp;lt;![CDATA[false]]&amp;gt;&amp;lt;/modeler:initiator-can-complete&amp;gt;
      &amp;lt;/extensionElements&amp;gt;
    &amp;lt;/userTask&amp;gt;
    &amp;lt;userTask id=&quot;sid-E0B61951-18D7-4CA5-B340-FE9A4CD4E9F0&quot; name=&quot;销售订单待确认&quot; flowable:assignee=&quot;${task_user}&quot; xmlns:buttonparam=&quot;http://buttonparam.com.cn&quot; buttonparam:buttonparam=&quot;[{&amp;amp;quot;id&amp;amp;quot;:5,&amp;amp;quot;name&amp;amp;quot;:&amp;amp;quot;Reject&amp;amp;quot;,&amp;amp;quot;alias&amp;amp;quot;:&amp;amp;quot;Reject&amp;amp;quot;,&amp;amp;quot;cssname&amp;amp;quot;:&amp;amp;quot; &amp;amp;quot;,&amp;amp;quot;type&amp;amp;quot;:&amp;amp;quot;1&amp;amp;quot;,&amp;amp;quot;createTime&amp;amp;quot;:&amp;amp;quot;2021-03-16 12:04:38&amp;amp;quot;,&amp;amp;quot;updateTime&amp;amp;quot;:&amp;amp;quot;2021-08-24 09:44:57&amp;amp;quot;,&amp;amp;quot;$$hashKey&amp;amp;quot;:&amp;amp;quot;object:2155&amp;amp;quot;},{&amp;amp;quot;id&amp;amp;quot;:13,&amp;amp;quot;name&amp;amp;quot;:&amp;amp;quot;Confirm&amp;amp;quot;,&amp;amp;quot;alias&amp;amp;quot;:&amp;amp;quot;Confirm&amp;amp;quot;,&amp;amp;quot;cssname&amp;amp;quot;:&amp;amp;quot;primary&amp;amp;quot;,&amp;amp;quot;type&amp;amp;quot;:&amp;amp;quot;1&amp;amp;quot;,&amp;amp;quot;createTime&amp;amp;quot;:&amp;amp;quot;2021-03-17 16:39:09&amp;amp;quot;,&amp;amp;quot;updateTime&amp;amp;quot;:&amp;amp;quot;2021-08-24 09:45:45&amp;amp;quot;,&amp;amp;quot;$$hashKey&amp;amp;quot;:&amp;amp;quot;object:2158&amp;amp;quot;}]&quot; xmlns:nodetype=&quot;http://nodetype.com.cn&quot; nodetype:nodetype=&quot;{
&amp;amp;quot;nodeType&amp;amp;quot;:&amp;amp;quot;Confirm_start&amp;amp;quot;,
&amp;amp;quot;signal&amp;amp;quot;:&amp;amp;quot;update_product&amp;amp;quot;
}&quot; xmlns:mail_param=&quot;http://mailparam.com.cn&quot; mail_param:mail_param=&quot;&amp;amp;quot;&amp;amp;quot;&quot;&amp;gt;
      &amp;lt;extensionElements&amp;gt;
        &amp;lt;modeler:initiator-can-complete xmlns:modeler=&quot;http://flowable.org/modeler&quot;&amp;gt;&amp;lt;![CDATA[false]]&amp;gt;&amp;lt;/modeler:initiator-can-complete&amp;gt;
      &amp;lt;/extensionElements&amp;gt;
    &amp;lt;/userTask&amp;gt;
    &amp;lt;userTask id=&quot;sid-34D7D478-B1FE-42A8-BBB1-FDC6BA6BD169&quot; name=&quot;销售订单已确认&quot; flowable:assignee=&quot;${task_user}&quot; xmlns:buttonparam=&quot;http://buttonparam.com.cn&quot; buttonparam:buttonparam=&quot;&amp;amp;quot;&amp;amp;quot;&quot; xmlns:nodetype=&quot;http://nodetype.com.cn&quot; nodetype:nodetype=&quot;&quot; xmlns:mail_param=&quot;http://mailparam.com.cn&quot; mail_param:mail_param=&quot;&amp;amp;quot;&amp;amp;quot;&quot;&amp;gt;
      &amp;lt;extensionElements&amp;gt;
        &amp;lt;modeler:initiator-can-complete xmlns:modeler=&quot;http://flowable.org/modeler&quot;&amp;gt;&amp;lt;![CDATA[false]]&amp;gt;&amp;lt;/modeler:initiator-can-complete&amp;gt;
      &amp;lt;/extensionElements&amp;gt;
    &amp;lt;/userTask&amp;gt;
    &amp;lt;endEvent id=&quot;sid-B45763A0-0E5F-41EF-B89F-54B52324D694&quot;&amp;gt;&amp;lt;/endEvent&amp;gt;
    &amp;lt;sequenceFlow id=&quot;sid-39048C6C-4266-45C4-96C7-D1C030542352&quot; sourceRef=&quot;sid-34D7D478-B1FE-42A8-BBB1-FDC6BA6BD169&quot; targetRef=&quot;sid-B45763A0-0E5F-41EF-B89F-54B52324D694&quot;&amp;gt;&amp;lt;/sequenceFlow&amp;gt;
    &amp;lt;userTask id=&quot;sid-DAF4B04E-6EF3-4AEB-B033-447CEA228874&quot; name=&quot;销售订单已驳回&quot; flowable:assignee=&quot;${task_user}&quot; xmlns:buttonparam=&quot;http://buttonparam.com.cn&quot; buttonparam:buttonparam=&quot;[{&amp;amp;quot;id&amp;amp;quot;:5,&amp;amp;quot;name&amp;amp;quot;:&amp;amp;quot;Reject&amp;amp;quot;,&amp;amp;quot;alias&amp;amp;quot;:&amp;amp;quot;Reject&amp;amp;quot;,&amp;amp;quot;cssname&amp;amp;quot;:&amp;amp;quot; &amp;amp;quot;,&amp;amp;quot;type&amp;amp;quot;:&amp;amp;quot;1&amp;amp;quot;,&amp;amp;quot;createTime&amp;amp;quot;:&amp;amp;quot;2021-03-16 12:04:38&amp;amp;quot;,&amp;amp;quot;updateTime&amp;amp;quot;:&amp;amp;quot;2021-08-24 09:44:57&amp;amp;quot;,&amp;amp;quot;$$hashKey&amp;amp;quot;:&amp;amp;quot;object:2988&amp;amp;quot;}]&quot; xmlns:nodetype=&quot;http://nodetype.com.cn&quot; nodetype:nodetype=&quot;&quot; xmlns:mail_param=&quot;http://mailparam.com.cn&quot; mail_param:mail_param=&quot;&amp;amp;quot;&amp;amp;quot;&quot;&amp;gt;
      &amp;lt;extensionElements&amp;gt;
        &amp;lt;modeler:initiator-can-complete xmlns:modeler=&quot;http://flowable.org/modeler&quot;&amp;gt;&amp;lt;![CDATA[false]]&amp;gt;&amp;lt;/modeler:initiator-can-complete&amp;gt;
      &amp;lt;/extensionElements&amp;gt;
    &amp;lt;/userTask&amp;gt;
    &amp;lt;sequenceFlow id=&quot;sid-1EE913E4-CCAB-439D-BE26-338D4530DFC3&quot; name=&quot;驳回(同步客户S/O)&quot; sourceRef=&quot;sid-DAF4B04E-6EF3-4AEB-B033-447CEA228874&quot; targetRef=&quot;sid-43EB1A4D-AB9D-4E8E-954B-44B48738DE7C&quot;&amp;gt;
      &amp;lt;conditionExpression xsi:type=&quot;tFormalExpression&quot;&amp;gt;&amp;lt;![CDATA[${Reject}]]&amp;gt;&amp;lt;/conditionExpression&amp;gt;
    &amp;lt;/sequenceFlow&amp;gt;
    &amp;lt;sequenceFlow id=&quot;sid-8CD85261-F4E8-438F-928C-6E891027584D&quot; name=&quot;驳回&quot; sourceRef=&quot;sid-E0B61951-18D7-4CA5-B340-FE9A4CD4E9F0&quot; targetRef=&quot;sid-DAF4B04E-6EF3-4AEB-B033-447CEA228874&quot;&amp;gt;
      &amp;lt;conditionExpression xsi:type=&quot;tFormalExpression&quot;&amp;gt;&amp;lt;![CDATA[${Reject}]]&amp;gt;&amp;lt;/conditionExpression&amp;gt;
    &amp;lt;/sequenceFlow&amp;gt;
    &amp;lt;sequenceFlow id=&quot;sid-BDCCAE12-E4C3-482E-9065-9BF43459CC15&quot; name=&quot;确认(触发orderP/O新建)&quot; sourceRef=&quot;sid-E0B61951-18D7-4CA5-B340-FE9A4CD4E9F0&quot; targetRef=&quot;sid-34D7D478-B1FE-42A8-BBB1-FDC6BA6BD169&quot;&amp;gt;
      &amp;lt;conditionExpression xsi:type=&quot;tFormalExpression&quot;&amp;gt;&amp;lt;![CDATA[${Confirm}]]&amp;gt;&amp;lt;/conditionExpression&amp;gt;
    &amp;lt;/sequenceFlow&amp;gt;
    &amp;lt;sequenceFlow id=&quot;sid-7040954B-1B18-450D-A42F-1EA77654A9CF&quot; name=&quot;确认&quot; sourceRef=&quot;sid-2C6198F8-7872-416A-A5D7-520922753E6F&quot; targetRef=&quot;sid-E0B61951-18D7-4CA5-B340-FE9A4CD4E9F0&quot;&amp;gt;
      &amp;lt;conditionExpression xsi:type=&quot;tFormalExpression&quot;&amp;gt;&amp;lt;![CDATA[${Confirm}]]&amp;gt;&amp;lt;/conditionExpression&amp;gt;
    &amp;lt;/sequenceFlow&amp;gt;
    &amp;lt;sequenceFlow id=&quot;sid-262A809B-A151-4D1B-B8A7-3F23FCBFA358&quot; name=&quot;驳回（同步客户P/O）&quot; sourceRef=&quot;sid-2C6198F8-7872-416A-A5D7-520922753E6F&quot; targetRef=&quot;sid-43EB1A4D-AB9D-4E8E-954B-44B48738DE7C&quot;&amp;gt;
      &amp;lt;conditionExpression xsi:type=&quot;tFormalExpression&quot;&amp;gt;&amp;lt;![CDATA[${Reject}]]&amp;gt;&amp;lt;/conditionExpression&amp;gt;
    &amp;lt;/sequenceFlow&amp;gt;
    &amp;lt;sequenceFlow id=&quot;sid-B904D61F-C5CF-41AB-A348-3B665ABC8A12&quot; sourceRef=&quot;sid-43EB1A4D-AB9D-4E8E-954B-44B48738DE7C&quot; targetRef=&quot;sid-B45763A0-0E5F-41EF-B89F-54B52324D694&quot;&amp;gt;&amp;lt;/sequenceFlow&amp;gt;
  &amp;lt;/process&amp;gt;
  &amp;lt;bpmndi:BPMNDiagram id=&quot;BPMNDiagram_self_support_order_sales&quot;&amp;gt;
    &amp;lt;bpmndi:BPMNPlane bpmnElement=&quot;self_support_order_sales&quot; id=&quot;BPMNPlane_self_support_order_sales&quot;&amp;gt;
      &amp;lt;bpmndi:BPMNShape bpmnElement=&quot;startEvent1&quot; id=&quot;BPMNShape_startEvent1&quot;&amp;gt;
        &amp;lt;omgdc:Bounds height=&quot;30.0&quot; width=&quot;30.0&quot; x=&quot;570.0&quot; y=&quot;30.0&quot;&amp;gt;&amp;lt;/omgdc:Bounds&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNShape bpmnElement=&quot;sid-2C6198F8-7872-416A-A5D7-520922753E6F&quot; id=&quot;BPMNShape_sid-2C6198F8-7872-416A-A5D7-520922753E6F&quot;&amp;gt;
        &amp;lt;omgdc:Bounds height=&quot;80.0&quot; width=&quot;100.0&quot; x=&quot;535.0&quot; y=&quot;186.0&quot;&amp;gt;&amp;lt;/omgdc:Bounds&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNShape bpmnElement=&quot;sid-43EB1A4D-AB9D-4E8E-954B-44B48738DE7C&quot; id=&quot;BPMNShape_sid-43EB1A4D-AB9D-4E8E-954B-44B48738DE7C&quot;&amp;gt;
        &amp;lt;omgdc:Bounds height=&quot;80.0&quot; width=&quot;100.0&quot; x=&quot;825.0&quot; y=&quot;186.0&quot;&amp;gt;&amp;lt;/omgdc:Bounds&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNShape bpmnElement=&quot;sid-E0B61951-18D7-4CA5-B340-FE9A4CD4E9F0&quot; id=&quot;BPMNShape_sid-E0B61951-18D7-4CA5-B340-FE9A4CD4E9F0&quot;&amp;gt;
        &amp;lt;omgdc:Bounds height=&quot;80.0&quot; width=&quot;100.0&quot; x=&quot;535.0&quot; y=&quot;405.0&quot;&amp;gt;&amp;lt;/omgdc:Bounds&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNShape bpmnElement=&quot;sid-34D7D478-B1FE-42A8-BBB1-FDC6BA6BD169&quot; id=&quot;BPMNShape_sid-34D7D478-B1FE-42A8-BBB1-FDC6BA6BD169&quot;&amp;gt;
        &amp;lt;omgdc:Bounds height=&quot;80.0&quot; width=&quot;100.0&quot; x=&quot;535.0&quot; y=&quot;660.0&quot;&amp;gt;&amp;lt;/omgdc:Bounds&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNShape bpmnElement=&quot;sid-B45763A0-0E5F-41EF-B89F-54B52324D694&quot; id=&quot;BPMNShape_sid-B45763A0-0E5F-41EF-B89F-54B52324D694&quot;&amp;gt;
        &amp;lt;omgdc:Bounds height=&quot;28.0&quot; width=&quot;28.0&quot; x=&quot;571.0&quot; y=&quot;840.0&quot;&amp;gt;&amp;lt;/omgdc:Bounds&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNShape bpmnElement=&quot;sid-DAF4B04E-6EF3-4AEB-B033-447CEA228874&quot; id=&quot;BPMNShape_sid-DAF4B04E-6EF3-4AEB-B033-447CEA228874&quot;&amp;gt;
        &amp;lt;omgdc:Bounds height=&quot;80.0&quot; width=&quot;100.0&quot; x=&quot;750.0&quot; y=&quot;405.0&quot;&amp;gt;&amp;lt;/omgdc:Bounds&amp;gt;
      &amp;lt;/bpmndi:BPMNShape&amp;gt;
      &amp;lt;bpmndi:BPMNEdge bpmnElement=&quot;sid-BDCCAE12-E4C3-482E-9065-9BF43459CC15&quot; id=&quot;BPMNEdge_sid-BDCCAE12-E4C3-482E-9065-9BF43459CC15&quot;&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;585.0&quot; y=&quot;484.95000000000005&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;585.0&quot; y=&quot;660.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNEdge bpmnElement=&quot;sid-8CD85261-F4E8-438F-928C-6E891027584D&quot; id=&quot;BPMNEdge_sid-8CD85261-F4E8-438F-928C-6E891027584D&quot;&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;634.9499999999999&quot; y=&quot;445.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;750.0&quot; y=&quot;445.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNEdge bpmnElement=&quot;sid-1EE913E4-CCAB-439D-BE26-338D4530DFC3&quot; id=&quot;BPMNEdge_sid-1EE913E4-CCAB-439D-BE26-338D4530DFC3&quot;&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;800.0&quot; y=&quot;405.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;800.0&quot; y=&quot;247.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;824.9999999999998&quot; y=&quot;246.03653846153847&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNEdge bpmnElement=&quot;sid-B904D61F-C5CF-41AB-A348-3B665ABC8A12&quot; id=&quot;BPMNEdge_sid-B904D61F-C5CF-41AB-A348-3B665ABC8A12&quot;&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;875.0&quot; y=&quot;265.95000000000005&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;875.0&quot; y=&quot;854.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;598.9499200108609&quot; y=&quot;854.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNEdge bpmnElement=&quot;sid-262A809B-A151-4D1B-B8A7-3F23FCBFA358&quot; id=&quot;BPMNEdge_sid-262A809B-A151-4D1B-B8A7-3F23FCBFA358&quot;&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;634.9499999999999&quot; y=&quot;226.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;824.9999999999804&quot; y=&quot;226.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNEdge bpmnElement=&quot;sid-1EB675F0-8B1F-42E3-B105-AFD704C4F6B9&quot; id=&quot;BPMNEdge_sid-1EB675F0-8B1F-42E3-B105-AFD704C4F6B9&quot;&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;585.0&quot; y=&quot;59.94999944212711&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;585.0&quot; y=&quot;186.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNEdge bpmnElement=&quot;sid-7040954B-1B18-450D-A42F-1EA77654A9CF&quot; id=&quot;BPMNEdge_sid-7040954B-1B18-450D-A42F-1EA77654A9CF&quot;&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;585.0&quot; y=&quot;265.95000000000005&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;585.0&quot; y=&quot;405.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
      &amp;lt;bpmndi:BPMNEdge bpmnElement=&quot;sid-39048C6C-4266-45C4-96C7-D1C030542352&quot; id=&quot;BPMNEdge_sid-39048C6C-4266-45C4-96C7-D1C030542352&quot;&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;585.0&quot; y=&quot;739.9499999999999&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
        &amp;lt;omgdi:waypoint x=&quot;585.0&quot; y=&quot;840.0&quot;&amp;gt;&amp;lt;/omgdi:waypoint&amp;gt;
      &amp;lt;/bpmndi:BPMNEdge&amp;gt;
    &amp;lt;/bpmndi:BPMNPlane&amp;gt;
  &amp;lt;/bpmndi:BPMNDiagram&amp;gt;
&amp;lt;/definitions&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记录下。。。。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-03-13-zjdemo/04.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[工具] 一个好用的 Nginx 配置生成工具</title><link>https://blog.loli.wang/blog/2025-02-06-ngconf/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2025-02-06-ngconf/doc/</guid><description>[工具] 一个好用的 Nginx 配置生成工具</description><pubDate>Thu, 06 Feb 2025 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;NGINXConfig 工具介绍&lt;/h2&gt;
&lt;p&gt;在运维和开发过程中，配置 Nginx 常常是一项既重要又令人头疼的任务。尤其是对于新手来说，手动编写 Nginx 配置文件可能充满了各种细节与坑。今天，我们要介绍的 &lt;strong&gt;NGINXConfig 工具&lt;/strong&gt;，正是一款能够帮你快速生成优化配置的在线工具，极大地降低了配置 Nginx 的门槛。&lt;/p&gt;
&lt;h3&gt;工具概述&lt;/h3&gt;
&lt;p&gt;NGINXConfig 是 DigitalOcean 提供的一个在线配置生成工具，它可以让你通过简单的表单输入，定制出符合自己需求的 Nginx 配置文件。无论你是配置静态网站、反向代理、还是需要支持 PHP 的应用，这个工具都可以帮助你一步步完成配置，从而减少出错几率，提高部署效率。&lt;/p&gt;
&lt;h3&gt;为什么选择 NGINXConfig？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;零基础友好&lt;/strong&gt;&lt;br /&gt;
对于从未接触过 Nginx 配置的新手来说，该工具提供了直观的配置项说明和默认推荐值，你只需按照提示一步步填写即可生成完整的配置文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;定制化选项丰富&lt;/strong&gt;&lt;br /&gt;
无论你需要配置 SSL/TLS 加密、反向代理、URL 重写等功能，NGINXConfig 都能提供相应的选项，让你可以根据项目需要自由选择。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;快速部署&lt;/strong&gt;&lt;br /&gt;
配置好后，你可以直接复制生成的配置内容或下载配置文件，方便快捷地将新配置部署到你的服务器上。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;如何使用 NGINXConfig？&lt;/h3&gt;
&lt;p&gt;下面简单介绍一下使用步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;访问工具页面&lt;/strong&gt;&lt;br /&gt;
打开浏览器，访问 &lt;a href=&quot;https://www.digitalocean.com/community/tools/nginx&quot;&gt;NGINXConfig 工具&lt;/a&gt;。页面加载后，你会看到多个配置选项。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;填写基本信息&lt;/strong&gt;&lt;br /&gt;
在页面上，你需要输入服务器相关的信息，比如域名、网站根目录、是否需要 SSL 加密等。每一项都有详细的说明，帮助你理解配置项的作用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;选择高级选项&lt;/strong&gt;&lt;br /&gt;
如果你对 Nginx 配置有所了解，还可以展开高级选项，进行更细致的定制，比如反向代理设置、缓存策略、错误页面定制等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;生成并预览配置&lt;/strong&gt;&lt;br /&gt;
填写完所有信息后，点击“生成配置”按钮。系统会根据你的设置生成对应的 Nginx 配置文件，你可以在页面上预览效果，确认无误后复制或下载配置文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;部署到服务器&lt;/strong&gt;&lt;br /&gt;
将生成的配置文件上传到服务器的 &lt;code&gt;/etc/nginx&lt;/code&gt; 目录（或对应的配置目录），记得先备份原有的配置文件。上传后，通过 &lt;code&gt;nginx -t&lt;/code&gt; 命令测试配置是否正确，最后重启 Nginx 服务，使新配置生效。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-02-06-ngconf/01.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;备份原有配置&lt;/strong&gt;&lt;br /&gt;
在替换配置之前，务必备份原有的 Nginx 配置文件，以防新配置出现问题时能够迅速恢复。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;测试环境先行&lt;/strong&gt;&lt;br /&gt;
尽量在测试环境中验证新配置，确保不会影响生产环境中的业务运行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;理解各项设置&lt;/strong&gt;&lt;br /&gt;
虽然工具已经帮你简化了配置流程，但了解基本的 Nginx 配置知识依然非常重要。建议新手在使用工具的同时，多阅读相关教程和官方文档，加深对配置原理的理解。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;NGINXConfig 工具是一款为新手和经验不足的开发者设计的实用配置生成工具，它能帮你快速上手并生成高质量的 Nginx 配置。通过直观的界面和详细的提示信息，即使你对 Nginx 了解不多，也可以轻松完成配置任务。如果你正在为 Nginx 配置而烦恼，不妨试试这款工具，相信它会给你带来不小的帮助！&lt;/p&gt;
&lt;p&gt;希望这篇博文能为你提供一些实用的技巧和帮助。如果你有任何问题或使用心得，欢迎在评论区留言讨论！&lt;/p&gt;
</content:encoded></item><item><title>【记录】旧版 Dart Sass 中已经移除 @import 引入方式的问题</title><link>https://blog.loli.wang/blog/2025-01-20-sasserrorupd/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2025-01-20-sasserrorupd/doc/</guid><description>Deprecation Warning: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.</description><pubDate>Mon, 20 Jan 2025 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;将公司的项目项目升级sass版本的时候发现了项目警告&lt;/h2&gt;
&lt;p&gt;提示如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Deprecation Warning: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.

More info and automated migrator: https://sass-lang.com/d/import
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2025-01-20-sasserrorupd/01.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;p&gt;官方提示了，使用新的引入方式 @use，如下已经不让使用 @import 引入方式了&lt;/p&gt;
&lt;p&gt;在vite里面修改下配置文件即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 
 css: {
    preprocessorOptions: {
      scss: {
        // additionalData: `@import &quot;./node_modules/swiper/swiper.scss&quot;;`,
        additionalData: `@use &quot;./node_modules/swiper/swiper.scss&quot;;`,
      },
    },
  }
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>如何将U盘拷录后进行格式化</title><link>https://blog.loli.wang/blog/2024-12-14-usbkaolurest/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-12-14-usbkaolurest/doc/</guid><description>如何将U盘拷录后进行格式化</description><pubDate>Sat, 14 Dec 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;因为前一阵子装linux的时候，拷录了一个镜像，但是后续发现没办法在win中直接格式化。导致状态不符, 原本16gb的U盘就剩余几百M容量了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-12-14-usbkaolurest/03.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;步骤&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;打开CMD(命令指示符) ，输入 &lt;code&gt;diskpart&lt;/code&gt; 命令，进入磁盘管理。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-12-14-usbkaolurest/01.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入 &lt;code&gt;list disk&lt;/code&gt; 命令，查看磁盘列表，找到要格式化的磁盘，输入 &lt;code&gt;select disk x&lt;/code&gt; x为磁盘序号，例如我的是0，输入 &lt;code&gt;clean&lt;/code&gt; 命令。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-12-14-usbkaolurest/02.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-12-14-usbkaolurest/04.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;创建卷 &lt;code&gt;create partition primary&lt;/code&gt; ,查看 &lt;code&gt;list partition&lt;/code&gt;, 最后选择卷&lt;code&gt;select partition&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后进行格式化 U盘恢复正常
&lt;img src=&quot;http://img.blog.loli.wang/2024-12-14-usbkaolurest/05.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>写了个临时邮箱的小Demo</title><link>https://blog.loli.wang/blog/2024-11-05-amail/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-11-05-amail/doc/</guid><description>写了个临时邮箱的小Demo</description><pubDate>Tue, 05 Nov 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h1&gt;关于AMAIL 邮件服务&lt;/h1&gt;
&lt;p&gt;AMAIL 是一个基于Cloudflare Email Routeing的邮件服务，用于快速搭建临时邮箱服务。&lt;/p&gt;
&lt;h1&gt;快速上手&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;创建一个 Cloudflare 账户，并且绑定好一个属于自己的域名。 并且创建一个 Cloudflare Pages 项目。和一个 Cloudflare Workers。同样的也建立好KV存储。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改 &lt;strong&gt;/packages/workers/wrangler.toml&lt;/strong&gt; 中的 &lt;strong&gt;name&lt;/strong&gt; 改为你的cloudflare workers名称。 并且修改kv的&lt;code&gt;binding&lt;/code&gt; &lt;code&gt;id&lt;/code&gt;，修改 &lt;strong&gt;/packages/web/wrangler.toml&lt;/strong&gt; 中的 &lt;code&gt;name&lt;/code&gt; 改为你的cloudflare pages的所属name&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改 &lt;strong&gt;/packages/web/config.ts&lt;/strong&gt; 中的 &lt;code&gt;nuxt.config.ts&lt;/code&gt; 文件&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;public: {
    API_URL: &quot;amail.xmw.pw&quot;,  // 你的workers绑定的域名
    EMAIL_DOMAIN: &quot;xmw.pw&quot;    // 你所使用的邮箱域名
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;创建一个 Cloudflare routeing，开启 &lt;code&gt;Catch-all addres&lt;/code&gt;s 模式，并指向 &lt;code&gt;Workers&lt;/code&gt; 为邮件接受地&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-11-05-amail/3.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;执行命令一键部署&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;pnpm build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;预览&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-11-05-amail/0.png&quot; alt=&quot;0&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-05-amail/1.png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://xmw.pw&quot;&gt;演示地址&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/itmowang/amail&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>关于我最近频繁换耳机的测评</title><link>https://blog.loli.wang/blog/2024-11-04-erjiceping/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-11-04-erjiceping/doc/</guid><description>关于我最近频繁换耳机的测评</description><pubDate>Mon, 04 Nov 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h1&gt;耳机只是消耗品罢了&lt;/h1&gt;
&lt;p&gt;在一个月前，我正在使用的耳机是  &lt;a href=&quot;https://www.sonystyle.com.cn/products/headphone/linkbuds_s/linkbuds_s_e.html&quot;&gt;Sony LinkBuds S&lt;/a&gt;，他是一款入耳式耳机， 降噪功能不错，我的使用时常为1年多一点，到目前为止他一直功能正常，但是美中不足的是，电池寿命已经短到无法使用了。而保修期仅限一年，所以只能换了。准确的说没有进行更换，我开始有了一个自己更换电池的想法。 正是这种想法。。。&lt;/p&gt;
&lt;p&gt;我购买了同款的电池，以及购买了热风枪。和耳机专用胶水，虽然也是更换了电池，但是耳机合不上了，不知道是胶水问题还是我的操作问题，耳机边缘处也多了很多刀痕&lt;/p&gt;
&lt;p&gt;强烈建议每次买耳机的时候给耳机买上全保换新只换不修的服务！！！！！！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/0.jpg&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/1.jpg&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/3.jpg&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到，确实成功了，但是因为没有很专业的工具设备，所以没有很好的结果，甚至因为各种失误造成了很多困扰。&lt;/p&gt;
&lt;h1&gt;为什么会考虑开放式耳机&lt;/h1&gt;
&lt;p&gt;因为前一阵子头疼，未知的头疼，做ct的时候医生让看下耳朵，结果说有点中耳炎，所以是考虑开放式耳机。。&lt;/p&gt;
&lt;h2&gt;正题&lt;/h2&gt;
&lt;p&gt;因为预算有限，我在这中途买了很多款开放式耳机，对比降噪，音质，还有佩戴舒适程度，还有价格，毕竟千元内的预算哈哈。。&lt;/p&gt;
&lt;p&gt;（可以思考的是开放式耳机的降噪，都不要抱有太大的期望，因为在地铁等公众场合，他是没有办法满足你的需求的，硬性问题，不是因为是入耳式）&lt;/p&gt;
&lt;h3&gt;不是专业人士，以一个体验者来评价&lt;/h3&gt;
&lt;p&gt;第一款登场的是&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;小米开放式耳机&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;音质： 还不错&lt;/p&gt;
&lt;p&gt;降噪： 很一般，地铁上你根本就听不到耳机里的声音&lt;/p&gt;
&lt;p&gt;佩戴舒适程度： 差劲 (佩戴有种硬邦邦的感觉)&lt;/p&gt;
&lt;p&gt;价格：650 左右&lt;/p&gt;
&lt;p&gt;颜值： 你是社牛就带着吧 ，&lt;/p&gt;
&lt;p&gt;评价： 小米的耳机系列不要抱有太大的期望，手机性价比做的还不错，耳机这方面还有很远的路要走。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/4.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/5.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/6.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/7.jpg&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;韶音 Openfit Air&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;音质： 没小米好（计量单位，小米）&lt;/p&gt;
&lt;p&gt;降噪： 很一般。&lt;/p&gt;
&lt;p&gt;佩戴舒适程度：很不错，戴在耳朵上很舒服。&lt;/p&gt;
&lt;p&gt;价格：678 左右&lt;/p&gt;
&lt;p&gt;颜值： 一般&lt;/p&gt;
&lt;p&gt;评价： 老实说戴着确实比小米的舒服，但是音质不太好，有种故意给你把音量调小了的感觉。拉胯，别买
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/8.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/9.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/10.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/11.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/12.jpg&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;华为 FreeClip （最终选择）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;音质： 还不错&lt;/p&gt;
&lt;p&gt;降噪： 还不错，但是和小米的一样有相同的缺点，好上那么一丢丢。&lt;/p&gt;
&lt;p&gt;佩戴舒适程度：很不错，夹在耳朵上 很轻 耳机仓盒子很小。&lt;/p&gt;
&lt;p&gt;价格：1200 左右&lt;/p&gt;
&lt;p&gt;颜值： 高&lt;/p&gt;
&lt;p&gt;评价： 耳机盒子很小，耳机很mini，但是不会掉，音质也不错，我原本以为华为也就那个b摸样，价格贵东西差，但是事实上他确实还行&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/13.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/14.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/15.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/16.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/17.jpg&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;你就体验了这三款开放式耳机？&lt;/h3&gt;
&lt;p&gt;没，我体验了很多，甚至去线下体验店也体验了很多款式，但是最终还是选了华为的。某种意义上来说开放式耳机还有很长的路要走，不值得你去花钱买&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/18.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/19.jpg&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-11-04-erjiceping/20.jpg&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[转职说明] 恭喜你达到20级，请选择你的转职路线</title><link>https://blog.loli.wang/blog/2024-10-10-mhsj/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-10-10-mhsj/doc/</guid><description>[转职说明] 恭喜你达到20级，请选择你的转职路线</description><pubDate>Thu, 10 Oct 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h1&gt;恭喜你达到20等级，请选择你的转职路线：&lt;/h1&gt;
&lt;h3&gt;火系魔法师&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-10-mhsj/0.gif&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;冰系魔法师&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-10-mhsj/1.gif&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;雷系魔法师&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-10-mhsj/2.gif&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;神枪手&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-10-mhsj/3.gif&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;战士&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-10-mhsj/4.gif&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;厨师&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-10-mhsj/5.gif&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[问题记录] 使用 cloudflare wrangler 使用代理导致 Invalid URL</title><link>https://blog.loli.wang/blog/2024-10-08-cfworkersbughttpporxy/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-10-08-cfworkersbughttpporxy/doc/</guid><description>[问题记录] 使用 cloudflare wrangler 使用代理导致 Invalid URL</description><pubDate>Tue, 08 Oct 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h1&gt;因为白嫖 cloudflare 的无服务器服务 ，  开了代理导致启动一直   Invalid URL&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-08-cfworkersbughttpPorxy/01.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;同样的我去官方找了文档,也同样的，没有找到相关的说明，去官方看issues有相关的提问，但是都不了了之，最后在隔壁看到了解决方案。。。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-08-cfworkersbughttpPorxy/02.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-08-cfworkersbughttpPorxy/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;删除 http_proxy ，再执行 wrangler dev 就可以了。&lt;/p&gt;
</content:encoded></item><item><title>一个基于 Monorepo + CloudFlare 全家桶 + Prisma + NodeJs 的简易全栈项目模板。</title><link>https://blog.loli.wang/blog/2024-10-05-mwcftemplate/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-10-05-mwcftemplate/doc/</guid><description>一个基于 Monorepo + CloudFlare Worker + CloudFlare Pages + CloudFlare D1 + Prisma + NodeJs 的简易全栈项目模板。</description><pubDate>Sat, 05 Oct 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;一个基于 Monorepo + CloudFlare Worker + CloudFlare Pages + CloudFlare D1 + Prisma + NodeJs 的简易全栈项目模板。&lt;/p&gt;
&lt;h2&gt;升级日志&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2024-10-05 编写了基础的模板，并编写了用户注册和登录功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 拉取项目
git clone https://github.com/itmowang/cloudflare_system.git

# 安装pnpm
npm install pnpm -g

# 拉取项目依赖
pnpm install

# 启动本地环境
pnpm run dev

# 发布到 CloudFlare
pnpm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;其他相关&lt;/h2&gt;
&lt;p&gt;方便用于开展其他项目的个人向模板&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/itmowang/cloudflare_system&quot;&gt;https://github.com/itmowang/cloudflare_system&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;文档&lt;/h2&gt;
&lt;p&gt;...持续更新中&lt;/p&gt;
&lt;h2&gt;演示&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-05-mwcftemplate/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-10-05-mwcftemplate/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[白嫖] Lade 免费白嫖应用部署容器 1核心 128MB内存 1GB数据库 1GB磁盘 100GB流量</title><link>https://blog.loli.wang/blog/2024-09-14-lade/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-09-14-lade/doc/</guid><description>[白嫖] Lade 免费白嫖应用部署容器 1核心 128MB内存 1GB数据库 1GB磁盘 100GB流量</description><pubDate>Sat, 14 Sep 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;最近&lt;/h3&gt;
&lt;p&gt;最近没有因为身体原因没咋更新，并且也没啥写的，今天逛社区看见一个新的应用部署平台，有免费额度。毕竟白嫖。折腾个玩玩&lt;/p&gt;
&lt;h3&gt;Lade&lt;/h3&gt;
&lt;p&gt;Lade 的使命是构建一个简单、可靠且易于扩展的云平台。我们很高兴能够让开发人员和公司更轻松地管理生产中的应用程序。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lade.io/&quot;&gt;Lade&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在价格表中可以看到，Lade 的免费额度有 1 核心 128MB 内存 1GB 数据库 1GB 磁盘 100GB 流量。感觉可以满足一个普通 blog 的需求， 尝试着部署个 WordPress 吧&lt;/p&gt;
&lt;p&gt;Lade 支持 Go , Nodejs, PHP, Python ,Ruby 这几种语言。同样的支持 MariaDB，Memcached，MongoDB，Mysql，PostgreSQL，Redis 这几种不同的数据库&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;注册并登录 &lt;strong&gt;Lade&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/01.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;windows&lt;/strong&gt; 客户端下载 并且配置环境变量 （当然如果是 Linux 就不用了，或者说你win电脑上有&lt;strong&gt;wsl2&lt;/strong&gt;也行）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lade-io/lade/releases/download/v0.1.7/lade-darwin-amd64.tar.gz&quot;&gt;https://github.com/lade-io/lade/releases/download/v0.1.7/lade-darwin-amd64.tar.gz&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;下载好后配置将压缩包中的 &lt;strong&gt;lade.exe&lt;/strong&gt; 放入 D 盘，然后配置环境变量 &lt;strong&gt;D:\lade&lt;/strong&gt; , 并且执行指令 &lt;strong&gt;lade&lt;/strong&gt; 看看否可以正常执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/02.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/03.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建一个应用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;执行命令 &lt;strong&gt;lade apps create 项目名称&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建一个应用
lade apps create lade_blog

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会提示输入账号密码 ，按照我们注册的输入，会让我们选择存储服务器的节点，我们中国离新加坡最近，所以选择 &lt;strong&gt;Singapore&lt;/strong&gt;，这样会让我们网站对自己访问速度更快。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/04.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;这样代表应用就建立完成了&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/05.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;执行 &lt;strong&gt;lade apps list&lt;/strong&gt; 可以看到我们创建的应用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lade apps list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/06.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;执行 lade apps deploy 命令 , 进行部署&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# 部署应用
lade apps deploy lade_blog

# 查看部署状态
lade apps show lade_blog
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/07.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;查看我们部署的测试文件&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/08.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;接下来我们将 Wordpress 部署到 Lade 上 ，将我们的wordpress 源码放置在lade创建的根目录下&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/09.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;虽然这样能够正常跑起 PHP 的程序，但是他有一些问题，一些wordpress所需要用到的拓展没有安装，所以不能用这种单纯上传文件的方式去使用。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/10.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/11.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;配置 Composer&lt;/h2&gt;
&lt;p&gt;可以看到，他的介绍是如果想支持其他的拓展，需要用到 composer 的支持， composer 是 PHP的包管理器，类似于node中的npm。&lt;/p&gt;
&lt;p&gt;安装好composer 后，执行 &lt;strong&gt;composer --version&lt;/strong&gt; 查看是否安装成功。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/12.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;配置好 composer 后，我们执行 &lt;strong&gt;composer init&lt;/strong&gt; 命令，生成一个 composer.json 文件。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;http://img.blog.loli.wang/2024-09-14-lade/13.png&quot; width=&quot;600&quot; /&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>electron + Vue 搭建第一个桌面端应用</title><link>https://blog.loli.wang/blog/2024-08-21-electronvue/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-08-21-electronvue/doc/</guid><description>electron + Vue 搭建第一个桌面端应用</description><pubDate>Wed, 21 Aug 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;搭建环境&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 推荐使用yarn
npm i -g yarn

# 创建工作目录
mkdir electron-vue
cd electron-vue
yarn init

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改&lt;code&gt;package.json&lt;/code&gt;文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;electron-vue&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;Hello World!&quot;,
  &quot;main&quot;: &quot;main.js&quot;,
  &quot;author&quot;: &quot;Mowang&quot;,
  &quot;license&quot;: &quot;MIT&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装依赖&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 提前切换成淘宝源
npm config set registry https://registry.npmmirror.com/
# 安装electron 网络原因会无法下载，更换源
yarn config set electron_mirror &apos;https://npmmirror.com/mirrors/electron/&apos;
# 安装electron
yarn add --dev electron

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 &lt;code&gt;package.json&lt;/code&gt; 文件,增加启动脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;electron-vue&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;Hello World!&quot;,
  &quot;main&quot;: &quot;main.js&quot;,
  &quot;author&quot;: &quot;Mowang&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;electron .&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 &lt;code&gt;main.js&lt;/code&gt; 文件&lt;/p&gt;
&lt;p&gt;写入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { app, BrowserWindow } = require(&quot;electron/main&quot;);
const path = require(&quot;node:path&quot;);

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, &quot;preload.js&quot;),
    },
  });

  win.loadFile(&quot;index.html&quot;);
}

app.whenReady().then(() =&amp;gt; {
  createWindow();

  app.on(&quot;activate&quot;, () =&amp;gt; {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

app.on(&quot;window-all-closed&quot;, () =&amp;gt; {
  if (process.platform !== &quot;darwin&quot;) {
    app.quit();
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 &lt;code&gt;index.html&lt;/code&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;title&amp;gt;Hello World!&amp;lt;/title&amp;gt;
    &amp;lt;meta
      http-equiv=&quot;Content-Security-Policy&quot;
      content=&quot;script-src &apos;self&apos; &apos;unsafe-inline&apos;;&quot;
    /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Hello World!&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;
      We are using Node.js &amp;lt;span id=&quot;node-version&quot;&amp;gt;&amp;lt;/span&amp;gt;, Chromium
      &amp;lt;span id=&quot;chrome-version&quot;&amp;gt;&amp;lt;/span&amp;gt;, and Electron
      &amp;lt;span id=&quot;electron-version&quot;&amp;gt;&amp;lt;/span&amp;gt;.
    &amp;lt;/p&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 &lt;code&gt;preload.js&lt;/code&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;window.addEventListener(&quot;DOMContentLoaded&quot;, () =&amp;gt; {
  const replaceText = (selector, text) =&amp;gt; {
    const element = document.getElementById(selector);
    if (element) element.innerText = text;
  };

  for (const type of [&quot;chrome&quot;, &quot;node&quot;, &quot;electron&quot;]) {
    replaceText(`${type}-version`, process.versions[type]);
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-21-electronVue/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;运行项目进行启动测试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试成功&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-21-electronVue/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;创建Vue项目&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yarn add vue
yarn add vue-router
yarn add vue-tsc -D
yarn add vite -D
yarn add typescript -D
yarn add @vitejs/plugin-vue

npx tsc --init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改&lt;code&gt;package.json&lt;/code&gt;文件, 修改 &lt;code&gt;scripts&lt;/code&gt; 脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;electron .&quot;,
    &quot;dev&quot;: &quot;vite&quot;,
    &quot;build&quot;: &quot;vite build&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改原有 &lt;code&gt;index.html&lt;/code&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Hello World!&amp;lt;/title&amp;gt;
    &amp;lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;script-src &apos;self&apos; &apos;unsafe-inline&apos;;&quot; /&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script type=&quot;module&quot; src=&quot;/src/main.ts&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 &lt;code&gt;src/main.ts&lt;/code&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createApp } from &apos;vue&apos;;
import App from &apos;./App.vue&apos;;

const app = createApp(App);
app.mount(&apos;#root&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 &lt;code&gt;vite.config.ts&lt;/code&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &quot;vite&quot;;
import vue from &quot;@vitejs/plugin-vue&quot;;

export default defineConfig({
  plugins: [vue()],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建立 &lt;code&gt;src/App.vue&lt;/code&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&amp;lt;template&amp;gt;
  &amp;lt;div id=&quot;app&quot;&amp;gt;
    {{msg}}
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &quot;App&quot;,
  data() {
    return {
      msg: &quot;Welcome to Your Vue.js App&quot;,
    };
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进行&lt;code&gt;web&lt;/code&gt;启动的测试  &lt;code&gt;yarn dev&lt;/code&gt; 能够正常启动vue项目&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-21-electronVue/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-21-electronVue/04.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;安装其他依赖 让electron支持vite打包出来的代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yarn add vite-plugin-electron -D
yarn add vite-plugin-electron-renderer -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;code&gt;vite.config.ts&lt;/code&gt;文件中添加如下配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &quot;vite&quot;;
import vue from &quot;@vitejs/plugin-vue&quot;;
import electron from &apos;vite-plugin-electron/simple&apos;


export default defineConfig({
  plugins: [vue(),electron({
    main: {
      entry: &apos;./main.js&apos;,
    },
    preload: {
      input: &apos;./preload.js&apos;,
    },
    renderer: {},
  })],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 electron 的 &lt;code&gt;main.js&lt;/code&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { app, BrowserWindow } = require(&apos;electron/main&apos;)
const path = require(&apos;node:path&apos;)

function createWindow() {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, &apos;preload.js&apos;)
        }
    })

    win.loadFile(&apos;index.html&apos;)
}

// app.whenReady().then(() =&amp;gt; {
//   createWindow()

//   app.on(&apos;activate&apos;, () =&amp;gt; {
//     if (BrowserWindow.getAllWindows().length === 0) {
//       createWindow()
//     }
//   })
// })

app.whenReady().then(() =&amp;gt; {
    const win = new BrowserWindow({
        title: &apos;Main window&apos;,
    })

    // You can use `process.env.VITE_DEV_SERVER_URL` when the vite command is called `serve`
    if (process.env.VITE_DEV_SERVER_URL) {
        win.loadURL(process.env.VITE_DEV_SERVER_URL)
    } else {
        // Load your file
        win.loadFile(&apos;dist/index.html&apos;);
    }
})


app.on(&apos;window-all-closed&apos;, () =&amp;gt; {
    if (process.platform !== &apos;darwin&apos;) {
        app.quit()
    }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 &lt;code&gt;package.json&lt;/code&gt; 中 &lt;code&gt;main &lt;/code&gt;的指向改为 &lt;code&gt;dist-electron/main.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;main&quot;: &quot;dist-electron/main.js&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-21-electronVue/05.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;修改运行指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &quot;scripts&quot;: {
    &quot;start&quot;: &quot;electron .&quot;,
    &quot;dev&quot;: &quot;vite&quot;,
    &quot;dev:win&quot;:&quot;vite build  &amp;amp;&amp;amp; electron .&quot;,
    &quot;build&quot;: &quot;vite build&quot;
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行 &lt;code&gt;yarn dev:win&lt;/code&gt; 测试&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-21-electronVue/06.png&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;添加electron-builder打包&lt;/h2&gt;
&lt;p&gt;(这个应该没上面好说的 比较简单)&lt;/p&gt;
&lt;h2&gt;参考代码&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/itmowang/desktop-vue-demo&quot;&gt;demo 地址&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>2024年了居然有人还不会搭建FRP做内网穿透</title><link>https://blog.loli.wang/blog/2024-08-17-frpinit/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-08-17-frpinit/doc/</guid><description>2024年了居然有人还不会搭建FRP做内网穿透</description><pubDate>Sat, 17 Aug 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;最近不少人问我内网穿透的问题，我觉着作为一个开发者，正常可能经常会有些这种需求， 一般直接推荐 &lt;strong&gt;花生壳&lt;/strong&gt; 、&lt;strong&gt;NATAPP&lt;/strong&gt;等产品，但是直到问我，想要映射的端口太多怎么办，想绑定域名怎么办，不想备案怎么办，等等等这些问题。。。 我就会推荐自己搭建服务。但是纠结的是。很多人连听都没听过。&lt;/p&gt;
&lt;h2&gt;搭建前提&lt;/h2&gt;
&lt;p&gt;一台带有公网ip的外网服务器(VPS)&lt;/p&gt;
&lt;h2&gt;什么是FRP&lt;/h2&gt;
&lt;p&gt;frp 是一个专注于内网穿透的高性能的反向代理应用，支持 TCP、UDP、HTTP、HTTPS 等多种协议，且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。&lt;/p&gt;
&lt;h2&gt;安装服务端&lt;/h2&gt;
&lt;p&gt;去github找到最新的发布的版本,并在服务器上使用使用&lt;strong&gt;uname -m&lt;/strong&gt;命令检查当前系统架构&lt;/p&gt;
&lt;p&gt;https://github.com/fatedier/frp/releases/tag/v0.59.0&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用wget命令下载服务端压缩包
wget https://github.com/fatedier/frp/releases/download/v0.59.0/frp_0.59.0_linux_amd64.tar.gz

# 解压到 /usr/local
tar -zxvf frp_0.59.0_linux_amd64.tar.gz -C /usr/local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进入 &lt;strong&gt;/etc/systemd/system/&lt;/strong&gt; 目录建立新的文件 &lt;strong&gt;frps.service&lt;/strong&gt; 配置成Systemd服务&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# frps.service 
[Unit]
Description=frps service
After=network.target syslog.target
Wants=network.target
[Service]
Type=simple
#Restart=always
Restart=on-failure
RestartSec=5s
#启动服务的命令
ExecStart=/usr/local/frp_0.59.0_linux_amd64/frps -c /usr/local/frp_0.59.0_linux_amd64/frps.toml
[Install]
WantedBy=multi-user.target

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;保存 切换回我们原来的 &lt;strong&gt;/usr/local/frp_0.59.0_linux_amd64&lt;/strong&gt; 目录，修改文件 &lt;strong&gt;frps.toml&lt;/strong&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
[common]
bind_addr = xx.xx.xx.xx # 你的服务器ip地址
bind_port = 7000
kcp_bind_port = 7000
vhost_https_port = 7001
dashboard_addr = xx.xx.xx.xx
dashboard_port = 7500
dashboard_user = admin
dashboard_pwd = admin
log_file = ./frps.log
log_level = info
log_max_days = 3
authentication_timeout = 900
token=mowangmowang
allow_ports = 2000-3000,3001,3003,4000-50000,3362
max_pool_count = 50
max_ports_per_client = 0

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://gofrp.org/zh-cn/docs/reference/server-configures/&quot;&gt;服务端配置详情 - gofrp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/04.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样就差不多配置完成了。&lt;/p&gt;
&lt;p&gt;在服务器上启动frp服务端&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 设为开机自启
sudo systemctl enable frps
# 启动frp
sudo systemctl start frps
# 查看启动日志 
sudo systemctl status frps 
# 重启frp服务
sudo systemctl restart frps 
# 关闭frp服务
sudo systemctl stop frps 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/05.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后去网页上访问我们刚刚配置的 &lt;strong&gt;ip:7500&lt;/strong&gt; 管理面板是否正常 ，输入我们刚刚配置文件里设置的账号密码 &lt;strong&gt;admin&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/06.png&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样服务端就配置成功了&lt;/p&gt;
&lt;h2&gt;客户端的使用 (WIN)&lt;/h2&gt;
&lt;p&gt;事实上服务端安装成客户端就很简单了，只不过是玩法问题，这里只告诉如何使用&lt;/p&gt;
&lt;p&gt;下载windows包 解压到你的硬盘里&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/07.png&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;编辑 &lt;strong&gt;frpc.toml&lt;/strong&gt; 文件 ，并且配置简单映射下&lt;strong&gt;4321&lt;/strong&gt;端口到外网的&lt;strong&gt;2000&lt;/strong&gt;去&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[common]
server_addr = &quot;你的服务器ip&quot;
server_port = 7000
token = &quot;mowangmowang&quot;

[[proxies]]
name = &quot;web&quot;
type = &quot;tcp&quot;
local_ip = &quot;127.0.0.1&quot;
local_port  = 4321
remote_port  = 2000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行命令行命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;frpc -c frpc.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/08.png&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/09.png&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-17-frpinit/10.png&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;映射成功&lt;/p&gt;
&lt;h4&gt;相关资料&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://gofrp.org/zh-cn/docs/reference/server-configures/&quot;&gt;服务端配置详情 - gofrp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/fatedier/frp&quot;&gt;官方GITHUB - frp&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>[记录] React Native 搭建跑起第一个APP</title><link>https://blog.loli.wang/blog/2024-08-14-reactnativesetup/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-08-14-reactnativesetup/doc/</guid><description>React Native 搭建跑起第一个Demo</description><pubDate>Thu, 15 Aug 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;尝试了下 React Native 搭建跑起第一个Demo (第一次跑React Native是会比较花时间的，耐心去一步一步去解决就好)&lt;/p&gt;
&lt;h3&gt;准备工作&lt;/h3&gt;
&lt;p&gt;安装所需的工具&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.oracle.com/cn/java/technologies/downloads/#java17&quot;&gt;JDK 17&lt;/a&gt;  版本必须要大于等于17 和 小于等于20&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.google.cn/studio/install?hl=zh-cn&quot;&gt;Android Studio&lt;/a&gt; 最新版即可&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nodejs.org/en/download/&quot;&gt;Node.js&lt;/a&gt;  版本必须要大于等于18&lt;/p&gt;
&lt;p&gt;安卓模拟器 用android studio自带的，或者第三方的&lt;/p&gt;
&lt;h3&gt;创建React Native项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  npx react-native init AwesomeProject
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建立出来后的项目结构&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-14-reactNativeSetup/02.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;检查环境&lt;/h3&gt;
&lt;p&gt;中途必须配置好 JDK 和 Android SDK 和ADB 详情可以看中文网的文档&lt;/p&gt;
&lt;p&gt;[React Native 中文网] (https://reactnative.cn/docs/environment-setup)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx react-native doctor  # 检查环境
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;环境如果全部检查通过，可以开始启动项目了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-14-reactNativeSetup/07.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;启动项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#启动android
npx react-native run-android
#启动ios
npx react-native run-ios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-14-reactNativeSetup/08.png&quot; alt=&quot;08&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-14-reactNativeSetup/09.png&quot; alt=&quot;09&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;跑起这一个demo花了我一天的时间，我觉着这一天我可以做很多东西了，这里面各种编译，各种网络问题拉不下来依赖。编译看了下语法，还是和React有很大不同的，编译出来的代码是kotlin的，是调用的原生平台的组件，性能方面比Webview的性能要好很多，但是估计熟悉还是要话一点时间。&lt;/p&gt;
</content:encoded></item><item><title>快速更换npm镜像源的几种方法</title><link>https://blog.loli.wang/blog/2024-08-14-nrmcenter/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-08-14-nrmcenter/doc/</guid><description>快速更换npm镜像源的几种方法</description><pubDate>Wed, 14 Aug 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;在思考使用原生应用还是使用混合开发的时候，准备拿fullter和React Native做对比，发现官方文档有个推荐的切换镜像源的方式&lt;strong&gt;nrm&lt;/strong&gt; ，故此来记录一下。写下我一般是怎样切换镜像的 (想到什么记录什么)&lt;/p&gt;
&lt;h2&gt;常用镜像源&lt;/h2&gt;
&lt;p&gt;npm   https://registry.npmjs.org/       默认官方镜像&lt;/p&gt;
&lt;p&gt;yarn  https://registry.yarnpkg.com/       yarn官方镜像&lt;/p&gt;
&lt;p&gt;taobao &lt;s&gt;https://registry.npm.taobao.org/ 已废弃&lt;/s&gt;  https://registry.npmmirror.com/  taobao镜像&lt;/p&gt;
&lt;p&gt;tencen https://mirrors.cloud.tencent.com/npm/ tencent镜像&lt;/p&gt;
&lt;p&gt;cnpm  https://r.cnpmjs.org/               cnpm镜像&lt;/p&gt;
&lt;h2&gt;nrm 命令&lt;/h2&gt;
&lt;p&gt;nrm 命令是一个用来管理npm镜像的命令行工具，可以方便的切换镜像源，使用起来非常方便。&lt;/p&gt;
&lt;p&gt;安装nrm&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install -g nrm
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ nrm ls

* npm ---------- https://registry.npmjs.org/
  yarn --------- https://registry.yarnpkg.com/
  tencent ------ https://mirrors.cloud.tencent.com/npm/
  cnpm --------- https://r.cnpmjs.org/
  taobao ------- https://registry.npmmirror.com/
  npmMirror ---- https://skimdb.npmjs.com/registry/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;切换镜像源&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nrm use taobao # 切换为淘宝镜像
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加镜像源&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nrm add newNpm https://registry.npmjs.org/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;删除镜像源&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nrm del newNpm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更多命令参考官方文档 &lt;a href=&quot;https://www.npmjs.com/package/nrm&quot;&gt;nrm&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;.npmrc&lt;/h2&gt;
&lt;p&gt;.npmrc 是一个配置文件，用来配置npm的镜像源，可以通过修改这个文件来切换镜像源。&lt;/p&gt;
&lt;p&gt;创建一个 .npmrc 文件，并添加以下内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;registry=https://registry.npmmirror.com/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 .npmrc 文件，将 registry 的值改为你想要的镜像源。这样就可以切换镜像源了。&lt;/p&gt;
&lt;p&gt;或者使用指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm config set registry https://registry.npmmirror.com/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-14-nrmcenter/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>flutter安装到初始化搭建第一个Demo</title><link>https://blog.loli.wang/blog/2024-08-13-flutterappsetup1/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-08-13-flutterappsetup1/doc/</guid><description>flutter安装到初始化搭建第一个Demo</description><pubDate>Tue, 13 Aug 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;因为公司最近可能会APP开发的需求，并且手头工作已经接近尾声，身为一个前端，现在多端混合开发在国内是很常见的，首先第一时间考虑UNIAPP，但是在此之前给PDA设备开发了一套软件，性能给我的感觉极度糟糕，并且感觉问题杂乱差，文档甚至有时候还要去微信小程序开发者文档去找，给我的感觉是在写一个网页，没有写一个APP的体验， 所以我决定学习Flutter，以便用于下一个项目，作为一个充满实力的挑战者，我觉着我应该会很快就会熟悉。&lt;/p&gt;
&lt;h3&gt;编辑器推荐&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;VSCode (推荐)&lt;/li&gt;
&lt;li&gt;Android Studio&lt;/li&gt;
&lt;li&gt;IntelliJ IDEA&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;提前安装好的工具&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://gitforwindows.org/&quot;&gt;Git for Windows &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/studio/install#windows&quot;&gt;Android Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;安装Flutter的方式&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;手动下载Flutter SDK 本地安装&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;官方下载地址
https://docs.flutter.cn/get-started/install/windows/mobile/&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-13-flutterAPPSetup1/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;下载好后，将文件夹放到你的硬盘下，并且配置好环境变量，在cmd中输入flutter doctor，如果出现如下图所示，说明安装成功。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-13-flutterAPPSetup1/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Vscode 插件安装
插件名称：&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter&quot;&gt;Flutter&lt;/a&gt; ，去VSCode插件市场安装。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;VScode 按下快捷键 &lt;strong&gt;Ctrl + Shift + P&lt;/strong&gt; , 打开命令面板，输入 Flutter，选择 &lt;strong&gt;Flutter: New Project&lt;/strong&gt; ，等待检查完成后会自动生成项目。&lt;/p&gt;
&lt;h3&gt;境内镜像问题&lt;/h3&gt;
&lt;p&gt;修改maven.google.com 改为阿里的镜像&lt;/p&gt;
&lt;p&gt;进入  &lt;strong&gt;D:\flutter\packages\flutter_tools\lib\src&lt;/strong&gt; 找到 &lt;strong&gt;http_host_validator.dart&lt;/strong&gt; ,修改 kMaven 值为 &lt;strong&gt;https://maven.aliyun.com/repository/google&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const String kMaven = &apos;https://maven.aliyun.com/repository/google/&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-13-flutterAPPSetup1/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;配置环境变量&lt;/h2&gt;
&lt;p&gt;打开系统环境变量，新建环境变量，找到Path ，增加一条，你的flutter安装路径，例如：D:\flutter\bin&lt;/p&gt;
&lt;h2&gt;执行 flutter doctor -v 命令 ， 检测Flutter环境是否正常&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flutter doctor -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检测完成结果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-13-flutterAPPSetup1/04.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;创建项目&lt;/h2&gt;
&lt;p&gt;VScode 按下快捷键 &lt;strong&gt;Ctrl + Shift + P&lt;/strong&gt; , 打开命令面板，输入 Flutter，选择 &lt;strong&gt;Flutter: New Project&lt;/strong&gt; ，等待检查完成后会自动生成项目。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-13-flutterAPPSetup1/06.png&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;启动测试成功&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-13-flutterAPPSetup1/08.png&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-13-flutterAPPSetup1/07.png&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>让浏览器重新支持玩耍Flash游戏！！（Ruffle）</title><link>https://blog.loli.wang/blog/2024-08-09-rustgametools/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-08-09-rustgametools/doc/</guid><description>让浏览器重新支持玩耍Flash游戏！！（Ruffle） </description><pubDate>Sat, 10 Aug 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;平时就有去各种平台找各种大佬博客的习惯并学习一二，一次偶然的机会看到一个博客文章很有趣，《重拾 flash 小游戏，给博客用上 Ruffle》 - J.F’s Blog  ，因为从我的记忆里&lt;strong&gt;FLASH&lt;/strong&gt;是个淘汰过时并且淘汰的东西，前些年也开始各个大的浏览器都不再支持&lt;strong&gt;FLASH&lt;/strong&gt;这个技术了，并且现在想让浏览器重新运行&lt;strong&gt;FLASH&lt;/strong&gt;的动画网页也特别费劲。0看到这个插件让我折腾的心又恢复了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-09-rustgametools/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;介绍&lt;/h3&gt;
&lt;p&gt;著名的 Ruffle项目使用 Rust 语言将 flash 移植到了 Windows、Mac、Android 等主流操作系统，当然也包括基于 Javascript 的 web 环境。事实上，你可以直接在 web achieve 打开 flash🕹️，比如刚刚提到的拼命玩三郎的博客，因为时光机已经贴心地内嵌了 Ruffle 脚本。简单搜索会发现，目前众多资源大站都用它作为前端引擎，可见兼容性是不错的。作为最简单的方法之一，用它复活 swf 文件是再合适不过了。于是，花了点时间在博客下面增加了小游戏子页面，并且制作了简单的游戏列表，以及功能按钮，包括重载、本地上传、字体加载和全屏模式。默认 swf 是二进制时钟。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-09-rustgametools/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;使用方式&lt;/h3&gt;
&lt;h1&gt;如何在前端项目中使用 Ruffle.rs&lt;/h1&gt;
&lt;p&gt;Ruffle 是一个用 Rust 编写的 Flash Player 播放器，允许你在现代浏览器中运行 Flash 内容&lt;/p&gt;
&lt;h2&gt;1. 添加 Ruffle 到项目&lt;/h2&gt;
&lt;p&gt;你可以通过以下几种方式将 Ruffle 添加到你的前端项目中：&lt;/p&gt;
&lt;h3&gt;使用 CDN&lt;/h3&gt;
&lt;p&gt;你可以通过 CDN 直接加载 Ruffle 的 JavaScript 文件。将以下代码添加到你的 HTML 文件中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;https://unpkg.com/@ruffle-rs/ruffle&quot;&amp;gt;&amp;lt;/script&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用embed 或 object 加载资源&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
&amp;lt;object data=&quot;your-flash-file.swf&quot; width=&quot;800&quot; height=&quot;600&quot;&amp;gt;&amp;lt;/object&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;我的打算&lt;/h3&gt;
&lt;p&gt;我尝到甜头后，因为小时候对4399游戏也特别上瘾，正好最近也给自己做了个工具站模板，看来对这个也适用，准备用来做一个同年怀旧小游戏的网站。顺便找个机会记录游戏过程&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-09-rustgametools/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;文档相关&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://ruffle.rs&quot;&gt;ruffle官方文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.zzbd.org/&quot;&gt;J.F’s Blog&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>如何再Uniapp中使用Tailwindcss</title><link>https://blog.loli.wang/blog/2024-08-10-uniapptailwindcss/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-08-10-uniapptailwindcss/doc/</guid><description>如何再Uniapp中使用Tailwindcss </description><pubDate>Sat, 10 Aug 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;先使用npm 安装tailwindcss&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;pnpm add tailwindcss
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;命令创建 tailwind.config.js 配置文件 并修改&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npx tailwindcss init
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
/** @type {import(&apos;tailwindcss&apos;).Config} */
module.exports = {
  content: [&apos;./pages/**/*.{vue,js}&apos;, &apos;./App.vue&apos;],
  theme: {
    extend: {},
  },
  plugins: [],
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;根目录新建 tailwind-input.css&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* @tailwind base; */
@tailwind components;
@tailwind utilities;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;编写监听指令&lt;/h3&gt;
&lt;p&gt;编写监听指令，每次启动项目的时候启动，会自动编译你对css所做出的更改&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx tailwindcss -o ./static/tailwind.css --watch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写入到启动命令中，每次输入npm run css 进行启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &quot;scripts&quot;: {
    &quot;css&quot;: &quot;npx tailwindcss -o ./static/tailwind.css --watch&quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-10-uniapptailwindcss/01.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;APP.vue中 全局引用我们生成的css 即可&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-10-uniapptailwindcss/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;测试&lt;/h2&gt;
&lt;p&gt;我们给按钮让它进行向上面加上一个上边距&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-08-10-uniapptailwindcss/03.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成功&lt;/p&gt;
</content:encoded></item><item><title>(转载) 语义化版本 2.0.0</title><link>https://blog.loli.wang/blog/2024-07-18-semver/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-07-18-semver/doc/</guid><description>(转载) 语义化版本 2.0.0 </description><pubDate>Thu, 18 Jul 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h1&gt;语义化版本 2.0.0&lt;/h1&gt;
&lt;h2&gt;摘要&lt;/h2&gt;
&lt;p&gt;版本格式：主版本号.次版本号.修订号，版本号递增规则如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主版本号：当你做了不兼容的 API 修改，&lt;/li&gt;
&lt;li&gt;次版本号：当你做了向下兼容的功能性新增，&lt;/li&gt;
&lt;li&gt;修订号：当你做了向下兼容的问题修正。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面，作为延伸。&lt;/p&gt;
&lt;h2&gt;简介&lt;/h2&gt;
&lt;p&gt;在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷，系统规模越大，加入的包越多，你就越有可能在未来的某一天发现自己已深陷绝望之中。&lt;/p&gt;
&lt;p&gt;在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高，可能面临版本控制被锁死的风险（必须对每一个依赖包改版才能完成某次升级）。而如果依赖关系过于松散，又将无法避免版本的混乱（假设兼容于未来的多个版本已超出了合理数量）。当你项目的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠，就意味着你正处于依赖地狱之中。&lt;/p&gt;
&lt;p&gt;作为这个问题的解决方案之一，我提议用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据（但不局限于）已经被各种封闭、开放源码软件所广泛使用的惯例所设计。为了让这套理论运作，你必须先有定义好的公共 API。这可能包括文档或代码的强制要求。无论如何，这套 API 的清楚明了是十分重要的。一旦你定义了公共 API，你就可以透过修改相应的版本号来向大家说明你的修改。考虑使用这样的版本号格式：X.Y.Z（主版本号.次版本号.修订号）修复问题但不影响 API 时，递增修订号；API 保持向下兼容的新增及修改时，递增次版本号；进行不向下兼容的修改时，递增主版本号。&lt;/p&gt;
&lt;p&gt;我称这套系统为“语义化的版本控制”，在这套约定下，版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。&lt;/p&gt;
&lt;h2&gt;语义化版本控制规范（SemVer）&lt;/h2&gt;
&lt;p&gt;以下关键词 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、SHOULD NOT、 RECOMMENDED、MAY、OPTIONAL 依照 RFC 2119 的叙述解读。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用语义化版本控制的软件必须（MUST）定义公共 API。该 API 可以在代码中被定义或出现于严谨的文档内。无论何种形式都应该力求精确且完整。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;标准的版本号必须（MUST）采用 X.Y.Z 的格式，其中 X、Y 和 Z 为非负的整数，且禁止（MUST NOT）在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须（MUST）以数值来递增。例如：1.9.1 -&amp;gt; 1.10.0 -&amp;gt; 1.11.0。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;标记版本号的软件发行后，禁止（MUST NOT）改变该版本软件的内容。任何修改都必须（MUST）以新版本发行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;主版本号为零（0.y.z）的软件处于开发初始阶段，一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修订号 Z（x.y.Z &lt;code&gt;|&lt;/code&gt; x &amp;gt; 0）必须（MUST）在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;次版本号 Y（x.Y.z &lt;code&gt;|&lt;/code&gt; x &amp;gt; 0）必须（MUST）在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须（MUST）递增。也可以（MAY）在内部程序有大量新功能或改进被加入时递增，其中可以（MAY）包括修订级别的改变。每当次版本号递增时，修订号必须（MUST）归零。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;主版本号 X（X.y.z &lt;code&gt;|&lt;/code&gt; X &amp;gt; 0）必须（MUST）在有任何不兼容的修改被加入公共 API 时递增。其中可以（MAY）包括次版本号及修订级别的改变。每当主版本号递增时，次版本号和修订号必须（MUST）归零。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;先行版本号可以（MAY）被标注在修订版之后，先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须（MUST）由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成，且禁止（MUST NOT）留白。数字型的标识符禁止（MUST NOT）在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。范例：1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;版本编译信息可以（MAY）被标注在修订版或先行版本号之后，先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须（MUST）由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成，且禁止（MUST NOT）留白。当判断版本的优先层级时，版本编译信息可（SHOULD）被忽略。因此当两个版本只有在版本编译信息有差别时，属于相同的优先层级。范例：1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;版本的优先层级指的是不同版本在排序时如何比较。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;判断优先层级时，必须（MUST）把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较（版本编译信息不在这份比较的列表中）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;由左到右依序比较每个标识符，第一个差异值用来决定优先层级：主版本号、次版本号及修订号以数值比较。&lt;/p&gt;
&lt;p&gt;例如：1.0.0 &amp;lt; 2.0.0 &amp;lt; 2.1.0 &amp;lt; 2.1.1。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当主版本号、次版本号及修订号都相同时，改以优先层级比较低的先行版本号决定。&lt;/p&gt;
&lt;p&gt;例如：1.0.0-alpha &amp;lt; 1.0.0。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;有相同主版本号、次版本号及修订号的两个先行版本号，其优先层级必须（MUST）透过由左到右的每个被句点分隔的标识符来比较，直到找到一个差异值后决定：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;只有数字的标识符以数值高低比较。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;有字母或连接号时则逐字以 ASCII 的排序来比较。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数字的标识符比非数字的标识符优先层级低。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若开头的标识符都相同时，栏位比较多的先行版本号优先层级比较高。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例如：1.0.0-alpha &amp;lt; 1.0.0-alpha.1 &amp;lt; 1.0.0-alpha.beta &amp;lt; 1.0.0-beta &amp;lt; 1.0.0-beta.2 &amp;lt; 1.0.0-beta.11 &amp;lt; 1.0.0-rc.1 &amp;lt; 1.0.0。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;合法语义化版本的巴科斯范式语法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;valid semver&amp;gt; ::= &amp;lt;version core&amp;gt;
                 | &amp;lt;version core&amp;gt; &quot;-&quot; &amp;lt;pre-release&amp;gt;
                 | &amp;lt;version core&amp;gt; &quot;+&quot; &amp;lt;build&amp;gt;
                 | &amp;lt;version core&amp;gt; &quot;-&quot; &amp;lt;pre-release&amp;gt; &quot;+&quot; &amp;lt;build&amp;gt;

&amp;lt;version core&amp;gt; ::= &amp;lt;major&amp;gt; &quot;.&quot; &amp;lt;minor&amp;gt; &quot;.&quot; &amp;lt;patch&amp;gt;

&amp;lt;major&amp;gt; ::= &amp;lt;numeric identifier&amp;gt;

&amp;lt;minor&amp;gt; ::= &amp;lt;numeric identifier&amp;gt;

&amp;lt;patch&amp;gt; ::= &amp;lt;numeric identifier&amp;gt;

&amp;lt;pre-release&amp;gt; ::= &amp;lt;dot-separated pre-release identifiers&amp;gt;

&amp;lt;dot-separated pre-release identifiers&amp;gt; ::= &amp;lt;pre-release identifier&amp;gt;
                                          | &amp;lt;pre-release identifier&amp;gt; &quot;.&quot; &amp;lt;dot-separated pre-release identifiers&amp;gt;

&amp;lt;build&amp;gt; ::= &amp;lt;dot-separated build identifiers&amp;gt;

&amp;lt;dot-separated build identifiers&amp;gt; ::= &amp;lt;build identifier&amp;gt;
                                    | &amp;lt;build identifier&amp;gt; &quot;.&quot; &amp;lt;dot-separated build identifiers&amp;gt;

&amp;lt;pre-release identifier&amp;gt; ::= &amp;lt;alphanumeric identifier&amp;gt;
                           | &amp;lt;numeric identifier&amp;gt;

&amp;lt;build identifier&amp;gt; ::= &amp;lt;alphanumeric identifier&amp;gt;
                     | &amp;lt;digits&amp;gt;

&amp;lt;alphanumeric identifier&amp;gt; ::= &amp;lt;non-digit&amp;gt;
                            | &amp;lt;non-digit&amp;gt; &amp;lt;identifier characters&amp;gt;
                            | &amp;lt;identifier characters&amp;gt; &amp;lt;non-digit&amp;gt;
                            | &amp;lt;identifier characters&amp;gt; &amp;lt;non-digit&amp;gt; &amp;lt;identifier characters&amp;gt;

&amp;lt;numeric identifier&amp;gt; ::= &quot;0&quot;
                       | &amp;lt;positive digit&amp;gt;
                       | &amp;lt;positive digit&amp;gt; &amp;lt;digits&amp;gt;

&amp;lt;identifier characters&amp;gt; ::= &amp;lt;identifier character&amp;gt;
                          | &amp;lt;identifier character&amp;gt; &amp;lt;identifier characters&amp;gt;

&amp;lt;identifier character&amp;gt; ::= &amp;lt;digit&amp;gt;
                         | &amp;lt;non-digit&amp;gt;

&amp;lt;non-digit&amp;gt; ::= &amp;lt;letter&amp;gt;
              | &quot;-&quot;

&amp;lt;digits&amp;gt; ::= &amp;lt;digit&amp;gt;
           | &amp;lt;digit&amp;gt; &amp;lt;digits&amp;gt;

&amp;lt;digit&amp;gt; ::= &quot;0&quot;
          | &amp;lt;positive digit&amp;gt;

&amp;lt;positive digit&amp;gt; ::= &quot;1&quot; | &quot;2&quot; | &quot;3&quot; | &quot;4&quot; | &quot;5&quot; | &quot;6&quot; | &quot;7&quot; | &quot;8&quot; | &quot;9&quot;

&amp;lt;letter&amp;gt; ::= &quot;A&quot; | &quot;B&quot; | &quot;C&quot; | &quot;D&quot; | &quot;E&quot; | &quot;F&quot; | &quot;G&quot; | &quot;H&quot; | &quot;I&quot; | &quot;J&quot;
           | &quot;K&quot; | &quot;L&quot; | &quot;M&quot; | &quot;N&quot; | &quot;O&quot; | &quot;P&quot; | &quot;Q&quot; | &quot;R&quot; | &quot;S&quot; | &quot;T&quot;
           | &quot;U&quot; | &quot;V&quot; | &quot;W&quot; | &quot;X&quot; | &quot;Y&quot; | &quot;Z&quot; | &quot;a&quot; | &quot;b&quot; | &quot;c&quot; | &quot;d&quot;
           | &quot;e&quot; | &quot;f&quot; | &quot;g&quot; | &quot;h&quot; | &quot;i&quot; | &quot;j&quot; | &quot;k&quot; | &quot;l&quot; | &quot;m&quot; | &quot;n&quot;
           | &quot;o&quot; | &quot;p&quot; | &quot;q&quot; | &quot;r&quot; | &quot;s&quot; | &quot;t&quot; | &quot;u&quot; | &quot;v&quot; | &quot;w&quot; | &quot;x&quot;
           | &quot;y&quot; | &quot;z&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;为什么要使用语义化的版本控制？&lt;/h2&gt;
&lt;p&gt;这并不是一个新的或者革命性的想法。实际上，你可能已经在做一些近似的事情了。问题在于只是“近似”还不够。如果没有某个正式的规范可循，版本号对于依赖的管理并无实质意义。将上述的想法命名并给予清楚的定义，让你对软件使用者传达意向变得容易。一旦这些意向变得清楚，弹性（但又不会太弹性）的依赖规范就能达成。&lt;/p&gt;
&lt;p&gt;举个简单的例子就可以展示语义化的版本控制如何让依赖地狱成为过去。假设有个名为“救火车”的函数库，它需要另一个名为“梯子”并已经有使用语义化版本控制的包。当救火车创建时，梯子的版本号为 3.1.0。因为救火车使用了一些版本 3.1.0 所新增的功能，你可以放心地指定依赖于梯子的版本号大于等于 3.1.0 但小于 4.0.0。这样，当梯子版本 3.1.1 和 3.2.0 发布时，你可以将直接它们纳入你的包管理系统，因为它们能与原有依赖的软件兼容。&lt;/p&gt;
&lt;p&gt;作为一位负责任的开发者，你理当确保每次包升级的运作与版本号的表述一致。现实世界是复杂的，我们除了提高警觉外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包，而无需推出新的依赖包，节省你的时间及烦恼。&lt;/p&gt;
&lt;p&gt;如果你对此认同，希望立即开始使用语义化版本控制，你只需声明你的函数库正在使用它并遵循这些规则就可以了。请在你的 README 文件中保留此页链接，让别人也知道这些规则并从中受益。&lt;/p&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;在 0.y.z 初始开发阶段，我该如何进行版本控制？&lt;/h3&gt;
&lt;p&gt;最简单的做法是以 0.1.0 作为你的初始化开发版本，并在后续的每次发行时递增次版本号。&lt;/p&gt;
&lt;h3&gt;如何判断发布 1.0.0 版本的时机？&lt;/h3&gt;
&lt;p&gt;当你的软件被用于正式环境，它应该已经达到了 1.0.0 版。如果你已经有个稳定的 API 被使用者依赖，也会是 1.0.0 版。如果你很担心向下兼容的问题，也应该算是 1.0.0 版了。&lt;/p&gt;
&lt;h3&gt;这不会阻碍快速开发和迭代吗？&lt;/h3&gt;
&lt;p&gt;主版本号为零的时候就是为了做快速开发。如果你每天都在改变 API，那么你应该仍在主版本号为零的阶段（0.y.z），或是正在下个主版本的独立开发分支中。&lt;/p&gt;
&lt;h3&gt;对于公共 API，若即使是最小但不向下兼容的改变都需要产生新的主版本号，岂不是很快就达到 42.0.0 版？&lt;/h3&gt;
&lt;p&gt;这是开发的责任感和前瞻性的问题。不兼容的改变不应该轻易被加入到有许多依赖代码的软件中。升级所付出的代价可能是巨大的。要递增主版本号来发行不兼容的改版，意味着你必须为这些改变所带来的影响深思熟虑，并且评估所涉及的成本及效益比。&lt;/p&gt;
&lt;h3&gt;为整个公共 API 写文档太费事了！&lt;/h3&gt;
&lt;p&gt;为供他人使用的软件编写适当的文档，是你作为一名专业开发者应尽的职责。保持项目高效的一个非常重要的部分是掌控软件的复杂度，如果没有人知道如何使用你的软件或不知道哪些函数的调用是可靠的，要掌控复杂度会是困难的。长远来看，使用语义化版本控制以及对于公共 API 有良好规范的坚持，可以让每个人及每件事都运行顺畅。&lt;/p&gt;
&lt;h3&gt;万一不小心把一个不兼容的改版当成了次版本号发行了该怎么办？&lt;/h3&gt;
&lt;p&gt;一旦发现自己破坏了语义化版本控制的规范，就要修正这个问题，并发行一个新的次版本号来更正这个问题并且恢复向下兼容。即使是这种情况，也不能去修改已发行的版本。可以的话，将有问题的版本号记录到文档中，告诉使用者问题所在，让他们能够意识到这是有问题的版本。&lt;/p&gt;
&lt;h3&gt;如果我更新了自己的依赖但没有改变公共 API 该怎么办？&lt;/h3&gt;
&lt;p&gt;由于没有影响到公共 API，这可以被认定是兼容的。若某个软件和你的包有共同依赖，则它会有自己的依赖规范，作者也会告知可能的冲突。要判断改版是属于修订等级或是次版等级，是依据你更新的依赖关系是为了修复问题或是加入新功能。对于后者，我经常会预期伴随着更多的代码，这显然会是一个次版本号级别的递增。&lt;/p&gt;
&lt;h3&gt;如果我变更了公共 API 但无意中未遵循版本号的改动怎么办呢？（意即在修订等级的发布中，误将重大且不兼容的改变加到代码之中）&lt;/h3&gt;
&lt;p&gt;自行做最佳的判断。如果你有庞大的使用者群在依照公共 API 的意图而变更行为后会大受影响，那么最好做一次主版本的发布，即使严格来说这个修复仅是修订等级的发布。记住， 语义化的版本控制就是透过版本号的改变来传达意义。若这些改变对你的使用者是重要的，那就透过版本号来向他们说明。&lt;/p&gt;
&lt;h3&gt;我该如何处理即将弃用的功能？&lt;/h3&gt;
&lt;p&gt;弃用现存的功能是软件开发中的家常便饭，也通常是向前发展所必须的。当你弃用部分公共 API 时，你应该做两件事：（1）更新你的文档让使用者知道这个改变，（2）在适当的时机将弃用的功能透过新的次版本号发布。在新的主版本完全移除弃用功能前，至少要有一个次版本包含这个弃用信息，这样使用者才能平顺地转移到新版 API。&lt;/p&gt;
&lt;h3&gt;语义化版本对于版本的字符串长度是否有限制呢？&lt;/h3&gt;
&lt;p&gt;没有，请自行做适当的判断。举例来说，长到 255 个字符的版本已过度夸张。再者，特定的系统对于字符串长度可能会有他们自己的限制。&lt;/p&gt;
&lt;h3&gt;“v1.2.3” 是一个语义化版本号吗？&lt;/h3&gt;
&lt;p&gt;“v1.2.3” 并不是一个语义化的版本号。但是，在语义化版本号之前增加前缀 “v” 是用来表示版本号的常用做法。在版本控制系统中，将 “version” 缩写为 “v” 是很常见的。比如：&lt;code&gt;git tag v1.2.3 -m &quot;Release version 1.2.3&quot;&lt;/code&gt; 中，“v1.2.3” 表示标签名称，而 “1.2.3” 是语义化版本号。&lt;/p&gt;
&lt;h3&gt;是否有推荐的正则表达式用以检查语义化版本号的正确性？&lt;/h3&gt;
&lt;p&gt;有两个推荐的正则表达式。第一个用于支持按组名称提取的语言（PCRE[Perl 兼容正则表达式，比如 Perl、PHP 和 R]、Python 和 Go）。&lt;/p&gt;
&lt;p&gt;参见：&lt;a href=&quot;https://regex101.com/r/Ly7O1x/3/&quot;&gt;https://regex101.com/r/Ly7O1x/3/&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;^(?P&amp;lt;major&amp;gt;0|[1-9]\d*)\.(?P&amp;lt;minor&amp;gt;0|[1-9]\d*)\.(?P&amp;lt;patch&amp;gt;0|[1-9]\d*)(?:-(?P&amp;lt;prerelease&amp;gt;(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P&amp;lt;buildmetadata&amp;gt;[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二个用于支持按编号提取的语言（与第一个对应的提取项按顺序分别为：major、minor、patch、prerelease、buildmetadata）。主要包括 ECMA Script（JavaScript）、PCRE（Perl 兼容正则表达式，比如 Perl、PHP 和 R）、Python 和 Go。
参见：&lt;a href=&quot;https://regex101.com/r/vkijKf/1/&quot;&gt;https://regex101.com/r/vkijKf/1/&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;关于&lt;/h2&gt;
&lt;p&gt;语义化版本控制的规范是由 Gravatars 创办者兼 GitHub 共同创办者 &lt;a href=&quot;http://tom.preston-werner.com&quot;&gt;Tom Preston-Werner&lt;/a&gt; 所建立。&lt;/p&gt;
&lt;p&gt;如果您有任何建议，请到 &lt;a href=&quot;https://github.com/semver/semver/issues&quot;&gt;GitHub 上提出您的问题&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;许可证&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://creativecommons.org/licenses/by/3.0/&quot;&gt;知识共享 署名 3.0 (CC BY 3.0)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://semver.org/lang/zh-CN/&quot;&gt;原文链接&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>重写自己脚手架工具之旅</title><link>https://blog.loli.wang/blog/2024-07-13-clicreate2/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-07-13-clicreate2/doc/</guid><description>重写自己脚手架工具之旅</description><pubDate>Sun, 14 Jul 2024 15:27:25 GMT</pubDate><content:encoded>&lt;p&gt;回首往昔，无趣的过往。没有任何精彩的事情发生，只有空的躯壳像NPC一样再运作。上一次写我的脚手架工具已经是一年前了，当时很多不成熟的思维也慢慢得到了改正，所以决定重新写一份了，毕竟每年都要好好的检查下自己嘛&lt;/p&gt;
&lt;h2&gt;必备项目依赖&lt;/h2&gt;
&lt;p&gt;-- kolorist 控制台命令行操控颜色的脚本库
-- minimist 参数解析器 类似 mw create --template 这种
-- prompts 一个提示用户选择的库
-- unbuild 构建工具&lt;/p&gt;
&lt;h2&gt;开始编写&lt;/h2&gt;
&lt;p&gt;安装我们所需要使用到的依赖&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add kolorist minimist prompts unbuild typescript -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化&lt;strong&gt;tsconfig.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES2022&quot;,
    &quot;module&quot;: &quot;ES2020&quot;,
    &quot;outDir&quot;: &quot;lib&quot;,
    &quot;moduleResolution&quot;: &quot;bundler&quot;,
    &quot;strict&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;declaration&quot;: false,
    &quot;sourceMap&quot;: false,
    &quot;noUnusedLocals&quot;: true,
    &quot;esModuleInterop&quot;: true
  },
  &quot;exclude&quot;: [&quot;template&quot;, &quot;lib&quot;]
}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;整理合理的项目目录
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-13-cliCreate2/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;编写 unbuild 的配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// build.config.ts

import { defineBuildConfig } from &apos;unbuild&apos;

export default defineBuildConfig({
  entries: [
    &quot;./src/index&quot;,
  ],
  outDir: &quot;lib&quot;,
  declaration: true,
});

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改package.json 修改Bin软链接指令，编写unbuild的本地打包测试指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;create-mw&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;type&quot;: &quot;module&quot;,
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;npx  unbuild --stub&quot;
  },
  &quot;bin&quot;: {
    &quot;cre&quot;:&quot;index.js&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;engines&quot;: {
    &quot;node&quot;: &quot;^18.0.0 || &amp;gt;=20.0.0&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@types/minimist&quot;: &quot;^1.2.5&quot;,
    &quot;@types/node&quot;: &quot;^20.14.10&quot;,
    &quot;kolorist&quot;: &quot;^1.8.0&quot;,
    &quot;minimist&quot;: &quot;^1.2.8&quot;,
    &quot;prompts&quot;: &quot;^2.4.2&quot;,
    &quot;tsx&quot;: &quot;^4.16.2&quot;,
    &quot;typescript&quot;: &quot;^5.5.3&quot;,
    &quot;unbuild&quot;: &quot;^2.0.0&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改&lt;strong&gt;index.js&lt;/strong&gt; 入口文件代码让他指向我们打包出来的js文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env node

import &apos;./lib/index.mjs&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打包后，&lt;strong&gt;npm link&lt;/strong&gt; 后测试下是否正常，执行命令cre 看看是否正常&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-13-cliCreate2/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后我们使用&lt;strong&gt;minimist&lt;/strong&gt; 来进行一个参数解析。 解析后让他成为能够获取 cre --template 获取这种在命令里的参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 参数解析器
const argv = minimist(process.argv.slice(2), {})

console.log(argv);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-13-cliCreate2/04.png&quot; alt=&quot;2&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-13-cliCreate2/03.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import minimist from &quot;minimist&quot;;
import prompts from &quot;prompts&quot;;
import {
  blue,
  cyan,
  green,
  lightBlue,
  lightGreen,
  lightRed,
  magenta,
  red,
  reset,
  yellow,
} from &quot;kolorist&quot;;

// 参数解析器 方便解析我们需要的
const argv = minimist(process.argv.slice(2), {});

console.log(argv);

//  获取项目路径
const cwd = process.cwd();

console.log(cwd);

// prettier-ignore
const helpMessage = `\
Usage: create-vite [OPTION]... [DIRECTORY]

Create a new Vite project in JavaScript or TypeScript.
With no arguments, start the CLI in interactive mode.

Options:
  -t, --template NAME        use a specific template

Available templates:
${yellow   (&apos;vanilla-ts     vanilla&apos;  )}
${green    (&apos;vue-ts         vue&apos;      )}
${cyan     (&apos;react-ts       react&apos;    )}
${cyan     (&apos;react-swc-ts   react-swc&apos;)}
${magenta  (&apos;preact-ts      preact&apos;   )}
${lightRed (&apos;lit-ts         lit&apos;      )}
${red      (&apos;svelte-ts      svelte&apos;   )}
${blue     (&apos;solid-ts       solid&apos;    )}
${lightBlue(&apos;qwik-ts        qwik&apos;     )}`


const FRAMEWORKS = [
  {
    name: &quot;vanilla&quot;,
    display: &quot;Vanilla&quot;,
    color: yellow,
    variants: [
      {
        name: &quot;vanilla-ts&quot;,
        display: &quot;TypeScript&quot;,
        color: blue,
      },
      {
        name: &quot;vanilla&quot;,
        display: &quot;JavaScript&quot;,
        color: yellow,
      },
    ],
  },
  {
    name: &quot;solid&quot;,
    display: &quot;Solid&quot;,
    color: blue,
    variants: [
      {
        name: &quot;solid-ts&quot;,
        display: &quot;TypeScript&quot;,
        color: blue,
      },
      {
        name: &quot;solid&quot;,
        display: &quot;JavaScript&quot;,
        color: yellow,
      },
    ],
  },
];

// 将 FRAMEWORKS 中存在的variants转换成一维数组 并转换成一维数组
const TEMPLATES = FRAMEWORKS.map(
  (item) =&amp;gt; (item.variants &amp;amp;&amp;amp; item.variants.map((v) =&amp;gt; v.name)) || [item.name]
).flat(Infinity);

// 默认文件名
const defaultTargetDir = &quot;cre-Project&quot;;

// 初始化函数
const init = async () =&amp;gt; {
  try {
    // prompts
    const result = await prompts([
      {
        type: &quot;text&quot;,
        name: &quot;projectName&quot;,
        message: &quot;Project name:&quot;,
        initial: defaultTargetDir,
      },
      {
        type: &quot;select&quot;,
        name: &quot;overwrite&quot;,
        message: &quot;Current directory&quot;,
        initial: 0,
        choices: [
          {
            title: &apos;Remove existing files and continue&apos;,
            value: &apos;yes&apos;,
          },
          {
            title: &apos;Cancel operation&apos;,
            value: &apos;no&apos;,
          },
          {
            title: &apos;Ignore files and continue&apos;,
            value: &apos;ignore&apos;,
          },
        ],
      }
    ]);

    console.log(result);
  } catch (error) {
    console.log(error);
  }
};

init();

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没写完 临时有别的东西要写，下次更新。。。。&lt;/p&gt;
</content:encoded></item><item><title>最简单运行TypeScript 的方法 esno 和 tsx</title><link>https://blog.loli.wang/blog/2024-07-13-esnoandtsx/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-07-13-esnoandtsx/doc/</guid><description>最简单运行TypeScript 的方法esno 和 tsx </description><pubDate>Sat, 13 Jul 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;在现在很多项目中都使用ts来做为编程语言，而如果&lt;strong&gt;没有编译器的支持下&lt;/strong&gt;执行ts会需要&lt;strong&gt;tsc&lt;/strong&gt;或者&lt;strong&gt;ts-node&lt;/strong&gt;这个库来进行编译，然后&lt;strong&gt;nodejs&lt;/strong&gt;去执行我们的ts文件，会影响工作效率和带来心智负担，困扰的我无意中发现了个项目 &lt;strong&gt;tsx&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;先做下对比吧&lt;/h2&gt;
&lt;p&gt;在没有使用tsx的情况下，我们编写ts项目想执行是需要花费很大的代价的。&lt;/p&gt;
&lt;p&gt;我们编写好相关的代码后 需要先 &lt;strong&gt;tsc&lt;/strong&gt; 进行编译，编译成js后通过node去执行js文件能够运行我们的ts项目代码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-13-esnoAndtsx/02.png&quot; alt=&quot;1&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-13-esnoAndtsx/03.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;如果使用Tsx&lt;/h3&gt;
&lt;p&gt;可以看到，如果我们使用tsc进行编译后再去运行js文件，他其实就是一个很繁琐的步骤。而&lt;strong&gt;tsx&lt;/strong&gt;可以方便的帮我们解决这个问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装tsx
pnpm add tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装好tsx后，使用tsx命令去运行我们的ts代码，可以看到不需要任何的编译，就能直接处理我们的ts代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# tsx运行指令
npx tsx test1.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-13-esnoAndtsx/04.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;esno&lt;/h3&gt;
&lt;p&gt;esno和tsx是一样的，不过是tsx的别名，antfu旗下的库，我看不出区别。他介绍也是这么说的&lt;/p&gt;
&lt;h3&gt;文档&lt;/h3&gt;
&lt;p&gt;当然还有ts-node 也是可以做到我上面同样的事情，但是esno和tsx能够更好的支持es模块，并且是基于esbuild开发的，速度也比ts-node快了一个台阶&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tsx.is/&quot;&gt;tsx 文档&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>记录一次修复 个人记录Blog 的 RSS </title><link>https://blog.loli.wang/blog/2024-07-08-jilurss/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-07-08-jilurss/doc/</guid><description>记录一次修复 个人记录Blog 的 RSS </description><pubDate>Mon, 08 Jul 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;为什么要使用 RSS ?&lt;/h3&gt;
&lt;p&gt;RSS是一种用于发布经常更新的内容的网站的一种数据格式。通过RSS，网站可以将最新的文章、新闻、博客等内容以统一的格式提供给用户。而用户则可以通过RSS订阅这些内容，无需再次访问网站，便能够及时获取更新。&lt;/p&gt;
&lt;p&gt;RSS的优点：
1. 更新及时：RSS订阅可以实时获取最新的内容，用户无需手动刷新页面，从而提高了用户体验。
2. 离线阅读：RSS订阅可以离线阅读，用户可以在没有网络连接的情况下查看订阅的RSS内容。
3. 聚合阅读：RSS订阅可以聚合多个来源的RSS内容，用户可以查看来自不同网站的内容。&lt;/p&gt;
&lt;h3&gt;起因&lt;/h3&gt;
&lt;p&gt;因为之前重新基于沉冰的项目升级除了故障,所以紧急自己写了一版.结果对RSS这部分有遗漏,这次就正好重新补上吧&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-08-jiluRSS/01.png&quot; alt=&quot;1&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-08-jiluRSS/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;安装 Astro RSS 插件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pnpm add @astrojs/rss
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置 RSS 插件&lt;/h3&gt;
&lt;p&gt;装载RSS 插件完成后,去修改astro的配置文件，配置文件的site属性一定要有正确配置。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-08-jiluRSS/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后再pages目录下新建一个 &lt;strong&gt;rss.xml.ts&lt;/strong&gt; 文件，然后写上如下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import rss from &apos;@astrojs/rss&apos;;

export function GET(context) {
  return rss({
    // 输出的 xml 中的`&amp;lt;title&amp;gt;`字段
    title: &apos;Buzz’s Blog&apos;,
    // 输出的 xml 中的`&amp;lt;description&amp;gt;`字段
    description: &apos;A humble Astronaut’s guide to the stars&apos;,
    // 从端点上下文获取项目“site”
    // https://docs.astro.build/zh-cn/reference/api-reference/#contextsite
    site: context.site,
    // 输出的 xml 中的`&amp;lt;item&amp;gt;`数组
    // 有关使用内容集合和 glob 导入的示例，请参阅“生成`items`”部分
    items: [],
    // (可选) 注入自定义 xml
    customData: `&amp;lt;language&amp;gt;en-us&amp;lt;/language&amp;gt;`,
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 /src/config.ts 中添加以下内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineCollection } from &apos;astro:content&apos;;
import { rssSchema } from &apos;@astrojs/rss&apos;;

const blog = defineCollection({
  schema: rssSchema,
});

export const collections = { blog };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-08-jiluRSS/04.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;接下来我们启动下本地项目,访问url/rss.xml,就可以看到rss文件了。&lt;/p&gt;
&lt;p&gt;接下来进行让展示的内容也出现在rss中&lt;/p&gt;
&lt;p&gt;修改&lt;strong&gt;rss.xml.ts&lt;/strong&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import rss from &apos;@astrojs/rss&apos;;
import { config_global,config_fun } from &quot;@/theme-simple/config&quot;;
import { getCollection } from &apos;astro:content&apos;;
import sanitizeHtml from &apos;sanitize-html&apos;;
import MarkdownIt from &apos;markdown-it&apos;;
const parser = new MarkdownIt();
export async function GET(context: { site: any; }) {
    const {title,description} = config_global;
    const { sortPosts  } = config_fun;
    const blog = await getCollection(&apos;blog&apos;);
    return rss({
        // 输出的 xml 中的`&amp;lt;title&amp;gt;`字段
          title, 
        // 输出的 xml 中的`&amp;lt;description&amp;gt;`字段
        description,
        // 从端点上下文获取项目“site”
        site: context.site,
        // 输出的 xml 中的`&amp;lt;item&amp;gt;`数组
        // 有关使用内容集合和 glob 导入的示例，请参阅“生成`items`”部分
        items:blog.sort(sortPosts).map((post) =&amp;gt; ({
            link: `/blog/${post.slug}/`,
            // 注意：这不会处理 MDX 文件中的组件或 JSX 表达式。
            content: sanitizeHtml(parser.render(post.body), {
              allowedTags: sanitizeHtml.defaults.allowedTags.concat([&apos;img&apos;])
            }),
            ...post.data,
          })),
        
        // (可选) 注入自定义 xml
        customData: `&amp;lt;language&amp;gt;en-us&amp;lt;/language&amp;gt;`,
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-08-jiluRSS/05.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;使用RSS Feeder 订阅成功&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-08-jiluRSS/06.png&quot; alt=&quot;5&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-08-jiluRSS/07.png&quot; alt=&quot;5&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-08-jiluRSS/08.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>使用 Prisma 配合 Cloudflare D1 构建应用</title><link>https://blog.loli.wang/blog/2024-07-06-cfd1prisma/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-07-06-cfd1prisma/doc/</guid><description>使用 Prisma 配合 Cloudflare D1 构建应用</description><pubDate>Sat, 06 Jul 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;Cloudflare Workers 是一种分布在全球范围内的轻量级无服务器计算形式。它们允许您尽可能靠近最终用户部署和运行应用程序 ,&lt;/p&gt;
&lt;p&gt;D1 是 Cloudflare 的原生无服务器数据库。基于 SQLite，可在通过 Cloudflare 部署应用程序时使用&lt;/p&gt;
&lt;p&gt;Prisma  数据库ORM框架。&lt;/p&gt;
&lt;h2&gt;开始前准备&lt;/h2&gt;
&lt;p&gt;提前建立一个空的前端node项目。&lt;/p&gt;
&lt;p&gt;node版本大于18+&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;创建 CloudFlare Worker&lt;/h2&gt;
&lt;p&gt;在使用cloudFlare D1 之前先需要安装 &lt;strong&gt;Wrangler&lt;/strong&gt; 并登录CloudFlare账户,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; #npm 
 npm install wrangler --save-dev 
 #pnpm
 pnpm add wrangler --save-dev 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装好 &lt;strong&gt;Wrangler&lt;/strong&gt; 后, 使用指令登录我们的cloudflare账户&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx wrangler login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在仪表盘建立我们 worker ，这个我是我目前建立的worker，然后我们在项目文件下编写我们的wrangler的配置文件
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;创建文件 wrangler.toml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# wrangler.toml
name = &quot;d1-prisma&quot; ## 指向我们的worker项目名称
main = &quot;src/index.ts&quot; ## 指定启动入口文件
compatibility_date = &quot;2024-07-01&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在src/目录下建立个index.ts , 写个测试的demo&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
export default {
  async fetch(request) {
    const data = {
      hello: &quot;hello world Mw!&quot;,
    };

    return Response.json(data);
  },
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在package.json中编写发布脚本,并启动本地测试和发布到cloudFlare worker中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;dev&quot;:&quot;npx wrangler dev&quot;,
  &quot;deploy&quot;: &quot;npx wrangler deploy&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，本地测试是成功的，&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/04.png&quot; alt=&quot;4&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/05.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发布到worker , 外网也可以正常访问
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/06.png&quot; alt=&quot;6&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/07.png&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;初始化 Prisma ORM&lt;/h2&gt;
&lt;p&gt;刚刚那一步我们worker已经正常了。现在我们开始初始化ORM工具&lt;/p&gt;
&lt;p&gt;详细看这篇文章 &lt;a href=&quot;https://www.prisma.io/docs/orm/overview/databases/cloudflare-d1#what-is-cloudflare-d1&quot;&gt;cloudFlare D1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;安装prisma依赖和对d1的支持&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pnpm
pnpm add prisma --save-dev
pnpm add @prisma/client @prisma/adapter-d1

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编写 prisma 配置和数据进行测试 ,创建文件 &lt;strong&gt;/prsima/schema.prisma&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//  ./schema.prisma

generator client {
  provider = &quot;prisma-client-js&quot;
}

datasource db {
  provider = &quot;sqlite&quot;
  url      = &quot;file:./dev.db&quot;
}


// 测试写的用户表
model User {
  id       Int     @id @default(autoincrement()) // 用户唯一ID
  email    String  @unique // 用户的联系邮箱 
  name     String? // 用户的名称
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用指令初始化prisma的数据库，初始化成功后会看到本地有db文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx prisma migrate dev --name init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建立测试创建和查询sql的测试文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ./prisma/test.ts
import { PrismaClient } from &apos;@prisma/client&apos;

const prisma = new PrismaClient()

async function main() {
  const users = await prisma.user.findMany()
  console.log(users)
}


main()
  .then(async () =&amp;gt; {
    await prisma.$disconnect()
  })
  .catch(async (e) =&amp;gt; {
    console.error(e)
    await prisma.$disconnect()
    process.exit(1)
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// ./prisma/testCreate.ts
import { PrismaClient } from &apos;@prisma/client&apos;

const prisma = new PrismaClient()

async function main() {
  const user = await prisma.user.create({
    data: {
      name: &apos;Bob&apos;,
      email: &apos;bob@prisma.io&apos;,
    },
  })
  console.log(user)
}

main()
  .then(async () =&amp;gt; {
    await prisma.$disconnect()
  })
  .catch(async (e) =&amp;gt; {
    console.error(e)
    await prisma.$disconnect()
    process.exit(1)
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编写新的指令 方便测试创建和查看数据库数据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
   &quot;test:select&quot;:&quot;npx tsx ./prisma/test.ts&quot;,
   &quot;test:create&quot;:&quot;npx tsx ./prisma/testCreate.ts&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/08.png&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;创建新数据
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/09.png&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查看所有数据
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/10.png&quot; alt=&quot;10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;到目前为止我们Prisma简单配置好了。&lt;/p&gt;
&lt;h2&gt;创建D1 数据库&lt;/h2&gt;
&lt;p&gt;在我们目前例子中都是通过仪表盘创建的worker项目，创建D1我们也通过仪表盘去创建。当然你也可以选择使用cli或者其他方式去创建
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/11.png&quot; alt=&quot;11&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/12.png&quot; alt=&quot;12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;创建好D1数据库后，修改 &lt;strong&gt;wrangler.toml&lt;/strong&gt;配置文件,对应你自己的D1配置就好。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
[[d1_databases]]
binding = &quot;DB&quot;  
database_name = &quot;prisma_d1&quot;
database_id = &quot;你自己的D1的id&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/13.png&quot; alt=&quot;13&quot; /&gt;&lt;/p&gt;
&lt;p&gt;编写生成迁移文件指令和diff指令到package.json&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;transfer&quot;:&quot;npx wrangler d1 migrations create prisma_d1 create_user_table&quot;,
    &quot;diff&quot;: &quot;npx prisma migrate diff --from-empty --to-schema-datamodel ./prisma/schema.prisma --script &amp;gt; migrations/0001_create_user_table.sql&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后会生成 迁移文件的配置和sql文件
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/14.png&quot; alt=&quot;14&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/15.png&quot; alt=&quot;15&quot; /&gt;&lt;/p&gt;
&lt;p&gt;新增迁移指令进行迁移，然后开始迁移&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;apply:local&quot;:&quot;npx wrangler d1 migrations apply prisma_d1 --local&quot;,
    &quot;apply:remote&quot;:&quot;npx wrangler d1 migrations apply prisma_d1 --remote&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到我们迁移成功了。去仪表盘看看D1上有没有相关的表文件。是正常和成功的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/16.png&quot; alt=&quot;16&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/17.png&quot; alt=&quot;16&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;修改index.ts 文件，查询D1数据库内的数据，进行收尾&lt;/h2&gt;
&lt;p&gt;打开 src/index.ts 并将整个内容替换为以下内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/index.ts
import { PrismaClient } from &quot;@prisma/client&quot;;
import { PrismaD1 } from &quot;@prisma/adapter-d1&quot;;

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env, ctx: any): Promise&amp;lt;Response&amp;gt; {
    const adapter = new PrismaD1(env.DB);

    const prisma = new PrismaClient({ adapter });

    const users = await prisma.user.findMany();

    const result = JSON.stringify(users);
    return new Response(result);
  },
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新发布到 Worker&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm run deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发布成功后我们去D1仪表盘，随意新增任意条数据作为测试。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/18.png&quot; alt=&quot;18&quot; /&gt;&lt;/p&gt;
&lt;p&gt;访问外网测试地址，看看是否能打印我们从D1数据库中查询出来的全部User数据&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-06-cfd1prisma/19.png&quot; alt=&quot;19&quot; /&gt;&lt;/p&gt;
&lt;p&gt;测试成功，搭建应用完成&lt;/p&gt;
&lt;h2&gt;心得&lt;/h2&gt;
&lt;p&gt;无服务器应用远远不止这点应用那么简单，他还有很多可以搭配的方案，我只是写了个小的demo&lt;/p&gt;
&lt;p&gt;大晚上写的。累死了，应该没有质量可言把&lt;/p&gt;
&lt;h2&gt;相关文档&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/itmowang/prisma-D1-demo&quot;&gt;完整Demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.prisma.io/docs/orm/prisma-client/deployment/edge/deploy-to-cloudflare#cloudflare-d1&quot;&gt;prisma 官方完整示例 &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.prisma.io/docs/orm/overview/databases/cloudflare-d1&quot;&gt;prisma 官方D1介绍&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.cloudflare.com/d1/get-started/&quot;&gt;D1 文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.cloudflare.com/workers/&quot;&gt;Workers 文档&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>使用CloudFlare的Pages服务部署自己的前端项目</title><link>https://blog.loli.wang/blog/2024-07-04-cfpages/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-07-04-cfpages/doc/</guid><description>使用CloudFlare的Pages服务部署自己的前端项目</description><pubDate>Thu, 04 Jul 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;因为最近在折腾SSR项目，看上了Cloudflare的Worker可以部署Node项目，所以打算写几篇CloudFlare利用的文章。之前也有写过使用利用CloudFlare进行反向代理&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.loli.wang/blog/2023-8-21-cfworkerproxy/doc/index.html&quot;&gt;CloudFlare Worker 反向代理 github 给静态博客做图床&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;使用Cloudflare的Pages部署自己的前端静态项目&lt;/h3&gt;
&lt;p&gt;先给自己项目安装 Wrangler ,Wrangler 需要 Node 版本大于 16.17.0+ 才能够正常运行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#npm 
npm install wrangler --save-dev 
#pnpm
pnpm add wrangler --save-dev 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先登录并授权项目&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx wrangler login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-04-cfpages/04.png&quot; alt=&quot;4&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-04-cfpages/05.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;给项目安装好&lt;strong&gt;Wrangler&lt;/strong&gt;并授权后，可以执行&lt;strong&gt;npx wrangler pages project create&lt;/strong&gt;  帮忙快速建立配置。可以看到我们建立的是名为mw-blog的项目名称，并且给了一个测试地址，在Cloudflare Workers管理页面也可以看到有新增相关的项目&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx wrangler pages project create
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-04-cfpages/01.png&quot; alt=&quot;1&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-07-04-cfpages/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后自己手动创建&lt;strong&gt;wrangler.toml&lt;/strong&gt;文件自行配置。&lt;/p&gt;
&lt;p&gt;目前写了个简单的配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
name = &quot;mw-blog&quot;  # 对应 Cloudflare Workers 刚刚创建的项目名
compatibility_date = &quot;2024-07-04&quot;    # 变更兼容日期
pages_build_output_dir = &quot;./docs&quot;  # 你需要上传打包的目录
send_metrics = false

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-04-cfpages/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后编写我们的前端项目的指令，修改package.json, 增加新的指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;deploy&quot;: &quot;npx wrangler pages deploy&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-04-cfpages/07.png&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;执行发布&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# npm
npm run deploy

#pnpm 
pnpm run deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-04-cfpages/08.png&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;打开给我们的测试页面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-04-cfpages/09.png&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成功&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;CloudFlare Pages  还有很多部署方式，比如和github关联，通过插件，通过流水线等等都可以进行一个非常快速的部署。 这只是介绍最简单最轻松的一种。看自己的个人灵活利用咯。&lt;/p&gt;
&lt;h3&gt;相关资料&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.cloudflare.com/pages/&quot;&gt;CloudFlare Pages &lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>[工具] 再也不需要破解Navicat了，Navicat推出免费版 Navicat Premium Lite </title><link>https://blog.loli.wang/blog/2024-07-03-sqltoolsnavicat/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-07-03-sqltoolsnavicat/doc/</guid><description>[工具] 再也不需要破解navicat了，Navicat推出免费版 Navicat Navicat Premium Lite </description><pubDate>Wed, 03 Jul 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;数据库管理工具领域的知名品牌Navicat，推出其免费版本——Navicat Premium Lite，用户可从Navicat官网下载体验这款软件。&lt;/p&gt;
&lt;p&gt;Navicat Premium Lite支持创建连接多种数据库，包括MySQL、Redis、PostgreSQL、SQL Server、Oracle、MariaDB、SQLite 和 MongoDB，覆盖了当前市场上主流的数据库平台。&lt;/p&gt;
&lt;h2&gt;使用体验&lt;/h2&gt;
&lt;p&gt;能够胜任我日常的MySQL数据库管理，拥有漂亮的ui界面，可惜高级功能收费, 但是免费版本已经足够使用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-03-sqltoolsNavicat/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-03-sqltoolsNavicat/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;带Ent 的就是需要收费的功能。但是日常使用是没什么问题的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-07-03-sqltoolsNavicat/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;功能差异 Lite版本 和企业版&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.navicat.com.cn/products/navicat-premium-feature-matrix&quot;&gt;navicat-premium-feature-matrix&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;下载地址&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.navicat.com.cn/download/navicat-premium-lite&quot;&gt;navicat-premium-lite&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Vite 搭建 SSR 进行服务器渲染</title><link>https://blog.loli.wang/blog/2024-06-29-vitessr/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-06-29-vitessr/doc/</guid><description>Vite 搭建 SSR 进行服务器渲染</description><pubDate>Sat, 29 Jun 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;为什么要使用 SSR&lt;/h3&gt;
&lt;p&gt;GPT 回复&lt;/p&gt;
&lt;p&gt;更快的首屏加载时间：SSR 将初始 HTML 页面在服务器上生成并发送给客户端，从而使用户在请求页面时无需等待 JavaScript 的加载和执行即可看到页面内容。这种方式可以显著提升首屏加载时间和用户体验。&lt;/p&gt;
&lt;p&gt;更好的 SEO：搜索引擎蜘蛛能够更容易地抓取和索引由 SSR 生成的页面内容，从而提高网站在搜索引擎中的排名。这对于内容驱动的网站（如博客、新闻网站等）尤为重要。&lt;/p&gt;
&lt;p&gt;便于预渲染静态页面：对于不经常变化的页面内容，SSR 可以预渲染并缓存生成的静态页面，从而减少服务器的负载和请求处理时间。&lt;/p&gt;
&lt;p&gt;说白了 ，更快的加载速度 不用向 spa 项目一样进行首次进去加载比较久, seo 友好，并且可以预渲染&lt;/p&gt;
&lt;h3&gt;起步工作&lt;/h3&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装vue
pnpm add vue
# 安装vite 和 vitejs支持vue的插件
pnpm add vite @vitejs/plugin-vue -D
# 安装服务端插件
pnpm add express
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 vite 配置文件 vite.config.ts&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &quot;vite&quot;;
import vue from &quot;@vitejs/plugin-vue&quot;;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 src 目录，建立 App.vue 和 main.ts 目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.ts
import { createSSRApp } from &quot;vue&quot;;
import App from &quot;./App.vue&quot;;

export function createApp() {
  const app = createSSRApp(App);
  return { app };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// App.vue
&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
   &amp;lt;h1&amp;gt;Vite SSR&amp;lt;/h1&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style scoped&amp;gt;

&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 entry-client.ts 和 entry-server.ts 文件， 并且将 index.html 连接至 entry-client.ts&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- index.html --&amp;gt;

&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&amp;gt;
    &amp;lt;title&amp;gt;Vite + Vue&amp;lt;/title&amp;gt;
    &amp;lt;!--app-head--&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;app&quot;&amp;gt;&amp;lt;!--app-html--&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script type=&quot;module&quot; src=&quot;/src/entry-client.ts&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编写客户端入口和服务端入口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// entry-client.ts
import { createApp } from &quot;./main&quot;;

const { app } = createApp();

app.mount(&quot;#app&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// entry-server.ts
import { renderToString } from &quot;vue/server-renderer&quot;;
import { createApp } from &quot;./main&quot;;

export async function render() {
  const { app } = createApp();

  const ctx = {};
  const html = await renderToString(app, ctx);

  return { html };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-29-vitessr/02.png&quot; alt=&quot;2&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-06-29-vitessr/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;编写编译客户端编译指令 和 服务端编译指令&lt;/p&gt;
&lt;p&gt;package.json&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;pnpm build:client &amp;amp;&amp;amp; pnpm build:server&quot;,
    &quot;build:client&quot;: &quot;vite build --ssrManifest --outDir dist/client&quot;,
    &quot;build:server&quot;: &quot;vite build --ssr src/entry-server.ts --outDir dist/server&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行 pnpm build 命令 , 构建客户端和服务端查看结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-29-vitessr/04.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到分别构建了 SSR的客户端和服务端&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装 压缩插件 和 静态资源插件
pnpm add sirv compression
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编写server.ts 启动服务,完成开发环境场景&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import express from &apos;express&apos;;
import fs from &apos;node:fs/promises&apos;


// 区分开发环境和生产环境
const isProd = process.env.NODE_ENV === &apos;production&apos;;

// 根路径
const base = process.env.BASE || &apos;/&apos;

// 启动端口
const port = 3000

// 读取客户端模板
const templateHtml = isProd ? await fs.readFile(&apos;./dist//index.html&apos;, &apos;utf-8&apos;) : &quot;&quot;;

// 读取ssr资源
const ssrManifest = isProd ? await fs.readFile(&apos;./dist/client/.vite/ssr-manifest.json&apos;, undefined) : undefined;


// 创建http服务
const app = express();

let vite

// 如果不是生产环境，启动vite  , 如果不是启动express中间件
if (!isProd) {
    const { createServer } = await import(&apos;vite&apos;)

    vite = await createServer({
        server: {
            middlewareMode: true
        },
        appType: &apos;custom&apos;,
        base
    })

    app.use(vite.middlewares)
} else {
    const compression = (await import(&apos;compression&apos;)).default;
    const sirv = (await import(&apos;sirv&apos;)).default;

    app.use(compression());
    app.use(base, sirv(&apos;./dist/client&apos;, { extensions: [] }))

}

// 服务端拦截，并且使用占位符去渲染页面
app.use(&apos;*&apos;, async (req, res) =&amp;gt; {
    try {
        const url = req.originalUrl.replace(base, &apos;/&apos;)

        let template
        let render

        if (!isProd) {
            // 如果是开发环境
            template = await fs.readFile(&apos;./index.html&apos;, &apos;utf-8&apos;)
            template = await vite.transformIndexHtml(url, template)
            render = (await vite.ssrLoadModule(&apos;/src/entry-server.ts&apos;)).render
        } else {
            // 如果是生产
            template = templateHtml;
            render = (await import(&apos;./dist/server/entry-server&apos;))
        }

        const rendered = await render(url, ssrManifest);

        // 占位符替换渲染
        const html = template
            .replace(`&amp;lt;!--app-head--&amp;gt;`, rendered.head ?? &apos;&apos;)
            .replace(`&amp;lt;!--app-html--&amp;gt;`, rendered.html ?? &apos;&apos;)

        res.status(200).set({ &apos;Content-Type&apos;: &apos;text/html&apos; }).send(html)

    } catch (e) {
        vite?.ssrFixStacktrace(e)
        console.log(e.stack)
        res.status(500).end(e.stack)
    }
})

// 启动服务
app.listen(port, () =&amp;gt; {
    console.log(`SSR项目已经启动 http://localhost:${port}`)
})

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编写启动指令,因为我们是ts语言, 我们使用antfu大佬写的&apos;tsx&apos;轮子来启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add tsx -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
    &quot;dev&quot;:&quot;tsx server&quot;,
    &quot;build&quot;: &quot;pnpm build:client &amp;amp;&amp;amp; pnpm build:server&quot;,
    &quot;build:client&quot;: &quot;vite build --ssrManifest --outDir dist/client&quot;,
    &quot;build:server&quot;: &quot;vite build --ssr src/entry-server.ts --outDir dist/server&quot;
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到我们已经启动成功了,页面渲染也正常 ，成功！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-29-vitessr/05.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-29-vitessr/06.png&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;相关资料&lt;/h3&gt;
&lt;p&gt;编写的测试模板存放地址：https://github.com/itmowang/vite-vue-ssr-template.git&lt;/p&gt;
</content:encoded></item><item><title>husky + eslint + lint-staged 进行代码规范检测并修复代码</title><link>https://blog.loli.wang/blog/2024-06-26-lintstaged/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-06-26-lintstaged/doc/</guid><description>husky + eslint + lint-staged 进行代码规范检测并修复代码</description><pubDate>Wed, 26 Jun 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;上次配置好了eslint ，在我们每次提交代码的时候就进行代码检测，这次我们写上配置lint-staged 进行代码修复。&lt;/p&gt;
&lt;h3&gt;安装 lint-staged&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 安装指令
pnpm add lint-staged -D
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;在package.json中添加lint-staged的配置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&quot;lint-staged&quot;:{
    &quot;*.js&quot;:&quot;eslint --fix&quot;,
    &quot;*.ts&quot;:&quot;eslint --fix&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 git hooks 钩子，每次提交的时候让他执行 &lt;strong&gt;eslint --fix&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;npx eslint --fix&quot; &amp;gt; .husky/pre-commit
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;测试一下&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-26-lintstaged/01.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到我们错误的代码格式已经帮我们解决了，并且严格按照规则给我们编写修复了代码。 （上面有test警告单纯是插件未安装问题 无关紧要）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-26-lintstaged/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Husky配合ESLint保证提交代码前的提交规范</title><link>https://blog.loli.wang/blog/2024-06-24-eslint/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-06-24-eslint/doc/</guid><description>Husky配合ESLint</description><pubDate>Mon, 24 Jun 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;之前说过了husky的简单安装和使用，而我们需要在每次代码提交前做一些代码检测和代码修复，一般有很多种方式，可以依赖于cicd自动化的时候进行代码检测等工作，但是我们也可以在开发的情况下避免这些情况的发生。&lt;/p&gt;
&lt;p&gt;ESLint 是用来检查我们写的 js 代码是否满足指定规则的静态代码检查工具。 通过用 ESLint 来检查一些规则,我们可以用来统一代码风格规则&lt;/p&gt;
&lt;h1&gt;ESLint  的安装和使用&lt;/h1&gt;
&lt;p&gt;安装eslint&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add eslint  -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化eslint&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx eslint --init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据指示选择项目配置，生成eslint的项目配置文件 &lt;strong&gt;eslint.config.mjs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-24-eslint/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;给项目添加 eslint的 git hooks&lt;/h2&gt;
&lt;p&gt;上一篇文章说过如何使用Husky， 先写一个提交 &lt;strong&gt;pre-commit&lt;/strong&gt; 的钩子，在每次提交之前进行一次检查下整个项目代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
echo &quot;npx eslint .&quot; &amp;gt; .husky/pre-commit

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-24-eslint/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;配置eslint&lt;/h2&gt;
&lt;p&gt;可以通过修改 &lt;strong&gt;eslint.config.mjs&lt;/strong&gt; 文件修改你的代码的风格和规则&lt;/p&gt;
&lt;p&gt;我给出一个我自己常用的eslint规则&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  parser: &apos;@typescript-eslint/parser&apos;,
  plugins: [&apos;@typescript-eslint&apos;],
  rules: {
    &apos;no-var&apos;: &apos;error&apos;, // 不能使用var声明变量
    &apos;no-extra-semi&apos;: &apos;error&apos;,
    &apos;@typescript-eslint/indent&apos;: [&apos;error&apos;, 2],
    &apos;import/extensions&apos;: &apos;off&apos;,
    &apos;linebreak-style&apos;: [0, &apos;error&apos;, &apos;windows&apos;],
    indent: [&apos;error&apos;, 2, { SwitchCase: 1 }], // error类型，缩进2个空格
    &apos;space-before-function-paren&apos;: 0, // 在函数左括号的前面是否有空格
    &apos;eol-last&apos;: 0, // 不检测新文件末尾是否有空行
    semi: [&apos;error&apos;, &apos;always&apos;], // 在语句后面加分号
    quotes: [&apos;error&apos;, &apos;single&apos;], // 字符串使用单双引号,double,single
    &apos;no-console&apos;: [&apos;error&apos;, { allow: [&apos;log&apos;, &apos;warn&apos;] }], // 允许使用console.log()
    &apos;arrow-parens&apos;: 0,
    &apos;no-new&apos;: 0, //允许使用 new 关键字
    &apos;comma-dangle&apos;: [2, &apos;never&apos;], // 数组和对象键值对最后一个逗号， never参数：不能带末尾的逗号, always参数：必须带末尾的逗号，always-multiline多行模式必须带逗号，单行模式不能带逗号
    &apos;no-undef&apos;: 0,
  },
  parserOptions: {
    ecmaVersion: 6,
    sourceType: &apos;module&apos;,
    ecmaFeatures: {
      modules: true,
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;执行测试&lt;/h2&gt;
&lt;p&gt;我们估计在我们的测试代码中写一个错误的示例，并且写上禁止使用var作为声明变量&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-24-eslint/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;执行代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add . 
git commit -m &quot;Love is lonely&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行结果，发现被禁止了提交 ，完成了eslint的语法规范&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-24-eslint/04.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;相关文档&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://zh-hans.eslint.org/docs/latest/use/configure/ignore&quot;&gt;eslint相关配置&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;明天写利用 husky + eslint + lint-staged 进行代码规范检测并修复代码&lt;/p&gt;
</content:encoded></item><item><title>husky 的安装和使用</title><link>https://blog.loli.wang/blog/2024-06-23-web-husky/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-06-23-web-husky/doc/</guid><description>husky的使用</description><pubDate>Sun, 23 Jun 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;什么是husky&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Husky 是一个用于 Git hooks 的工具，它能够在特定的 Git 操作（如 commit、push 等）之前或之后自动运行脚本，从而帮助开发者保持代码质量、执行代码检查、自动化任务等。将白了，用来约束git的。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;安装并使用husky&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;安装&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# npm 安装
npm install husky -D
# yarn 安装
yarn add husky -D
#pnpm 安装 
pnpm add husky -D
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;配置husky&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在给前端项目配置husky的之前一定要给项目建立版本控制&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 建立 git 版本控制
git init

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用指令创建husky配置, 会自动创建.husky文件夹，里面会包含git hooks的配置等。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 指令创建 (老版本是这样)
npx husky install
# 指令创建 (新版本)
npx husky init

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-23-web-husky/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;测试配置是否成功&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &quot;Keep calm and commit&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到我们提交命令被拦截了，不能被自由的正常使用git commit&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-23-web-husky/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建husky钩子&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;创建一个husky钩子，让我们使用 npm test 的时候执行 .husky/pre-commit 的提交规则&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;npm test&quot; &amp;gt; .husky/pre-commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到.husky 文件夹中增加了一个 pre-commit的文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-23-web-husky/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;里面会有一个 npm test 的指令代表 你每次执行 git commit 的时候就会先执行 npm test 指令，然后再走正常的git commit&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-06-23-web-husky/04.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;-------- 剩下的交给下个文章再写 《使用husky提交代码时使用eslint检测代码》&lt;/p&gt;
</content:encoded></item><item><title>好玩的console - 水一篇</title><link>https://blog.loli.wang/blog/2024-05-29-consolelog/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-05-29-consolelog/doc/</guid><description>奇妙的console - 水一篇</description><pubDate>Wed, 29 May 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;console.log&lt;/h2&gt;
&lt;p&gt;常规的打印&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(&quot;魔王&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-29-consolelog/01.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;console.warm&lt;/h2&gt;
&lt;p&gt;警告的打印&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.warn(&quot;魔王&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-29-consolelog/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;console.error&lt;/h2&gt;
&lt;p&gt;错误的打印&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.error(&quot;魔王&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-29-consolelog/03.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;console.dir&lt;/h2&gt;
&lt;p&gt;输出对象&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; const obj = { name: &quot;魔王&quot;, age: 999 };
 console.dir(obj);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-29-consolelog/04.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;console.clear()&lt;/h2&gt;
&lt;p&gt;清空控制台&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.clear();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-29-consolelog/05.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;console.table&lt;/h2&gt;
&lt;p&gt;以表格的形式输出对象或者数组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const test = [&quot;Apple&quot;, &quot;Banana&quot;, &quot;Orange&quot;];
    const test1 = {
      name: &quot;mowang&quot;,
      age: 999,
    };
    console.table(test);
    console.table(test1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-29-consolelog/06.png&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;console.assert()&lt;/h2&gt;
&lt;p&gt;用于条件不满足的时候输出信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.assert(2 + 2 === 5, &quot;2+2不等于5&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-29-consolelog/07.png&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>前端大文件切片上传以及使用webWorker</title><link>https://blog.loli.wang/blog/2024-05-27-webupload/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-05-27-webupload/doc/</guid><description>前端大文件切片上传以及使用webWorke</description><pubDate>Tue, 28 May 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;常规项目日常开发的时候我们经常用到上传这个功能,一般日常的话不会有很大的文件上传，但是就是有不长眼的需求过来。 ==&lt;/p&gt;
&lt;h2&gt;切片上传优点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;大文件分割，切片后分批上传，减轻服务器压力&lt;/li&gt;
&lt;li&gt;断点续传，可以记录上传位置，方便上次上传未结束的操作&lt;/li&gt;
&lt;li&gt;上传进度控制的功能，不然无法知道文件是否上传的进度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么使用web worker&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;让线程不阻塞 ，切片 和 上传大文件是一个比较耗费时间的操作，如果在主要线程直行这个操作，容易卡死和卡顿，影响体验和效果&lt;/li&gt;
&lt;li&gt;提高性能充分利用处理器&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;切片主要代码演示&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import React, { useEffect } from &quot;react&quot;;

const Login: React.FC = () =&amp;gt; {
  const chunkSize = 1 * 1024 * 1024; // 1M
  // 将文件切片
  const sliceFile = (file: File) =&amp;gt; {
    // 切片的内容存放
    const chunks = [];
    // 切片的位置
    let offset = 0;

    while (offset &amp;lt; file.size) {
      // 分割切片位置以及每次切片的大小
      const chunk = file.slice(offset, offset + chunkSize);
      chunks.push(chunk);
      // 切片的位置计算
      offset = offset + chunkSize;
    }
    return chunks;
  };

  // 上传的切片
  const uploadChunks = async (chunks: any) =&amp;gt; {
    for (let i = 0; i &amp;lt; chunks.length; i++) {
      const formData = new FormData();
      formData.append(&quot;fileChunk&quot;, chunks[i]);

      // 发送切片上传请求
      await fetch(&quot;/update&quot;, {
        method: &quot;POST&quot;,
        body: formData,
      });
    }
  };

  // 上传的主函数
  const uploadFile = async (file: File) =&amp;gt; {
    const chunks = await sliceFile(file);

    await uploadChunks(chunks);
  };

  const click = () =&amp;gt; {
    document.getElementById(&quot;upload&quot;)?.click();
  };

  useEffect(() =&amp;gt; {
    document.getElementById(&quot;upload&quot;)?.addEventListener(&quot;change&quot;, (e) =&amp;gt; {
      // 获得当前上传的文件
      const files = (e.target as HTMLInputElement).files?.[0];
      if (files) {
        uploadFile(files);
      }
    });
  }, []);

  return (
    &amp;lt;div className=&quot;login&quot;&amp;gt;
      &amp;lt;h1&amp;gt;大文件上传&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={click}&amp;gt; 选择上传文件&amp;lt;/button&amp;gt;
      &amp;lt;input type=&quot;file&quot; id=&quot;upload&quot; style={{ display: &quot;none&quot; }} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Login;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果如下
&lt;img src=&quot;http://img.blog.loli.wang/2024-05-27-webUpload/01.png&quot; alt=&quot;1&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-05-27-webUpload/02.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成功切片上传 但是会影响到页面卡顿 使用webworker&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//worker.ts

// 上传的切片
const uploadChunks = async (chunks: any) =&amp;gt; {
  for (let i = 0; i &amp;lt; chunks.length; i++) {
    const formData = new FormData();
    formData.append(&quot;fileChunk&quot;, chunks[i]);

    // 发送切片上传请求
    await fetch(&quot;/update&quot;, {
      method: &quot;POST&quot;,
      body: formData,
    });
  }
};

// 创建接收消息通知
this.addEventListener(&quot;message&quot;, async (event) =&amp;gt; {
  const chunkSize = 1 * 1024 * 1024; // 1M
  const file = event.data.payload.file;
  // 切片的内容存放
  const chunks = [];
  // 切片的位置
  let offset = 0;

  while (offset &amp;lt; file.size) {
    // 分割切片位置以及每次切片的大小
    const chunk = file.slice(offset, offset + chunkSize);
    chunks.push(chunk);
    // 切片的位置计算
    offset = offset + chunkSize;
  }

  //   上传切片
  await uploadChunks(chunks);

  // 通知完成
  postMessage({ type: &quot;uploadComplete&quot; });
});

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主页面&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { useEffect } from &quot;react&quot;;

const Login: React.FC = () =&amp;gt; {
  const click = () =&amp;gt; {
    document.getElementById(&quot;upload&quot;)?.click();
  };

  useEffect(() =&amp;gt; {
    // 创建Worker的实例，并启动实例
    const worker = new Worker(&quot;src/pages/login/worker.ts&quot;);

    document.getElementById(&quot;upload&quot;)?.addEventListener(&quot;change&quot;, (e) =&amp;gt; {
      // 获得当前上传的文件
      const files = (e.target as HTMLInputElement).files?.[0];

      // 接收消息
      worker.addEventListener(&quot;message&quot;, (event) =&amp;gt; {
        const { type } = event.data;
        if (type === &quot;uploadComplete&quot;) {
          console.log(&quot;文件上传完成！&quot;);
        }
      });
      if (files) {
        // 向worker发送消息
        worker.postMessage({ type: &quot;upload&quot;, payload: { file: files } });
      }
    });
  }, []);

  return (
    &amp;lt;div className=&quot;login&quot;&amp;gt;
      &amp;lt;h1&amp;gt;大文件上传&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={click}&amp;gt; 选择上传文件&amp;lt;/button&amp;gt;
      &amp;lt;input type=&quot;file&quot; id=&quot;upload&quot; style={{ display: &quot;none&quot; }} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Login;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成配合使用webWorker&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-27-webUpload/03.png&quot; alt=&quot;1&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-05-27-webUpload/04.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;最近状态不好。。。看上去不能写的太细节，当作demo预览吧&lt;/h1&gt;
</content:encoded></item><item><title>chrome浏览器暴力解决跨域的方案</title><link>https://blog.loli.wang/blog/2024-05-15-chromecubaoporxy/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-05-15-chromecubaoporxy/doc/</guid><description>chrome浏览器暴力解决跨域的方案</description><pubDate>Wed, 15 May 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;为什么会知道这个&lt;/h3&gt;
&lt;p&gt;一个群交流看见的。 有这么个解决方案，记录下。毕竟有时候真的需要很粗暴的解决方式。&lt;/p&gt;
&lt;p&gt;原先随意请求一个非同源的接口。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-15-chromecubaoporxy/01.png&quot; alt=&quot;处理前&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-05-15-chromecubaoporxy/05.png&quot; alt=&quot;处理后&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到，现在报错是提示403错误。代表没有权限，也就是我们能正常请求到接口了，跨域已经处理成功了&lt;/p&gt;
&lt;h3&gt;步骤&lt;/h3&gt;
&lt;p&gt;首先右键我们的google浏览器，选择&lt;strong&gt;属性&lt;/strong&gt;,&lt;strong&gt;打开文件所在位置&lt;/strong&gt;，右键发送一个快捷方式到桌面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-15-chromecubaoporxy/02.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在鼠标右键刚刚新建的快捷方式，选择&lt;strong&gt;属性&lt;/strong&gt;，在&lt;strong&gt;目标&lt;/strong&gt;中&lt;strong&gt;追加&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;--disable-web-security 代表禁用同源策略
--user-data-dir  代表用户缓存目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
目标路径 --args --disable-web-security --user-data-dir=D:\MyChromeDevUserData

或

目标路径 --disable-web-security --user-data-dir=D:\MyChromeDevUserData

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-15-chromecubaoporxy/03.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;关闭所有谷歌浏览器进程，重新运行一下这个快捷方式，就可以看到&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-15-chromecubaoporxy/04.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;并且访问接口的时候，跨域也被解决掉了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-15-chromecubaoporxy/05.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>UNIAPP离线打包plus.runtime.install无法调起安装的解决方法</title><link>https://blog.loli.wang/blog/2024-05-13-padandroidstudio01/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-05-13-padandroidstudio01/doc/</guid><description>记录一次UniAPP项目离线打包后项目无法调起安装包的问题</description><pubDate>Mon, 13 May 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;起因&lt;/h3&gt;
&lt;p&gt;公司项目出现问题了，业务人员说设备APP升级了无法安装，我想了想最近除了改过业务类的代码，也没有动过其他代码啊，为什么会出现问题 ？&lt;/p&gt;
&lt;h3&gt;判断问题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;测试环境测试，云打包测试，打包后安装，可以安装。&lt;/li&gt;
&lt;li&gt;离线打包测试，打包后升级出现问题，无法出现问题，自从前一阵子切换成离线打包后就出现这个问题了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;发现问题。&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-13-padandroidStudio01/02.png&quot; alt=&quot;切1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;下载完最新的安装包后，无法调起最新的安装包，而我又是最新的SDK。&lt;/p&gt;
&lt;p&gt;看官方文档&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ask.dcloud.net.cn/article/35703&quot;&gt;针对plus.runtime.install在安卓9.0+上无法执行的解决方案&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;使用&lt;strong&gt;云打包&lt;/strong&gt;的话，只用在UNIAPP的manifest.json中添加相关的&lt;strong&gt;权限&lt;/strong&gt;配置就好，但是离线打包的话，需要自己手动在 &lt;strong&gt;Android Studio&lt;/strong&gt; 进行权限配置。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-13-padandroidStudio01/03.png&quot; alt=&quot;切1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;既然知道问题了，就进行修改吧。打开&lt;strong&gt;Android Studio&lt;/strong&gt;，在&lt;strong&gt;AndroidManifest.xml&lt;/strong&gt;中，找到&lt;strong&gt;manifest&lt;/strong&gt;标签，在里面添加如下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;uses-permission android:name=&quot;android.permission.REQUEST_INSTALL_PACKAGES&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-13-padandroidStudio01/04.png&quot; alt=&quot;切1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;重新打包运行，成功安装。&lt;/p&gt;
&lt;h3&gt;相关文章&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.loli.wang/blog/2023-12-28-uniapppackage/doc/index.html&quot;&gt;UNIAPP离线打包配置&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Nestjs的微服务</title><link>https://blog.loli.wang/blog/2024-05-04-manest/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-05-04-manest/doc/</guid><description>使用NestJs的微服务</description><pubDate>Sat, 04 May 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;2024 年 5月 3日&lt;/h2&gt;
&lt;p&gt;到晚上了，QQ没人找，微信没人回。当然这么好的假日应该出去玩才对，但是我不愿意浪费这点时间给自己好好休息。偶然看到了某技术群聊微服务，很有兴趣，看资料折腾下吧。&lt;/p&gt;
&lt;h2&gt;使用nest的cli工具创建项目&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 全局安装nestjs的cli
npm i -g @nestjs/cli

# 安装第一个项目
nest new aa

# 启动aa项目
cd aa
pnpm run start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-04-MANest/01.png&quot; alt=&quot;切1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;启动访问成功&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-04-MANest/02.png&quot; alt=&quot;切2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;创建一个 微服务项目&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 通过cli安装一个新的项目
nest new micro-service

# 安装nestjs 微服务插件
pnpm add @nestjs/microservices
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;引用 @nestjs/microservices 修改main.ts&lt;/h2&gt;
&lt;p&gt;将原本启动http服务,改为启动微服务&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.ts

import { NestFactory } from &apos;@nestjs/core&apos;;
import { AppModule } from &apos;./app.module&apos;;
import { Transport, MicroserviceOptions } from &apos;@nestjs/microservices&apos;;

async function bootstrap() {
  // const app = await NestFactory.create(AppModule);

  const app = await NestFactory.createMicroservice&amp;lt;MicroserviceOptions&amp;gt;(
    AppModule,
    {
      transport: Transport.TCP,
      options: {
        port: 8888,
      },
    },
  );

  await app.listen();
}
bootstrap();

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 &lt;strong&gt;app.controller.ts&lt;/strong&gt; 改为接受Message通知，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// app.controller.ts

import { Controller, Get } from &apos;@nestjs/common&apos;;
import { AppService } from &apos;./app.service&apos;;
import { MessagePattern } from &apos;@nestjs/microservices&apos;;

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @MessagePattern(&apos;sum&apos;)
  sum(numArr: Array&amp;lt;number&amp;gt;): number {
    return numArr.reduce((total, item) =&amp;gt; total + item, 0);
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;改造 aa 项目，让他能够连接微服务&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 安装微服务依赖
pnpm add @nestjs/microservices

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进入 app.module.ts 注册&lt;strong&gt;微服务&lt;/strong&gt;那个项目,指定TCP端口为8888，让他直到我们的微服务项目端口是8888&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Module } from &apos;@nestjs/common&apos;;
import { AppController } from &apos;./app.controller&apos;;
import { AppService } from &apos;./app.service&apos;;
import { ClientsModule, Transport } from &apos;@nestjs/microservices&apos;;

@Module({
  imports: [

    ClientsModule.register([
      {
        name: &apos;MICRO_SERVICE&apos;,
        transport: Transport.TCP,
        options: {
          port: 8888,
        },
      },
    ]),

  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-04-MANest/03.png&quot; alt=&quot;切2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样就注册完了。 然后在 &lt;strong&gt;aa&lt;/strong&gt; 项目中注册客户端代理&lt;strong&gt;clientProxy&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//app.controller.ts

import { Controller, Get, Query, Inject } from &apos;@nestjs/common&apos;;
// import { AppService } from &apos;./app.service&apos;;
import { ClientProxy } from &apos;@nestjs/microservices&apos;;
import { Observable } from &apos;rxjs&apos;;

@Controller()
export class AppController {
  constructor(@Inject(&apos;MICRO_SERVICE&apos;) private serviceClient: ClientProxy) {}

  @Get()
  server(@Query(&apos;num&apos;) str): Observable&amp;lt;number&amp;gt; {
    const numArr = str.split(&apos;,&apos;).map((item) =&amp;gt; parseInt(item));

    return this.serviceClient.send(&apos;sum&apos;, numArr);
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启下微服务，查看结果&lt;/p&gt;
&lt;p&gt;访问 aa 服务的看看是否有进行一个累加的计算 http://localhost:3000/?num=1,2,3&lt;/p&gt;
&lt;p&gt;能够正常进行的互相传递接收，微服务配置成功&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-04-MANest/04.png&quot; alt=&quot;切2&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-05-04-MANest/05.png&quot; alt=&quot;切2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;照葫芦画瓢整的，虽然能够理解，但是目前没有合适的东西能让我启用这一套，当作学习记录一下吧&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7207637337571901495?searchId=202405032014240A690D4981415C621CB4&quot;&gt;神说要有光 - 掘金&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/itmowang/nest-microservices-demo&quot;&gt;我的github - nestjs 微服务demo&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>搭建第一个Angular的Demo</title><link>https://blog.loli.wang/blog/2024-05-05-angulardevinit/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-05-05-angulardevinit/doc/</guid><description>搭建第一个Angular项目</description><pubDate>Sat, 04 May 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;2024 年 5月 5日&lt;/h2&gt;
&lt;p&gt;练完车，回来了, 明天又要上班了。工作是没有什么能给人内心带来充实的。只有学点东西能带来一点。&lt;/p&gt;
&lt;h2&gt;为什么要学Angular&lt;/h2&gt;
&lt;p&gt;身边太多Angular爱好者了，感觉不稍微学点融不进圈子，让人有很大的落差感。&lt;/p&gt;
&lt;p&gt;我认为想熟悉一个前端框架，必须要从他的生态入手，脚手架是官方提供的一个工具，我也需要从脚手架开始这样才能一步一步慢慢理解&lt;/p&gt;
&lt;h2&gt;Angular官网&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;https://angular.dev/overview
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;安装Angular官方的CLI工具&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install -g @angular/cli
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用 NG 指令搭建一个新项目&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;ng new &amp;lt;项目名&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-05-angulardevInit/01.png&quot; alt=&quot;切1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;安装成功后 ， 使用指令 &lt;strong&gt;pnpm run start&lt;/strong&gt;进行启动。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-05-angulardevInit/02.png&quot; alt=&quot;切2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;虽然使用cli安装项目成功跑起来了,语法也很熟悉，还需要想办法熟下，挖个坑&lt;strong&gt;后续使用Angular做我的图床程序&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://angular.dev/guide/templates/interpolation&quot;&gt;Anglar官网&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>使用Nestjs + prisma 构建 REST API</title><link>https://blog.loli.wang/blog/2024-05-03-prismanestapi/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-05-03-prismanestapi/doc/</guid><description>使用nestjs + prisma构建REST API</description><pubDate>Fri, 03 May 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;2024 年 5月 3日&lt;/h2&gt;
&lt;p&gt;难得的51假期,5天假期，本应该是好好去玩的一天，可惜没有什么地方可以去，并且还报了驾校正在练车，正好今天没有安排，正好熟悉下nestjs吧。&lt;/p&gt;
&lt;h3&gt;使用nest cli 安装nest空项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 创建空的 nest 项目
npx @nestjs/cli new &amp;lt;项目名&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/01.png&quot; alt=&quot;切01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;启动正常&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/02.png&quot; alt=&quot;切03&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;安装prisma, 并初始化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 安装prisma
pnpm install -D prisma

# 初始化prisma
npx prisma init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会增加一个prisma 的文件夹， 里面的schenma.prisma 是我们的数据库配置文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/03.png&quot; alt=&quot;切03&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/04.png&quot; alt=&quot;切04&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;本地安装mysql&lt;/h3&gt;
&lt;p&gt;比较常规，每个人情况不一样，我在这里使用的mysql&lt;/p&gt;
&lt;h3&gt;配置env环境变量&lt;/h3&gt;
&lt;p&gt;在初始化prisma的时候，会生成一个env的配置文件，使用env文件配置环境变量,如果你是用的其他的数据库参考文档
https://www.prisma.io/docs/orm/reference/connection-urls&lt;/p&gt;
&lt;p&gt;我这里使用的是mysql，先修改指定数据库然后修改env配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// prisma/schema.prisma
datasource db {
  provider = &quot;mysql&quot;
  url      = env(&quot;DATABASE_URL&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;DATABASE_URL=&quot;DATABASE_URL=&quot;mysql://prisma:123456@localhost:3306/prisma&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;增加数据模型&lt;/h3&gt;
&lt;p&gt;此处是定义了一个 Article的表 内有相关字段，以及字段代表的类型&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// prisma/schema.prisma

model Article {
  id          Int      @id @default(autoincrement())
  title       String   @unique
  description String?
  body        String
  published   Boolean  @default(false)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第一次执行&lt;/h3&gt;
&lt;p&gt;当你定义好数据模型后 我们需要执行一次指令 让数据库和prisma同步, 你也可以记录到package.json中的script指令中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 指令
npx prisma migrate dev --name &quot;init&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/05.png&quot; alt=&quot;切05&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/06.png&quot; alt=&quot;切06&quot; /&gt;&lt;/p&gt;
&lt;p&gt;执行成功后，这里可以看到我们的数据库内自动生成了，我们刚刚设置的模型。每次修改我们的数据库模型都要去生成一遍。&lt;/p&gt;
&lt;h3&gt;增加默认数据填充数据库&lt;/h3&gt;
&lt;p&gt;创建一个为 &lt;strong&gt;prisma/seed.ts&lt;/strong&gt; 的文件，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// prisma/seed.ts

import { PrismaClient } from &apos;@prisma/client&apos;;

// initialize Prisma Client
const prisma = new PrismaClient();

async function main() {
  // create two dummy articles
  const post1 = await prisma.article.upsert({
    where: { title: &apos;Prisma Adds Support for MongoDB&apos; },
    update: {},
    create: {
      title: &apos;Prisma Adds Support for MongoDB&apos;,
      body: &apos;Support for MongoDB has been one of the most requested features since the initial release of...&apos;,
      description:
        &quot;We are excited to share that today&apos;s Prisma ORM release adds stable support for MongoDB!&quot;,
      published: false,
    },
  });

  const post2 = await prisma.article.upsert({
    where: { title: &quot;What&apos;s new in Prisma? (Q1/22)&quot; },
    update: {},
    create: {
      title: &quot;What&apos;s new in Prisma? (Q1/22)&quot;,
      body: &apos;Our engineers have been working hard, issuing new releases with many improvements...&apos;,
      description:
        &apos;Learn about everything in the Prisma ecosystem and community from January to March 2022.&apos;,
      published: true,
    },
  });

  console.log({ post1, post2 });
}

// execute the main function
main()
  .catch((e) =&amp;gt; {
    console.error(e);
    process.exit(1);
  })
  .finally(async () =&amp;gt; {
    // close Prisma Client at the end
    await prisma.$disconnect();
  });


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在package.json中添加, 该命令使用 npx prisma db seed 的时候会自动执行，这样数据库内就会自动增加2条数据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx prisma db seed
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt; &quot;prisma&quot;: {
    &quot;seed&quot;: &quot;ts-node prisma/seed.ts&quot;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/07.png&quot; alt=&quot;切06&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/08.png&quot; alt=&quot;切08&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;创建 Prisma 服务&lt;/h3&gt;
&lt;p&gt;nest提供了一个直接使用cli生成模块和服务的指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx nest generate module prisma
npx nest generate service prisma
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;设置 swagger&lt;/h3&gt;
&lt;p&gt;Swagger是一个使用 API 规范记录 API 的工具。 Nest 有一个专门用于 Swagger 的模块。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装@nestjs/swagger 和  swagger-ui-express
pnpm install --save @nestjs/swagger swagger-ui-express
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;strong&gt;main.ts&lt;/strong&gt;中初始化 swagger&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { NestFactory } from &apos;@nestjs/core&apos;;
import { AppModule } from &apos;./app.module&apos;;
import { SwaggerModule, DocumentBuilder } from &apos;@nestjs/swagger&apos;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle(&apos;Median&apos;)
    .setDescription(&apos;The Median API description&apos;)
    .setVersion(&apos;0.1&apos;)
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup(&apos;api&apos;, app, document);

  await app.listen(3000);
}
bootstrap();

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们启动一下,可以看到 &lt;strong&gt;/api&lt;/strong&gt; 加上路径后，swagger访问正常&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/09.png&quot; alt=&quot;切08&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;生成 REST 资源&lt;/h3&gt;
&lt;p&gt;可以看到swagger虽然正常了 但是我们目前还没有任何的接口去供我们查阅api。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 快速生成REST资源
npx nest generate resource
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用指令可以快速帮我们生成&lt;strong&gt;REST&lt;/strong&gt;资源，供我们修改使用，重新启动一下。可以看到出现了我们需要的REST接口资源&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/10.png&quot; alt=&quot;切10&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;连接prisma 进行CURD&lt;/h3&gt;
&lt;p&gt;可以看到我们的api资源对应的，都是 &lt;strong&gt;@Body(),@Query()&lt;/strong&gt; 等装饰器来生成这个API。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/11.png&quot; alt=&quot;切11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是要让 &lt;strong&gt;Swagger&lt;/strong&gt; 访问到我们的&lt;strong&gt;prsima&lt;/strong&gt; 进行&lt;strong&gt;CURD&lt;/strong&gt;操作还需要一定的步骤,进入到我们的articles.module.ts文件，引入Prisma的模型.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/articles/articles.module.ts

import { Module } from &apos;@nestjs/common&apos;;
import { ArticlesService } from &apos;./articles.service&apos;;
import { ArticlesController } from &apos;./articles.controller&apos;;
import { PrismaModule } from &apos;src/prisma/prisma.module&apos;;

@Module({
  controllers: [ArticlesController],
  providers: [ArticlesService],
  imports: [PrismaModule],
})
export class ArticlesModule {}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 &lt;strong&gt;PrismaService&lt;/strong&gt;注入到&lt;strong&gt;ArticlesService&lt;/strong&gt; 中, 并且使用他来访问数据库，需要增加一个构造函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/articles/articles.service.ts
import { Injectable } from &apos;@nestjs/common&apos;;
import { CreateArticleDto } from &apos;./dto/create-article.dto&apos;;
import { UpdateArticleDto } from &apos;./dto/update-article.dto&apos;;
import { PrismaService } from &apos;src/prisma/prisma.service&apos;;

@Injectable()
export class ArticlesService {
  constructor(private prisma: PrismaService) {}
    //   ...CURD
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;更改一个测试接口用来测试&lt;/h3&gt;
&lt;p&gt;我只需要简单写一个接口，查询出表&lt;strong&gt;article&lt;/strong&gt;所有数据。看看是否成功 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/articles/articles.service.ts

import { Injectable } from &apos;@nestjs/common&apos;;
import { CreateArticleDto } from &apos;./dto/create-article.dto&apos;;
import { UpdateArticleDto } from &apos;./dto/update-article.dto&apos;;
import { PrismaService } from &apos;src/prisma/prisma.service&apos;;

@Injectable()
export class ArticlesService {
  constructor(private prisma: PrismaService) {}
  create(createArticleDto: CreateArticleDto) {
    return &apos;This action adds a new article&apos;;
  }

  findAll() {
    return this.prisma.article.findMany({});
  }

  findOne(id: number) {
    return `This action returns a #${id} article`;
  }

  update(id: number, updateArticleDto: UpdateArticleDto) {
    return `This action updates a #${id} article`;
  }

  remove(id: number) {
    return `This action removes a #${id} article`;
  }
}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/12.png&quot; alt=&quot;切12&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/13.png&quot; alt=&quot;切13&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-05-03-prismaNestApi/14.png&quot; alt=&quot;切14&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;相关文章&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.prisma.io/blog/nestjs-prisma-rest-api-7D056s1BmOL0#introduction&quot;&gt;Prsima 官网&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/itmowang/prisma-nest-mysql-demo&quot;&gt;Github编写的示例DEMO&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>关于帮网友解决linux安装Nginx，并配置SSL这回事</title><link>https://blog.loli.wang/blog/2024-04-30-linuxnginx/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-04-30-linuxnginx/doc/</guid><description>关于帮网友解决linux安装Nginx这回事</description><pubDate>Tue, 30 Apr 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;忙碌的一天&lt;/h2&gt;
&lt;p&gt;前天非常忙碌，客户那边问题挺多的，忙了一天解决了大部分事情。5:30分的时候，一网友问出了奇怪的问题。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-04-30-linuxnginx/01.png&quot; alt=&quot;切01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;浅想一下
&lt;code&gt;他或许是想问这个Nginx配置前端来配置好，还是后端配置好。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-04-30-linuxnginx/02.png&quot; alt=&quot;切01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;哦。。原来是NGINX让他来配置了，而他这边有点问题&lt;/p&gt;
&lt;p&gt;远程帮忙看了下，用的 &lt;strong&gt;腾讯云&lt;/strong&gt; , 并且自带Nginx，尝试直接配置反向代理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
#user  nobody;
worker_processes  1;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

   server {
    listen 443 ssl;
    server_name your-domain.com;
 
    ssl_certificate ./ssl/nxesp.cn_bundle.crt; # SSL证书文件路径
    ssl_certificate_key ./ssl/nxesp.cn_bundle.key; # SSL证书密钥文件路径
 
    # SSL配置
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
 
    # 指定加密套件（Suites）
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers &apos;ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384&apos;;
    ssl_prefer_server_ciphers on;
 
    location / {
        proxy_pass https://your-backend-server; # 后端服务器地址
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看上去 配置没有问题，但是腾讯云上自带的nginx没有ssl套件，所以需要手动安装。我觉着太麻烦了。。直接重装吧&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;卸载nginx&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;
killall nginx

yum remove nginx

rm -rf /usr/local/nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;安装nginx&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;yum install nginx

#测试启动
systemctl start nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问相应端口 安装成功&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;配置SSL&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SSL文件放入到 /etc/nginx/conf/ssl/ (我个人喜欢新建一个ssl文件夹存放ssl文件，这样方便直接./去调用),将我们的原有配置复制到新的配置文件中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; server {
    listen 443 ssl;
    server_name your-domain.com;
 
    ssl_certificate ./ssl/nxesp.cn_bundle.crt; # SSL证书文件路径
    ssl_certificate_key ./ssl/nxesp.cn_bundle.key; # SSL证书密钥文件路径
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;检测 Nginx 配置文件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;cd /etc/nginx/

# 检查配置文件语法是否正确
nginx -t

# 重启Nginx
systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正常后，会显示类似下面的信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; nginx success .... （具体不记得）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-04-30-linuxnginx/03.png&quot; alt=&quot;切03&quot; /&gt;&lt;/p&gt;
&lt;p&gt;嗯 看上去没问题了~&lt;/p&gt;
&lt;h4&gt;Centos 常用NGinx命令&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;
# 启动nginx
systemctl start nginx

# 停止nginx
systemctl stop nginx

# 重启nginx
systemctl restart nginx

# 查看nginx状态
systemctl status nginx

# 配置nginx开机自启动
systemctl enable nginx

# 配置nginx开机不启动
systemctl disable nginx

# 查看nginx日志
cat /var/log/nginx/error.log

# 查看nginx进程
ps -ef | grep nginx

# 查看nginx配置文件
cat /etc/nginx/nginx.conf

# 检查配置文件语法是否正确
nginx -t
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>记一次配置小主机装Ubuntu</title><link>https://blog.loli.wang/blog/2024-04-15-linuxserver/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-04-15-linuxserver/doc/</guid><description>记一次配置小主机装Ubuntu</description><pubDate>Mon, 15 Apr 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;因为 Switch 送人了，而在 PC 上玩模拟不痛快，就想着整个小主机装个 ubuntu 然后装模拟器给电视机用&lt;/h2&gt;
&lt;h1&gt;&lt;strong&gt;预算定为 500 块&lt;/strong&gt;&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;选购小主机&lt;/p&gt;
&lt;p&gt;在选择小主机的时候，我观察了很多，如果买现成的 ，网上大部分 cpu 大部分都是 N100 的处理器，性能偏弱，价格也在 1000 块钱左右，并且硬盘很少，内存也就给 8g，并不能满足日常生活需要，所以考虑选择自己配，看了许多配置后，选定如下配置。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;CPU：酷睿 i5-8500T 散片 (淘宝:357 RMB)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RAM：海力士 ddr4 3200mhz *2 (咸鱼: 160 RMB)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;硬盘：金士顿 m2 500G SSD (咸鱼: 210 RMB)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;主机: 准系统 DELL 戴尔 7060MFF (咸鱼:330 RMB)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;合计 : 1057 RMB&lt;/p&gt;
&lt;p&gt;以上配置，个人感觉还是能够接受的，虽然超预算了。。。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/01.jpg&quot; alt=&quot;切图01&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/02.png&quot; alt=&quot;切图02&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;装配小主机&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;准系统的小主机装配很简单，把内存 cpu 硬盘安装好，插上电源直接用就行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/03.jpg&quot; alt=&quot;切图03&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/04.jpg&quot; alt=&quot;切图04&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/05.jpg&quot; alt=&quot;切图05&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/06.jpg&quot; alt=&quot;切图06&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/07.jpg&quot; alt=&quot;切图07&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/08.jpg&quot; alt=&quot;切图08&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;点亮小主机&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;顺利点亮小主机，二手收的硬盘里装有一个自带的windows 11，发现上一任硬盘装的各种资料。。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/09.jpg&quot; alt=&quot;切图09&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/10.jpg&quot; alt=&quot;切图10&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/11.jpg&quot; alt=&quot;切图11&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/12.jpg&quot; alt=&quot;切图12&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/13.jpg&quot; alt=&quot;切图13&quot; /&gt;&lt;/p&gt;
&lt;p&gt;嗯。。。 虽然离谱。给他格式化吧。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;给小主机装ubuntu系统&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;小主机装配完毕了，是时候安装linux系统了，我自己的想法是先安装图形化界面的，后面再看着办，&lt;/p&gt;
&lt;p&gt;原本的想法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分配10G的硬盘，10G的硬盘里装ios镜像，然后安装ubuntu&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为u盘忘记放哪儿了，就准备采取上面的方案。但是试了很多次，总是出现错误。一会说不能插入网卡，等各种问题，好不容易进了安装界面，一会说不能找不到U盘。 (毕竟我也只有一块硬盘)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/14.jpg&quot; alt=&quot;切图14&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/15.jpg&quot; alt=&quot;切图15&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;连夜买了个u盘&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为这问题纠结太久了，夜晚比较浮躁，美团买了个u盘，用了优惠券 16g 20RMB，&lt;/p&gt;
&lt;p&gt;使用 &lt;strong&gt;Etcher&lt;/strong&gt; 工具少烧录了，直接uefi模式启动，顺利安装 unbuntu&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-04-15-linuxServer/17.jpg&quot; alt=&quot;切图16&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最终结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我最后还是失败了。在安装模拟器的过程中，发现到生态多烂多不完善，各种工具安装不上，安装好模拟器模拟器还乱码。和服务器语言有关，切换后正常了，下载模拟器游戏也各种不方便，最后还是放弃。。。 （或许过几天我还是会继续尝试下），但是感觉除了做服务器环境，用linux的话，还是不怎么好用的。。。&lt;/p&gt;
</content:encoded></item><item><title>(使用一年React后的回顾) React 常用 Hooks</title><link>https://blog.loli.wang/blog/2024-03-29-react-2/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-03-29-react-2/doc/</guid><description>React 常用Hooks</description><pubDate>Mon, 01 Apr 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h1&gt;useState&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;useState&lt;/strong&gt; 是一个 React 的 hook ，它的作用是让你向组件添加一个状态变量。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const [state, setState] = useState(initialState);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;useState 有两个参数&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;useState 返回的第一个参数，为我们需要展示或者使用的最新的值&lt;/li&gt;
&lt;li&gt;useState 的第二个参数，这个函数是一个 set 函数，可以传入任意变量，让其 state 返回新的值。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function App() {
    const [num, setNum] = useState(1);
    return (
        &amp;lt;div className=&quot;App&quot;&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; setNum(num + 1)}&amp;gt;{num}&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;useEffect&lt;/h1&gt;
&lt;p&gt;useEffect ，是副作用，副作用是指在组件在渲染期间发生的操作，如数据获取，订阅事件，手动操作 dom 等，在函数组件中，由于没有生命周期方法，我们无法再特定的时间执行这些操作，useEffect 正好解决了这个问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function getData() {
    return await new Promise((resolve, reject) =&amp;gt; {
        setTimeout(() =&amp;gt; {
            resolve(666);
        }, 3000);
    });
}

function App() {
    const [num, setNum] = useState(1);

    useEffect(() =&amp;gt; {
        getData().then((data: any) =&amp;gt; {
            setNum(data);
        });
    }, []);

    return (
        &amp;lt;div className=&quot;App&quot;&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; setNum((prevNum) =&amp;gt; prevNum + 1)}&amp;gt;
                {num}
            &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;useLayoutEffect&lt;/h1&gt;
&lt;p&gt;在 react 中 useLayoutEffect 和 useEffect 是差不都的，在绝大多数情况下，但是事实上是有一定的区别&lt;/p&gt;
&lt;p&gt;useEffect 会在渲染内容更新到 dom 上后执行，不会阻塞 dom 的更新
useLayoutEffect 会在渲染更新到 dom 之前就执行，会阻塞 dom 的更新&lt;/p&gt;
&lt;h1&gt;useReducer&lt;/h1&gt;
&lt;p&gt;用 useState 是直接修改值，如果想在修改值之前，执行一些操作，可以使用 useReducer&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function App() {

  interface Data {
    result: number,
  }

  interface Action {
    type: &apos;add&apos; | &apos;minus&apos;,
    num: number
  }

  function reducer(state: Data, action: Action) {
    switch (action.type) {
      case &apos;add&apos;:
        return {
          result: state.result + action.num
        }
      case &apos;minus&apos;:
        return {
          result: state.result - action.num
        }
    }
    return state
  }

  const [res, dispatch] = useReducer&amp;lt;Reducer&amp;lt;Data,Action&amp;gt;&amp;gt;(reducer, { result: 0 })

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; {
        dispatch({ type: &apos;add&apos;, num: 1 })
      }}&amp;gt;{res.result }&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;useRef&lt;/h1&gt;
&lt;p&gt;useRef 用来获取 dom 节点，或者获取组件的实例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function App() {
    const inputRef = useRef &amp;lt; HTMLInputElement &amp;gt; null;

    useEffect(() =&amp;gt; {
        inputRef.current?.focus();
    }, []);

    return (
        &amp;lt;div className=&quot;App&quot;&amp;gt;
            &amp;lt;input type=&quot;text&quot; ref={inputRef} /&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;forwardRef + useImperativeHandle&lt;/h1&gt;
&lt;p&gt;如果想把子组件的 ref 传递给父组件使用，可以使用 forwardRef 和 useImperativeHandle&lt;/p&gt;
&lt;p&gt;比如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { useRef } from &quot;react&quot;;
import &quot;./App.css&quot;;

const childrenComponent: React.ForwardRefRenderFunction&amp;lt;HTMLInputElement&amp;gt; = (
    props,
    ref
) =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;input ref={ref}&amp;gt;&amp;lt;/input&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

const Wraped = React.forwardRef(childrenComponent);

function App() {
    const ref = useRef &amp;lt; HTMLInputElement &amp;gt; null;

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Wraped ref={ref} /&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-29-react-2/01.png&quot; alt=&quot;切图01&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;useContext&lt;/h1&gt;
&lt;p&gt;跨任意组件传递数据，一般都会使用useContext来完成&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { createContext, useContext } from &apos;react&apos;;
import &apos;./App.css&apos;;

const Context = createContext(&quot;test&quot;)

const Components:React.FC = () =&amp;gt; {
  const value = useContext(Context)
 return &amp;lt;div&amp;gt;Context的值是 {value}&amp;lt;/div&amp;gt;
}

function App() {
  return &amp;lt;Context.Provider value=&apos;222&apos;&amp;gt;
    &amp;lt;div&amp;gt;&amp;lt;Components/&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/Context.Provider&amp;gt; 
}

export default App;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-29-react-2/02.png&quot; alt=&quot;切图02&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-29-react-2/03.png&quot; alt=&quot;切图03&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;memo + useMemo + useCallback&lt;/h1&gt;
&lt;p&gt;memo : 只有组件的props发生变化的时候，才会触发组件的重新渲染，否则总是返回缓存中的结果&lt;/p&gt;
&lt;p&gt;useMemo : 一般通过减少不必要的复杂计算来优化性能,类似于计算属性&lt;/p&gt;
&lt;p&gt;useCallback : 一般用于给子组件传递回调函数时，减少子组件的渲染次数，从而优化性能。&lt;/p&gt;
</content:encoded></item><item><title>Windows情况下，Vite配置https证书</title><link>https://blog.loli.wang/blog/2024-03-25-vite-https/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-03-25-vite-https/doc/</guid><description>Windows情况下，Vite配置https证书</description><pubDate>Mon, 25 Mar 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h1&gt;出现问题&lt;/h1&gt;
&lt;p&gt;和往常一样，工作日工作没做完，默默地的周六来公司加班修改项目代码，周末写没人打扰写代码真的是太爽了！，感觉一切顺顺利利没什么问题。下班走的时候还心里想像 &lt;strong&gt;我写的代码就是诗&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;直到周一，同事问我开发环境有BUG，我还在想我写的代码怎么可能会有Bug，迅速查看问题，发现问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;crypto.randomUUID()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我只是生成个UUID，有什么问题？？？&lt;/p&gt;
&lt;p&gt;本地开发环境下看上去也没问题啊，我对我同事产生了质疑，我以为是他浏览器的问题，但是我发现开发环境没问题，发现localhost环境地址上确实没问题，内网地址上确实这个问题，&lt;/p&gt;
&lt;p&gt;打开MDN 翻看API兼容程度，以及正常的使用操作&lt;/p&gt;
&lt;p&gt;tips
&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我明白了问题所在，但是又不想使用第三方库，考虑在本地配置下Https&lt;/p&gt;
&lt;h2&gt;vite 修改&lt;/h2&gt;
&lt;p&gt;Vite 默认使用的是http 需要修改下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vite.config.ts
server: {
    https:true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# package.json
&quot;scripts&quot;:{
    &quot;dev&quot;: &quot;vite --host&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;mkcert&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;mkcert&lt;/strong&gt; 是一个创建自签名证书的工具&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 全局安装mkcert
npm install -g mkcert
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编写相应的sh脚本,方便初始化证书&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

DIR=&quot;./cert&quot;

echo &quot;开始执行portmax cert脚本&quot;

if [ ! -e $DIR ]
then 
     mkdir -p $DIR
fi

cd $DIR

mkcert create-ca 

mkcert create-cert --domains localhost 127.0.0.1 192.168.0.61
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/03.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;windows 安装证书&lt;/h2&gt;
&lt;p&gt;打开 cert文件夹 ，双击ca.crt 和 cert.crt , 点开后选择 &lt;strong&gt;&quot;安装证书&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/04.png&quot; alt=&quot;切图4&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/05.png&quot; alt=&quot;切图5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是看我们生成的证书 windows 还是不受信任的，我们需要将此证书启用信任&lt;/p&gt;
&lt;h3&gt;windows 将CA证书启用信任&lt;/h3&gt;
&lt;p&gt;使用 &quot; win+R &quot; 打开运行对话框。输入 &quot;mmc&quot; 然后回车&lt;/p&gt;
&lt;p&gt;选择 左上角 &quot;文件&quot;，选择管理或删除管理单元&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/06.png&quot; alt=&quot;切图6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;strong&gt;&quot;管理或删除管理单元&quot;&lt;/strong&gt; 选择证书，并&lt;strong&gt;添加证书&lt;/strong&gt;，回到&lt;strong&gt;mmc&lt;/strong&gt;控制台&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/07.png&quot; alt=&quot;切图7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/08.png&quot; alt=&quot;切图8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;选择 &lt;strong&gt;受信任的根证书颁发机构&lt;/strong&gt;，然后选择&lt;strong&gt;证书&lt;/strong&gt;, 右键选择&lt;strong&gt;任务&lt;/strong&gt;， 导入我们项目文件里的证书&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/09.png&quot; alt=&quot;切图9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/10.png&quot; alt=&quot;切图10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;接下来就重回上面的步骤 双击ca.crt 和 cert.crt ，然后点击安装证书，可以看到当前证书是受信任的了，导入选择 &lt;strong&gt;本地计算机&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/11.png&quot; alt=&quot;切图11&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;vite中验证证书是否生效&lt;/h3&gt;
&lt;p&gt;修改 vite.config.ts&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server: { 
 https: {
   cert: fs.readFileSync(path.join(__dirname, &apos;keys/cert.crt&apos;)),
   key: fs.readFileSync(path.join(__dirname, &apos;keys/cert.key&apos;)),
 },
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/12.png&quot; alt=&quot;切图12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;验证成功&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/13.png&quot; alt=&quot;切图13&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-03-25-vite-https/14.png&quot; alt=&quot;切图14&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;后续。&lt;/h3&gt;
&lt;p&gt;虽然可以本地可以https了，但是去同事那边访问，也要去他那边导入证书 这样会显得很麻烦，因为是开发环境，做不了什么很好的操作，所以还是不用浏览器的生成 UUID的API了 。&lt;/p&gt;
&lt;p&gt;还有，如果是为了本地测试，并且是Vite的话，完全没必要使用 &lt;strong&gt;mkcert&lt;/strong&gt; , 有相关的Vite插件。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vitejs/vite-plugin-basic-ssl&quot;&gt;vite-plugin-basic-ssl&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;相关资料&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vitejs/vite-plugin-basic-ssl&quot;&gt;vite-plugin-basic-ssl&lt;/a&gt;
&lt;a href=&quot;https://www.npmjs.com/package/mkcert#create-a-certificate&quot;&gt;mkcert&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>tailwindcss中iconify的使用</title><link>https://blog.loli.wang/blog/2024-02-04-iconifycss/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-02-04-iconifycss/doc/</guid><description>iconify的使用</description><pubDate>Sun, 04 Feb 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;最近正在业余时间编写 Vue 的组件库, 设计完按钮组件后正准备设计 Icon 组件，看大佬 antfu 巨佬的支持过的 iconify 组件库，发现 iconify 的使用方法和我之前使用的 iconfont、font-awesome 等图标库的使用方法完全一致，于是决定使用 iconify 来替换 iconfont、font-awesome 等图标库。&lt;/p&gt;
&lt;h2&gt;iconify 的介绍&lt;/h2&gt;
&lt;p&gt;iconify 是基于 SVG 的图标库，可以使用 iconify 来替换 iconfont、font-awesome 等图标库。拥有市面上所有的流行图标库。&lt;/p&gt;
&lt;h2&gt;个人使用场景&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;tailwindcss&lt;/code&gt; 和 &lt;code&gt;vue3&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add @iconify-json/mdi-light --D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装 tailwind 插件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add @iconify/tailwind -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tailwindcss 中使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { addDynamicIconSelectors } from &apos;@iconify/tailwind&apos;

{
    &quot;plugins&quot;: [
        addDynamicIconSelectors()
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码中使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
 &amp;lt;span class=&quot;icon-[mdi-light--home]&quot; /&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功演示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-02-04-iconifycss/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;如果你不喜欢 tailwindcss 的插件，也可以使用原生的代码或者vue代码使用&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// vue代码
import { Icon } from &apos;@iconify/vue&apos;

&amp;lt;Icon icon=&quot;mdi-light:home&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;相关资料&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://iconify.design/&quot;&gt;iconify 官方文档&lt;/a&gt;
&lt;a href=&quot;https://github.com/iconify/iconify/tree/main/plugins/tailwind&quot;&gt;tailwindcss 如何使用 入口文档&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Vue3 打包组件库出错 Cannot read properties of null (reading &apos;isCE&apos;)</title><link>https://blog.loli.wang/blog/2024-01-24-vuecomponentserrorisce/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-01-24-vuecomponentserrorisce/doc/</guid><description>Vue3 打包组件库出错 Cannot read properties of null (reading &apos;isCE&apos;)</description><pubDate>Wed, 24 Jan 2024 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;写了套 React 的组件库后，觉着无法满足自己的需求，又删了重写了。。 从搭建 Monorepo，到编写组件测试的时候就发现了问题。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-24-vueComponentsErrorIsCe/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原因&lt;/p&gt;
&lt;p&gt;造成这个问题是因为又 2 个不同的 vue 版本，因为我打包的组件库内有个 vue 的版本，而靶场用例上也有个 vue 版本，2 个 vue 版本产生冲突，导致这个错误&lt;/p&gt;
&lt;p&gt;解决方案&lt;/p&gt;
&lt;p&gt;组件库内部屏蔽 vue 导出,修改 vite.config.ts&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vite.config.ts

rollupOptions: {
    external: [&apos;vue&apos;],
    output: {
      globals: {
        vue: &apos;Vue&apos;,
      },
    },
 },

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功解决&lt;/p&gt;
</content:encoded></item><item><title>TS 中 keyof 和 typeof</title><link>https://blog.loli.wang/blog/2024-01-20-typeofkeyof/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-01-20-typeofkeyof/doc/</guid><description>TS 中 keyof 和 typeof</description><pubDate>Sun, 21 Jan 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;keyof&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;keyof&lt;/code&gt; 是一个类型操作符, 用于获取一个类型所有的键值(属性名的)联合类型&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface Todo {
  name:string,
  age:number,
  address:string
}

// 通过keyof 获取类型的key
type todoKeys = keyof Todo

// 获得类型的key
const todoKeys:todoKeys = &apos;name&apos;

// todoKeys的类型为 &apos;naem&apos; | &apos;age&apos; | &apos;address&apos;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;typeof&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;typeof&lt;/code&gt; 是一个是一个类型查询操作符,用于获取一个值,或者表达式,用于获取已有数据的数据类型&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const todo = {
  title: &apos;Todo&apos;,
  dataIndex: &apos;todo&apos;,
  key: &apos;todo&apos;,
}

// 等于复制了 todo 的类型
type TodoType = typeof todo

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-20-typeofkeyof/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>TS 中的常用类型 (经常需要使用的)</title><link>https://blog.loli.wang/blog/2024-01-20-typescriptuitls/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-01-20-typescriptuitls/doc/</guid><description>TS 中的常用类型 (经常需要使用的)</description><pubDate>Sat, 20 Jan 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;本篇介绍的是 Typescript 中经常使用的一些类型,这些类型一般都是做一个项目中经常需要用上的.&lt;/p&gt;
&lt;h3&gt;Required&amp;lt;Type&amp;gt;&lt;/h3&gt;
&lt;p&gt;将类型&lt;code&gt;非必填&lt;/code&gt;改为&lt;code&gt;必填&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface Todo {
    username?: string;
    age?: number;
    address?: string;
}

// 正常使用情况
const todo: Todo = {};

// 整个类型都必须填写
const todoa: Required&amp;lt;Todo&amp;gt; = {};

// 配合Pick(选取) 选取单个属性必须填写
const todob: Required&amp;lt;Pick&amp;lt;Todo, &quot;username&quot;&amp;gt;&amp;gt; = {};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-20-typescriptUitls/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Readonly&amp;lt;Type&amp;gt;&lt;/h3&gt;
&lt;p&gt;将类型改为&lt;code&gt;只读&lt;/code&gt;,类型中所有属性都无法被重新分配&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface Todo {
    title: string;
}
// todo 改为了只读属性
const todo: Readonly&amp;lt;Todo&amp;gt; = {
    title: &quot;魔王Blog - 一个前端程序员的个人博客&quot;,
};

// 如果重新分配将会报错
todo.title = &quot;魔王Blog - 一个前端程序员的个人博客&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-20-typescriptUitls/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Record&amp;lt;Keys, Type&amp;gt;&lt;/h3&gt;
&lt;p&gt;构建一个&lt;code&gt;对象类型&lt;/code&gt; ,属性键名为&lt;code&gt;Keys&lt;/code&gt;,属性值为&lt;code&gt;Type&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 简单实用 对象结构为 key value 并且类型都为string
const value: Record&amp;lt;string, string&amp;gt; = {
    name: &quot;魔王&quot;,
    age: &quot;18&quot;,
    address: &quot;地狱&quot;,
};

interface Info {
    name: string;
    age: number;
    address: string;
}

type CityName = &quot;北京&quot; | &quot;上海&quot; | &quot;广州&quot; | &quot;深圳&quot;;

// 复杂实用 对象结构为 key value 并且类型都为Info 且key只能为CityName
const info: Record&amp;lt;CityName, Info&amp;gt; = {
    北京: {
        name: &quot;魔王&quot;,
        age: 18,
        address: &quot;地狱&quot;,
    },
    上海: {
        name: &quot;魔王&quot;,
        age: 18,
        address: &quot;地狱&quot;,
    },
    广州: {
        name: &quot;魔王&quot;,
        age: 18,
        address: &quot;地狱&quot;,
    },
    深圳: {
        name: &quot;魔王&quot;,
        age: 18,
        address: &quot;地狱&quot;,
    },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-20-typescriptUitls/03.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Pick(选取,选择) 看上一篇文章&lt;/h3&gt;
&lt;p&gt;https://blog.loli.wang/blog/2024-01-18-tspickomit/doc/index.html&lt;/p&gt;
&lt;h3&gt;Omit(排除) 上一篇文章&lt;/h3&gt;
&lt;p&gt;https://blog.loli.wang/blog/2024-01-18-tspickomit/doc/index.html&lt;/p&gt;
&lt;h3&gt;Exclude&amp;lt;UnionType, ExcludedMembers&amp;gt; (排除联合类型中指定类型)&lt;/h3&gt;
&lt;p&gt;Exclude 是一个工具类型,用于&lt;code&gt;排除&lt;/code&gt; &lt;code&gt;联合类型&lt;/code&gt;指定的类型,得到新的类型&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 原始联合类型
type CityName = &quot;北京&quot; | &quot;上海&quot; | &quot;广州&quot; | &quot;深圳&quot;;

// 通过Exclude排除类型 &apos;北京&apos; | &apos;上海&apos;  得到新的联合类型
type City = Exclude&amp;lt;CityName, &quot;北京&quot; | &quot;上海&quot;&amp;gt;;

// 如果实用原有的联合类型已排除过的 会报错
const city: City = &quot;北京&quot;;

// 如果使用未排除的联合类型 会正常
const cityName: CityName = &quot;北京&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-20-typescriptUitls/04.png&quot; alt=&quot;切图4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果联合类型比较复杂 是对象类型的,也同样&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Shape =
  | { kind: &quot;circle&quot;; radius: number }
  | { kind: &quot;square&quot;; x: number }
  | { kind: &quot;triangle&quot;; x: number; y: number };
 
type T3 = Exclude&amp;lt;Shape, { kind: &quot;circle&quot; }&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ReturnType&amp;lt;Type&amp;gt;&lt;/h3&gt;
&lt;p&gt;构造一个由 &lt;code&gt;function&lt;/code&gt; 的返回的数据组成的&lt;code&gt;类型&lt;/code&gt;, 用来指定函数返回的数据类型为什么格式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 指定返回值类型为 string
type Fun = ReturnType&amp;lt;() =&amp;gt; string&amp;gt;

// 正常写法
function fn(): Fun {
  return &quot;123&quot;
}

// 错误写法
function fn2(): Fun {
  return 123
}

// 指定返回值类型为对象
type Fun2 = ReturnType&amp;lt;()=&amp;gt;{
  name:string,
  age:number
}&amp;gt;

// 正常写法
function fn3(): Fun2 {
  return {
    name:&quot;123&quot;,
    age:123
  }
}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-20-typescriptUitls/05.png&quot; alt=&quot;切图5&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;字符串操作类型&lt;/h3&gt;
&lt;h4&gt;Uppercase&amp;lt;StringType&amp;gt;&lt;/h4&gt;
&lt;p&gt;用于将字符串类型的所有字符转换为大写。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type UppercaseString = Uppercase&amp;lt;&apos;hello&apos;&amp;gt;; // 类型为 &apos;HELLO&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Lowercase&amp;lt;StringType&amp;gt;&lt;/h4&gt;
&lt;p&gt;用于将字符串类型的所有字符转换为小写。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type LowercaseString = Lowercase&amp;lt;&apos;WORLD&apos;&amp;gt;; // 类型为 &apos;world&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Capitalize&amp;lt;StringType&amp;gt;&lt;/h4&gt;
&lt;p&gt;用于将字符串类型的首字母转换为大写。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type CapitalizedString = Capitalize&amp;lt;&apos;typescript&apos;&amp;gt;; // 类型为 &apos;Typescript&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Uncapitalize&amp;lt;StringType&amp;gt;&lt;/h4&gt;
&lt;p&gt;用于将字符串类型的首字母转换为小写。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type UncapitalizedString = Uncapitalize&amp;lt;&apos;JavaScript&apos;&amp;gt;; // 类型为 &apos;javaScript&apos;

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;相关参考文档&lt;/h4&gt;
&lt;h3&gt;相关文档&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys&quot;&gt;typescriptlang&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>TS 中的 Pick 和 Omit</title><link>https://blog.loli.wang/blog/2024-01-18-tspickomit/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-01-18-tspickomit/doc/</guid><description>TS 中的 Pick 和 Omit</description><pubDate>Thu, 18 Jan 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;Pick （选取）&lt;/h3&gt;
&lt;p&gt;官方介绍 : &lt;code&gt;通过从 interface 中选取属性集 Keys  来构造类型指定的Type。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 原接口
interface Todo {
  title: string,
  description: string,
  date: string
}

// 构建的新接口 只选取目标接口中的title 和 date
type TodoNewPreView = Pick&amp;lt;Todo, &quot;title&quot; | &quot;date&quot;&amp;gt;

const Todo: TodoNewPreView = {
  title: &quot;魔王のBlog&quot;,
  date: &quot;2023-01-01&quot;,
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;证明我们从 Todo 这个接口中选取了 &lt;code&gt;title&lt;/code&gt; 和 &lt;code&gt;date&lt;/code&gt; 这两个属性,形成了新的类型接口&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-18-tspickomit/01.png&quot; alt=&quot;切图1&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2024-01-18-tspickomit/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Omit （排除,省略）&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Omit&lt;/code&gt; 和 &lt;code&gt;Pick&lt;/code&gt; 是相反的,是这个接口中使用的过程中,有不想使用的参数,可以进行屏蔽掉&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-18-tspickomit/03.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到,我们这里Todo这个类型接口,有个必须接收的&lt;code&gt;username&lt;/code&gt;这个参数,如果我们需要用到这个类型,并且不想改变原有的使用类型的话,就需要使用Omit&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-18-tspickomit/04.png&quot; alt=&quot;切图4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;总结,&lt;code&gt;Pick&lt;/code&gt; 和 &lt;code&gt;Omit&lt;/code&gt; 都是比较Ts中比较实用的工具,使用和利用好会给自己带来更大的收获&lt;/p&gt;
&lt;h3&gt;相关文档&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys&quot;&gt;typescriptlang&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>dart-sass 和 node-sass 的区别以及使用</title><link>https://blog.loli.wang/blog/2024-01-08-draksass/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2024-01-08-draksass/doc/</guid><description>dart-sass 和 node-sass 的区别以及使用</description><pubDate>Mon, 08 Jan 2024 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;序&lt;/h3&gt;
&lt;p&gt;在我们开发场景中，如果在&lt;code&gt;node&lt;/code&gt;环境中使用&lt;code&gt;node-sass&lt;/code&gt;，经常会有&lt;code&gt;python&lt;/code&gt;版本安装问题，也有和&lt;code&gt;node版本&lt;/code&gt;是否和&lt;code&gt;node-sass&lt;/code&gt;版本有关联关系，还强制安装NET Framework版本，总而言之会出现各种问题，尤其是老项目居多...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2024-01-08-draksass/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;什么是Dart Sass&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Dart Sass&lt;/code&gt; 是官方力推的 &lt;code&gt;sass&lt;/code&gt; 继任者。官网主推项目，用来替代&lt;code&gt;node-sass&lt;/code&gt;，由&lt;code&gt;dart&lt;/code&gt;开发，对css新特性支持的更加全面，最大的好处是没有&lt;code&gt;node版本之间的依赖关系&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;node-sass 和 dart-sass的区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;node-sass
node-sass 是顺应前端工程化而出现的node包，是使用c++实现的libSass的封装，因为使用c++编译，所以速度比较快，前期支撑住了前端工程化的潮流。前端工程化项目的大功臣，遗留缺点有很多，比如硬性要求对应node版本之类的奇怪的错误&lt;/li&gt;
&lt;li&gt;dart-sass
dart-sass 是基于dart开发的，他的速度更快， 更易于安装，并且可以编译成纯JavaScript，并且对css新特性获得了更好的支持&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;后面使用预处理器，直接使用dart-sass啦。毕竟要适应新的时代
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>简单使用 tsup 进行打包</title><link>https://blog.loli.wang/blog/2023-12-30-tsup/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-12-30-tsup/doc/</guid><description>简单使用 tsup 进行打包</description><pubDate>Sat, 30 Dec 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;起因。&lt;/h3&gt;
&lt;p&gt;最近在编写自己的组件库，使用&lt;code&gt;monorepo&lt;/code&gt;模式进行开发,&lt;code&gt;monorepo&lt;/code&gt;开发将utils单独作为工具库，原本打算使用tsc编译出去的，无意中发现了打包工具。&lt;/p&gt;
&lt;h3&gt;介绍&lt;/h3&gt;
&lt;p&gt;tsup 是一个基于 ESBuild 实现在零配置的情况下快速打包 Typescript 模块的库，支持 .ts、.tsx的转换。它基于esbuild，但是同时也选择融合其他的构建工具共同参与，弥补了esbuild的不足。&lt;/p&gt;
&lt;h3&gt;安装使用&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# pnpm 安装 同样也可以npm yarn 等
pnpm add tsup -D

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;packages.json 文件中声明&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;build&quot;:&quot;tsup&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单使用可以在命令后面加输出路径等 也可以添加配置文件去使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;build&quot;:&quot;tsup src/index.ts src/cli.ts&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以使用配置文件，根目录下面新建一个 &lt;code&gt;tsup.config.ts&lt;/code&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
import { defineConfig } from &apos;tsup&apos;

export default defineConfig({
  entry: [&apos;./src/index.ts&apos;], // 打包入口
  splitting: false,
  sourcemap: true,
  clean: true,
})

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
    &quot;build&quot;: &quot;tsup --format esm,cjs,iife --config tsup.config.ts&quot;
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里也可以定义打包目录之类的，详情查看官网。配置信息比较完全
https://tsup.egoist.dev/#what-can-it-bundle&lt;/p&gt;
</content:encoded></item><item><title>UNI-APP 离线打包配置</title><link>https://blog.loli.wang/blog/2023-12-28-uniapppackage/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-12-28-uniapppackage/doc/</guid><description>UNI-APP 离线打包配置</description><pubDate>Thu, 28 Dec 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;起因&lt;/h3&gt;
&lt;p&gt;公司有2款不同的PDA设备&lt;code&gt;霍尼韦尔&lt;/code&gt;，&lt;code&gt;SUNMI&lt;/code&gt; 这2款pda设备，在有一次项目升级后，发现&lt;code&gt;SUNMI&lt;/code&gt;这种设备型号的PDA设备热更新失败，准确的说不是热更新是失败 是热更新下来的APK 安装时无法兼容&lt;/p&gt;
&lt;p&gt;如图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/01.jpg&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;引发原因&lt;/h3&gt;
&lt;p&gt;项目一直是用的离线打包，&lt;code&gt;Hbuilder X&lt;/code&gt; 这款开发工具，之前使用云打包的时候会自动调用本地的离线打包，可是有次开发工具更新后，云打包不触发离线打包了。本来并不在意这件事情，毕竟就是打包个安卓包而已，并不在意，打完包就发布了。。&lt;/p&gt;
&lt;p&gt;后续就出现问题了，&lt;code&gt;霍尼韦尔&lt;/code&gt;设备正常，&lt;code&gt;SUNMI&lt;/code&gt;设备异常，前者设备安卓版本9.0, 后者安卓设备版本7.0，版本不兼容了。低版本的无法安装。&lt;/p&gt;
&lt;p&gt;本地模拟器同样的也是这个问题，低版本的无法安装，安装上也是白屏。&lt;/p&gt;
&lt;p&gt;经过排查，使用离线打包的就没有问题。&lt;/p&gt;
&lt;h3&gt;如何配置离线打包&lt;/h3&gt;
&lt;p&gt;开始准备&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.java.com/zh-CN/download/&quot;&gt;安装 jdk 8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.google.cn/studio?hl=zh-cn&quot;&gt;安装 Android Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nativesupport.dcloud.net.cn/AppDocs/download/android.html&quot;&gt;下载 UNIAPP 离线 SDK&lt;/a&gt; (注意，下载的SDK版本必须和HubilderX版本对应上)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.dcloud.net.cn/pages/app/list&quot;&gt;UNIAPP 开发者中心账号&lt;/a&gt; (注意，提前实名认证啥破玩意都整完)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;获取配置信息&lt;/h4&gt;
&lt;p&gt;在UNIAPP项目中有个 &lt;code&gt;mainifest.json&lt;/code&gt; 文件 ，里面会看到一些基础配置信息，其中有个&lt;code&gt;UNIAPP 引用标识&lt;/code&gt;, 这个也同样是APPID, 也关系到开发中心对应的项目编号。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;生成本地打包资源&lt;/h4&gt;
&lt;p&gt;生成本地打包资源备用 后续放置到 Android Studio 内使用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/03.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/04.png&quot; alt=&quot;切图4&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;将本地资源文件夹移动至离线SDK中&lt;/h4&gt;
&lt;p&gt;将本地资源包复制到该目录下&lt;/p&gt;
&lt;p&gt;HBuilder-Integrate-AS\simpleDemo\src\main\assets\apps&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/05.png&quot; alt=&quot;切图4&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Android Studio 导入SDK的示例项目&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/06.png&quot; alt=&quot;切图4&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;修改dcloud_control.xml 中的 appid&lt;/h4&gt;
&lt;p&gt;在新打开的编辑器中 , 找到 simpleDemo/src/main/assets/data/dcloud_control.xml&lt;/p&gt;
&lt;p&gt;进行修改&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/07.png&quot; alt=&quot;切图7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;修改为之前 &lt;code&gt;mainifest.json&lt;/code&gt; 定义的APPID，还有当前准备发布的 &lt;code&gt;版本号&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;生成安卓签名&lt;/h4&gt;
&lt;p&gt;安卓打正式包必须要的，参考官方文档&lt;/p&gt;
&lt;p&gt;https://ask.dcloud.net.cn/article/35777&lt;/p&gt;
&lt;h4&gt;前往开发者中心生成AppKey&lt;/h4&gt;
&lt;p&gt;在我们使用 HbuilderX 后获取Appid后，登录账号,会在后台系统生成相应的应用，进入UNIAPP的开发者中心,选择相应的应用，获取离线的APPKEY&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/08.png&quot; alt=&quot;切图8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/09.png&quot; alt=&quot;切图9&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;修改包名 配置appKey&lt;/h4&gt;
&lt;p&gt;在 AndroidManifest.xml 中修改包名，在&lt;code&gt;开发者中心&lt;/code&gt;可以看到相关的包名，一般都是uni.xxx 开头的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/10.png&quot; alt=&quot;切图10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在下方找到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &amp;lt;meta-data
            android:name=&quot;dcloud_appkey&quot;
            android:value=&quot;239898****************b2d9dbcbf2bc&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;android:value 修改成自己的离线 appKey 即可&lt;/p&gt;
&lt;h4&gt;修改原有的默认APP图标还有打包出来的应用名&lt;/h4&gt;
&lt;p&gt;修改应用名 /simpleDemo/src/main/res/values/strings.xml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;resources&amp;gt;
    &amp;lt;string name=&quot;app_name&quot;&amp;gt;修改自己的应用名&amp;lt;/string&amp;gt;
&amp;lt;/resources&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;icon 启动图替换 /simpleDemo/src/main/res/drawable 中的 icon.png push.png splash.png 图片进行替换就好&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/11.png&quot; alt=&quot;切图10&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;最后尝试构建&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/12.png&quot; alt=&quot;切图12&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/13.png&quot; alt=&quot;切图13&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/14.png&quot; alt=&quot;切图14&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/15.png&quot; alt=&quot;切图15&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-12-28-uniappPackage/16.png&quot; alt=&quot;切图16&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;完成 ，，，总而言之，写UNIAPP的坑很多，如果不是赶时间的话，建议去使用Flutter 等框架，（--）
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>现代化 CSS 框架</title><link>https://blog.loli.wang/blog/2023-12-27-newcss/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-12-27-newcss/doc/</guid><description>新生代 CSS 框架</description><pubDate>Wed, 27 Dec 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;什么是原子化 css&lt;/h3&gt;
&lt;p&gt;原子 CSS 是一种 CSS 架构方法，它有利于小型、单一用途的类，其名称基于视觉功能。&lt;/p&gt;
&lt;p&gt;原子化 CSS 是一种 CSS 的架构方式，它倾向于小巧且用途单一的 class，并且会以视觉效果进行命名。&lt;/p&gt;
&lt;p&gt;例如 &lt;code&gt;.mt-10&lt;/code&gt; 代表 &lt;code&gt;margin-top: 10px&lt;/code&gt;，&lt;code&gt;.bg-red&lt;/code&gt; 代表 &lt;code&gt;background-color: red&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.mt-10 {
  margin-top: 10px;
}
.bg-red {
  background-color: red;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;为什么要使用原子化 css&lt;/h3&gt;
&lt;p&gt;原子化 CSS 有助于减少 CSS 的大小，因为它只使用了少量的 class，而不是大量的 class 和大量的 style 引入。优化了 CSS 的大小，也就优化了 CSS 的加载速度。&lt;/p&gt;
&lt;h3&gt;常用的 css 框架选择&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://unocss.dev/&quot;&gt;UnoCSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tachyons.io/&quot;&gt;Tachyons&lt;/a&gt;
....&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参考&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[重新构想原子化 CSS-antfu] https://antfu.me/posts/reimagine-atomic-css-zh&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Tailwind + Antd Css冲突解决方案(按钮颜色透明)</title><link>https://blog.loli.wang/blog/2023-12-14-tailwindbuttionope/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-12-14-tailwindbuttionope/doc/</guid><description>Tailwind + Antd Css冲突解决方案(按钮颜色透明)</description><pubDate>Thu, 14 Dec 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;问题引发&lt;/h2&gt;
&lt;p&gt;公司开展新项目，准备使用React，用React的主流框架肯定是Antd啦，为了防止公司样式污染以及结合了一些老项目的痛点，决心使用Tailwind来弥补这些问题，然后引发了按钮没有颜色的Bug 在原有Css中 Tailwind的优先级比Antd要高所以引发了这个问题&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-14-tailwindButtionOpe/02.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;tailwind.config.js 配置文件修改&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;将 preflight: false 修改为 false&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  /** @type {import(&apos;tailwindcss&apos;).Config} */
export default {
  content: [&apos;./src/**/*.{js,jsx,ts,tsx}&apos;],
  theme: {
    extend: { 
    }
  }
  // plugins: [],
  // corePlugins: {
  //   preflight: false
  // }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改 tailwind @layer样式&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;@tailwind base;
@tailwind components;
@tailwind utilities;


/* @layer base { 
  button, [type=&apos;button&apos;], [type=&apos;reset&apos;], [type=&apos;submit&apos;]
  { background-color: #3b82f6; 
  }
} */
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;全局修改 antd 全局样式&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;将自己的全局Css样式中处理这个这个引入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// global.css
:global(.ant-btn-primary) {
  background-color: #1677ff !important;
}
// 或者
.ant-btn-primary{
     background-color: #1677ff !important;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Directadmin 2222 开启 SSL </title><link>https://blog.loli.wang/blog/2023-12-05-directadmin2222ssl/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-12-05-directadmin2222ssl/doc/</guid><description>Directadmin 2222 开启 SSL</description><pubDate>Tue, 05 Dec 2023 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;太久没管理IDC.LA 网站了，这次上去，发现所有服务器面板的SSL都挂了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;真不是个合格的网站管理者
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;先启动LetsEncrypt&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在directadmin.conf中启用letsencrypt = 1选项，顺便先把ssl=0改为 ssl=1 ，使DA 2222使用SSL协议&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重启、build 相关组件&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;echo “action=directadmin&amp;amp;value=restart” &amp;gt;&amp;gt; /usr/local/directadmin/data/task.queue; /usr/local/directadmin/dataskq d2000

cd /usr/local/directadmin/custombuild
./build rewrite_confs

cd /usr/local/directadmin/custombuild
./build update
./build letsencrypt
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;为DA开启&lt;/h4&gt;
&lt;p&gt;先获得域名证书，设定跳转，使其生效。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/local/directadmin/scripts/letsencrypt.sh request_single us1.yunloli.com 4096

/usr/local/directadmin/directadmin set ssl_redirect_host us1.yunloli.com
service directadmin restart
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;相关资料&lt;/h3&gt;
&lt;p&gt;https://help.directadmin.com/item.php?id=15&lt;/p&gt;
&lt;p&gt;https://help.directadmin.com/item.php?id=648&lt;/p&gt;
&lt;p&gt;https://help.directadmin.com/item.php?id=629&lt;/p&gt;
</content:encoded></item><item><title>ViteSSG 搭建博客 01 (ssg项目项目搭建)</title><link>https://blog.loli.wang/blog/2023-12-13-ssgblog01/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-12-13-ssgblog01/doc/</guid><description>ViteSSG 搭建博客 01 (ssg项目项目搭建)</description><pubDate>Tue, 05 Dec 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;项目准备&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 强烈建议使用 pnpm
npm i -g pnpm 

# 安装主要项目依赖
pnpm add vue typescript less vue-router

pnpm add @unhead/vue @vitejs/plugin-vue vite vite-plugin-pages vite-ssg -D
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;开始&lt;/h3&gt;
&lt;p&gt;Node 版本需要高于 14 以上&lt;/p&gt;
&lt;p&gt;示例以多页应用为例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// package.json
{
    &quot;script&quot;:{
        &quot;dev&quot;:&quot;vite&quot;,
        &quot;build&quot;:&quot;vite-ssg build&quot;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// main.ts
import { ViteSSG } from &quot;vite-ssg&quot;;
import App from &quot;./App.vue&quot;;
import routes from &quot;~pages&quot;;

export const createApp = ViteSSG(
  App,
  { routes },
  ({ app, router, routes, isClient, initialState }) =&amp;gt; {}
);

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// vite.config.ts
import { defineConfig } from &quot;vite&quot;;
import vue from &quot;@vitejs/plugin-vue&quot;;
import Pages from &quot;vite-plugin-pages&quot;;

export default defineConfig({
  plugins: [vue(), Pages({ extensions: [&quot;vue&quot;, &quot;md&quot;] })],
  ssgOptions: {
    script: &quot;async&quot;,
    formatting: &quot;prettify&quot;,
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编写页面,在 pages 目录下新建文件夹 index 然后建立目录 index.vue&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/pages/index/index.vue
&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;Index&quot;&amp;gt;
    &amp;lt;div&amp;gt;Index&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;{{ index }}&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;{{ index2 }}&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script lang=&quot;tsx&quot; setup&amp;gt;
import { ref } from &quot;vue&quot;;

const index = ref(&quot;idx&quot;);
const index2 = ref(&quot;Leo&quot;);
&amp;lt;/script&amp;gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后执行 &lt;code&gt;pnpm build&lt;/code&gt; 即可生成静态构建的文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-12-13-ssgblog01/01.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Node版本管理Volta的使用</title><link>https://blog.loli.wang/blog/2023-11-27-volatsetup/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-11-27-volatsetup/doc/</guid><description>Node版本管理Volta的使用</description><pubDate>Mon, 27 Nov 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;为什么要有Volta&lt;/h2&gt;
&lt;p&gt;一个项目组有多个前端现象，使用的Node版本都不一样，并且有前端工程师一个人管理多个不同node版本的前端项目需要频繁的切换Node版本&lt;/p&gt;
&lt;h2&gt;使用 Volta&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;官网：https://volta.sh/
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# 安装volta 
curl https://get.volta.sh | bash

# 安装node
volta install node

# 指定node版本安装
volta install node@14.15.5

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在前端项目中配置进行自动切换&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// package.json

&quot;volta&quot;: {
  &quot;node&quot;: &quot;12.20.2&quot;,
  &quot;yarn&quot;: &quot;1.19.2&quot;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过volta 安装好指定的node版本后可以做到打开当前的项目的命令行，自动切换node版本&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-11-27-volatSetup/02.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-11-27-volatSetup/03.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>简单虚拟列表 + 无限滚动</title><link>https://blog.loli.wang/blog/2023-11-17-vuevirtuallist/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-11-17-vuevirtuallist/doc/</guid><description>简单虚拟列表 + 无限滚动</description><pubDate>Fri, 17 Nov 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;为什么要有虚拟列表这个东西&lt;/h2&gt;
&lt;p&gt;很多时候前端被迫被逼着接收上百条，上千条，上万条数据（因为需求的缘故），没有相关经验的前端会直接直接赋值渲染上去。
但是这样子操作是有很大的弊端的。轻则让浏览器卡顿，重则浏览器崩溃。更严重的老板直接过来骂人。&lt;/p&gt;
&lt;p&gt;也就是说，我们不能一股脑的直接赋值，要有合理的方案。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;后端给出分页 (是最好的处理方式 有什么东西是不能让后端给前端压力的呢)&lt;/li&gt;
&lt;li&gt;前端将数据切成小块 进行分页 (但是可能业务被迫需要更加直观的展示不让使用分页)&lt;/li&gt;
&lt;li&gt;前端通过搜索去过滤数据再渲染 (可以存入到缓存里,读取缓存内的数据,但是会有过滤后还是有大量数据的问题，和缓存有大小问题等限制，是不合理的方案)&lt;/li&gt;
&lt;li&gt;虚拟列表 (通过滚动条只渲染可见部分，随着滚动加载最新的数据，非常合理的方式)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过以上解释，虚拟列表是最佳合理的方案，实例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div id=&quot;app&quot;&amp;gt;
    &amp;lt;div
      class=&quot;virtual-list-container&quot;
      ref=&quot;listContainerRef&quot;
      @scroll=&quot;handleScroll&quot;
    &amp;gt;
      &amp;lt;div class=&quot;list-phantom&quot;&amp;gt;
        &amp;lt;div
          class=&quot;list-item&quot;
          v-for=&quot;(item, index) in state.visibleItem&quot;
          :key=&quot;index&quot;
        &amp;gt;
          虚拟化列表数据 {{ item.name }}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang=&quot;tsx&quot; setup&amp;gt;
import { ref, reactive, onMounted } from &quot;vue&quot;;

let state = reactive({
  visibleItem: [] as any,
  visibleItemCount: 20, // 可见区域显示多少条
  count: 800000, // 需要生成的数据总条数
});

// 数据生成函数
const genertteItems = (count: number): any[] =&amp;gt; {
  const items: any = [];
  Array.from({ length: count }).forEach((item, index: number) =&amp;gt; {
    items.push({ id: index, name: `${index}名称` });
  });
  return items;
};

// 关键代码
// 通过ref获取带有滚动条的dom
const listContainerRef = ref&amp;lt;any&amp;gt;(null);

// 滚动事件
const handleScroll = () =&amp;gt; {
  // 滚动容器
  const container = listContainerRef.value;
  // 获取容器当前至容器顶部距离多远
  const scrollTop = container?.scrollTop || 0;
  // 获取容器可视区域总高度
  const scrollHeight = container?.scrollHeight || 0;
  // 获取容器的总高度
  const containerHeight = container?.clientHeight || 0;
  // 如果滚动距离加上可是高度大于总高度 说明到了容器底部
  if (scrollTop + containerHeight &amp;gt;= scrollHeight) {
    // 滚动到底部加载更多数据
    loadMoreData();
  }
};

// 加载更多函数
const loadMoreData = () =&amp;gt; {
  const startIndex = state.visibleItem.length;
  const endIndex = startIndex + state.visibleItemCount;
  const newItems = genertteItems(state.count).slice(startIndex, endIndex);
  state.visibleItem = [...state.visibleItem, ...newItems];
};

onMounted(() =&amp;gt; {
  handleScroll(); // 确保组件挂载后初始化一次数据\
});
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;less&quot;&amp;gt;
.list-item {
  height: 80px;
  border: 1px solid red;
}
.virtual-list-container {
  overflow-y: auto;
  height: 400px; /* 容器高度 */
  position: relative;
}
.list-phantom {
  position: absolute;
  width: 100%;
  pointer-events: none;
}
&amp;lt;/style&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现效果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-11-17-vuevirtualList/1.gif&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>dependencies 和 devDependencies 的区别(回忆录1)</title><link>https://blog.loli.wang/blog/2023-11-11-dependenciesanddevdependencies/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-11-11-dependenciesanddevdependencies/doc/</guid><description>dependencies 和devDependencies 的区别</description><pubDate>Sat, 11 Nov 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;回忆录1: 一次给别人解决BUG引发的问题&lt;/h2&gt;
&lt;p&gt;事情是这样的，一个小伙伴问我一个问题。项目写了个插件，导出到NPM中了，但是通过npm install 下来无法使用，仔细检查，发现依赖都是装载在 &lt;strong&gt;devDependencies&lt;/strong&gt; 中&lt;/p&gt;
&lt;h3&gt;理解&lt;/h3&gt;
&lt;p&gt;区别:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dependencies 用于生产环境
devDependencies 用于开发环境，打包成npm插件后无法获取内容
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拓展:&lt;/p&gt;
&lt;p&gt;npm install xxx -g 表示全局安装，通常用于安装脚手架 yarn pnpm webpack 等工具&lt;/p&gt;
&lt;p&gt;npm install xxx –save(-S) 表示本地安装，会被加至dependencies部分&lt;/p&gt;
&lt;p&gt;npm install xxx –save-dev(-D) 表示本地安装，会被加至
devDependencies部分&lt;/p&gt;
&lt;p&gt;npm install会默认下载dependencies和devDependencies中的所有依赖包&lt;/p&gt;
&lt;p&gt;1.如webpack、html-webpack-plugin等工具包就安装在devDependencies开发环境下，&lt;/p&gt;
&lt;p&gt;项目部署到开发环境所必须的依赖包则安装在dependencies生产环境下。
在项目编译时dependencies、devDependencies里的依赖其实没有影响，最重要的区别体现在:&lt;/p&gt;
&lt;p&gt;npm包发布的时候，其他的开发者可以从你发布的npm包中下载dependencies里的依赖包，而不能下载devDependencies里的内容。&lt;/p&gt;
</content:encoded></item><item><title>类型“ImportMeta”上不存在属性“glob” </title><link>https://blog.loli.wang/blog/2023-11-11-viteerrorglob/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-11-11-viteerrorglob/doc/</guid><description>类型“ImportMeta”上不存在属性“glob”</description><pubDate>Sat, 11 Nov 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;类型“ImportMeta”上不存在属性“glob”&lt;/h2&gt;
&lt;p&gt;如果直接使用import.meta.glob，vscode会报类型ImportMeta上不存在属性“glob”的错误，需要在tsconfig文件下添加类型定义vite/client&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;types&quot;: [&quot;vite/client&quot;]
  }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>rust的编程概念</title><link>https://blog.loli.wang/blog/2023-11-08-rust003/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-11-08-rust003/doc/</guid><description>rust的编程概念</description><pubDate>Wed, 08 Nov 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;变量&lt;/h2&gt;
&lt;p&gt;声明变量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 普通变量
let x = 5;  // 无法修改 类似常量

let mut x = 5; // 增加mut 变为可修改

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数据类型&lt;/p&gt;
&lt;p&gt;长度 有符号类型 无符号类型
8 位 i8 u8
16 位 i16 u16
32 位 i32 u32
64 位 i64 u64
128 位 i128 u128
arch isize usize&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 整型
let guess=22222;
// 浮点型
let x: f64 = 2.0;
// 布尔型
let t = true;
// 字符串类型
let c = &apos;z&apos;;
// 元组类型
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
// 数组类型
let a = [1, 2, 3, 4, 5];

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 函数
fn another_function() {
    println!(&quot;Another function.&quot;);
}
// 带参函数
fn another_function(x: i32) {
    println!(&quot;The value of x is: {}&quot;, x);
}
// 带返回值的函数
fn main() {
    let x = plus_one(5);

    println!(&quot;The value of x is: {}&quot;, x);
}

fn plus_one(x: i32) -&amp;gt; i32 {
    x + 1
    //  也可以  return x+1

}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;循环&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 循环
loop {
        println!(&quot;again!&quot;);
}
// 条件循环
let mut number = 3
while number != 0 {
    println!(&quot;{}!&quot;, number); 
    number -= 1;
}
// for 循环
let a = [10, 20, 30, 40, 50];

for element in a {
    println!(&quot;the value is: {}&quot;, element);
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>使用rust建立一个简单的web服务器</title><link>https://blog.loli.wang/blog/2023-11-07-rust002/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-11-07-rust002/doc/</guid><description>rust开发环境安装</description><pubDate>Tue, 07 Nov 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// main.rs

use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::fs;

fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 1024];
    stream.read(&amp;amp;mut buffer).unwrap();

    let response = &quot;HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n&quot;;
    let contents = fs::read_to_string(&quot;html/index.html&quot;).unwrap();
    let response = format!(&quot;{}{}&quot;, response, contents);

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

fn main() {
    let listener = TcpListener::bind(&quot;127.0.0.1:3030&quot;).unwrap();
    println!(&quot;Server is listening on port 3030&quot;);

    for stream in listener.incoming() {
        let stream = stream.unwrap();
        handle_client(stream);
    }
}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-11-07-rust002/001.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>给表妹的 Astro Blog 搭建流程</title><link>https://blog.loli.wang/blog/2023-11-6-astrosetup/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-11-6-astrosetup/doc/</guid><description>给表妹的 Astro Blog 搭建流程</description><pubDate>Mon, 06 Nov 2023 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;表妹上大学了，理工类的学科，看来以后绝对优秀人才呢。因此需要一个blog来记录学习过程。&lt;/p&gt;
&lt;h3&gt;为什么使用 Astro&lt;/h3&gt;
&lt;p&gt;当然和我是前端工程师是分不开的关系，本来也是推荐使用一些PHP的blog程序，但是需要一些服务器相关的知识，也并非省时省力。顺带还需要给她传授一点 Git 相关的知识，所以对于正在使用astro 的我当然首选 &lt;strong&gt;Astro&lt;/strong&gt; 啦 , 也可以讲下web相关的知识。&lt;/p&gt;
&lt;h3&gt;在此之前&lt;/h3&gt;
&lt;p&gt;安装好nodejs:&lt;/p&gt;
&lt;p&gt;https://nodejs.org/en&lt;/p&gt;
&lt;p&gt;熟悉md的编写:&lt;/p&gt;
&lt;p&gt;https://www.math.pku.edu.cn/teachers/lidf/docs/Rbook/html/_Rbook/markdown.html&lt;/p&gt;
&lt;h3&gt;在 github 克隆一个 astro 项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
# 首推自己的个人github项目

https://github.com/itmowang/sxq-astro
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-11-6-astrosetup/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;github推出的新功能 &lt;strong&gt;repository templates&lt;/strong&gt;  方便快速使用该代码库。使用后会在自己的本地仓库看到该项目&lt;/p&gt;
&lt;h3&gt;编写文章&lt;/h3&gt;
&lt;p&gt;克隆到自己的私人仓库后 通过 git 工具拉取自己仓库的代码代码。&lt;/p&gt;
&lt;p&gt;「文章」存放于&lt;code&gt;src/content/blog&lt;/code&gt;路径内，可自行清空后新建；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-11-6-astrosetup/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;文章内固定格式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: &quot;文章标题&quot;   # 文章标题
pubDate: 2021-03-27 09:45:11  # 发布日期
description: &quot;文章描述。&quot;  # 文章描述
heroImage: &quot;http://img.blog.loli.wang/2023-8-21-cfworkerProxy/01.png&quot; # 主页预览图
---
    # mdx
    文章内容
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;发布&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt; # 通过npm发布至线上
 # 强烈要求 Node 版本在 18x + 
    npm i -g pnpm

    pnpm install
    
    pnpm build

    然后将docs目录内的文件部署到服务器上，同样的也可以直接使用 github 的 pages 根据自己的喜好来。

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>rust开发环境安装以及Cargo</title><link>https://blog.loli.wang/blog/2023-11-6-rust001/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-11-6-rust001/doc/</guid><description>rust开发环境安装</description><pubDate>Mon, 06 Nov 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;我正在学习准备rust&lt;/h2&gt;
&lt;p&gt;学习资源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    官方文档 
    https://doc.rust-lang.org/book/
    第三方中文文档
    https://rustwiki.org/zh-CN/book/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;根据官网提供的exe 安装进行下一步就好&lt;/p&gt;
&lt;h3&gt;hello world!&lt;/h3&gt;
&lt;p&gt;建立一个 &lt;strong&gt;project&lt;/strong&gt; 文件夹 创建文件 main.rs 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# main.rs

fn main() {
    println!(&quot;Hello, world!&quot;);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存文件 在命令行中输入命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
rustc main.rs

# 会自动生成 main.exe main.pdb 文件 exe 是主要可执行文件 pdb是调试信息文件 方便调试

./main.exe

# Hello, world!

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Rust 是预编译语言&lt;/h3&gt;
&lt;p&gt;Rust 是一门预编译(ahead-of-time compiled)语言，这意味着你可以编译一个程序，将编译后的可执行文件给别人，即使他们没有安装 Rust 也可以运行程序。&lt;/p&gt;
&lt;h3&gt;Cargo&lt;/h3&gt;
&lt;p&gt;使用 Cargo 创建一个新项目&lt;/p&gt;
&lt;p&gt;查看cargo的版本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cargo --version
# cargo 1.73.0 (9c4383fb5 2023-08-26)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建项目&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cargo new hello_cargo
# Created binary (application) `hello_cargo` package
cd hello_cargo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目录结构&lt;/p&gt;
&lt;p&gt;src 目录内有个 main.rs 作为主入口&lt;/p&gt;
&lt;p&gt;Cargo.toml 为 Cargo的配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Cargo.toml
[package]
name = &quot;hello_cargo&quot;
version = &quot;0.1.0&quot;
edition = &quot;2021&quot;

[dependencies]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;构建并运行 Cargo 项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cargo build # 构建项目
cargo run # 一步构建并运行项目
cargo check # 检查项目是否可以编译通过
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看来可以开始Rust之旅了&lt;/p&gt;
</content:encoded></item><item><title>docker 配置 nginx 环境</title><link>https://blog.loli.wang/blog/2023-10-13-dockerdevops/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-10-13-dockerdevops/doc/</guid><description>docker 配置 nginx 环境</description><pubDate>Fri, 13 Oct 2023 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;如果是windows环境下，记得安装上 ubuntu ，然后安装好docker Desktop&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-13-dockerDevops/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;搜索nginx镜像&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;
打开ubuntu的命令行。

 docker search nginx # 搜索nginx镜像
 
 NAME: 镜像仓库源的名称
 DESCRIPTION: 镜像的描述
 OFFICIAL: 是否为 docker 官方发布
 stars: 类似 Github 里面的 star
 AUTOMATED: 自动构建。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-13-dockerDevops/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;拉取最新NGINX Docker镜像&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;
docker pull nginx:latest # latest 代表最新版本的意思 同样也可以在这里制定版本

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-13-dockerDevops/03.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;查看镜像&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt; docker images # 查看所有已经拉取和自己的镜像
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-13-dockerDevops/04.png&quot; alt=&quot;切图4&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;使用执行镜像创建一个新的容器&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;
docker run -it nginx

语法：docker run [OPTIONS] IMAGE [COMMAND] [ARG…]

OPTIONS说明：
-a stdin: 指定标准输入输出内容类型，可选 STDIN/STDOUT/STDERR 三项；
-d: 后台运行容器，并返回容器ID；
-i: 以交互模式运行容器，通常与 -t 同时使用；
-P: 随机端口映射，容器内部端口随机映射到主机的端口
-p: 指定端口映射，格式为：主机(宿主)端口:容器端口
-t: 为容器重新分配一个伪输入终端，通常与 -i 同时使用；
–name=&quot;nginx-lb&quot;: 为容器指定一个名称；
–dns 8.8.8.8: 指定容器使用的DNS服务器，默认和宿主一致；
–dns-search example.com: 指定容器DNS搜索域名，默认和宿主一致；
-h “mars”: 指定容器的hostname；
-e username=“ritchie”: 设置环境变量；
–env-file=[]: 从指定文件读入环境变量；
–cpuset=&quot;0-2”&quot; or --cpuset=&quot;0,1,2&quot;: 绑定容器到指定CPU运行；
-m :设置容器使用内存最大值；
–net=&quot;bridge&quot;: 指定容器的网络连接类型，支持 bridge/host/none/container: 四种类型；
–link=[]: 添加链接到另一个容器；
–expose=[]: 开放一个端口或一组端口；
–volume , -v: 绑定一个卷


# 使用指令

docker run -it --name=&quot;testNgx&quot; nginx

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-13-dockerDevops/05.png&quot; alt=&quot;切图5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到我们这里有了新的我们创建的容器。可以对容器启动关闭销毁等快捷的操作。&lt;/p&gt;
&lt;p&gt;当然 我们是nginx 要配置个ip和端口，方便本地测试&lt;/p&gt;
&lt;h2&gt;实践测试&lt;/h2&gt;
&lt;p&gt;写上第二个测试 ，可以看到我们正常创建了个新的容器，并8080端口映射了80端口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 指令
docker run --name nginx-test -p 8080:80 -d nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-13-dockerDevops/06.png&quot; alt=&quot;切图6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-13-dockerDevops/07.png&quot; alt=&quot;切图7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-13-dockerDevops/08.png&quot; alt=&quot;切图8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;改变docker nginx的conf配置改为自己的配置就可以啦&lt;/p&gt;
</content:encoded></item><item><title>记一次折腾 Surge (github pages替代品) </title><link>https://blog.loli.wang/blog/2023-10-08-surge/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-10-08-surge/doc/</guid><description>记一次折腾 Surge (github pages替代品)</description><pubDate>Sun, 08 Oct 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-08-surge/05.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;看到老师发这玩意，不管是什么，总得折腾一番 😁&lt;/p&gt;
&lt;h2&gt;如何使用 Surge.sh 免费部署静态网站&lt;/h2&gt;
&lt;p&gt;Surge.sh 是一个免费的静态网站主机，您可以通过命令行与之交互。它可以快速轻松地在线获取新站点和应用程序，无论是手动还是作为 CI 构建过程的一部分。以下是如何开始使用该服务。类似github 的pages功能&lt;/p&gt;
&lt;h2&gt;第一次运行&lt;/h2&gt;
&lt;p&gt;我们假设您已经有了要部署到 Web 的文件目录。如果还没有，请创建一个新文件夹，添加一个 index.html 和一些简单的入门内容。&lt;/p&gt;
&lt;p&gt;Surge 是通过Npm发布的JavaScript的引用程序 ，在这之前必须安装Node.js ，再然后使用npm安装Surge&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install --global surge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后执行指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
surge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后会要求你注册/登录一个相关的账号，填写完后，会要求让你填写需要发布的目录，填写完成后自动发布&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-08-surge/02.png&quot; alt=&quot;切图2&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-10-08-surge/03.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;补充，如何绑定域名&lt;/h2&gt;
&lt;p&gt;只需要在需要发布的 Web 文件目录，建立一个CNAME文件，内部填写需要绑定的域名即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-08-surge/07.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发布成功&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-10-08-surge/06.png&quot; alt=&quot;切图4&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>问答：DPlayer关键帧预览图生成思路 </title><link>https://blog.loli.wang/blog/2023-9-dplayer-thumbnails/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-9-dplayer-thumbnails/doc/</guid><description>问答：DPlayer关键帧预览图生成思路</description><pubDate>Tue, 26 Sep 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;网友学习java工作了，公司领导给了个需求，要播放视频同事鼠标悬停到进度条位置有预览效果&lt;/p&gt;
&lt;p&gt;类似
&lt;img src=&quot;http://img.blog.loli.wang/2023-9-dplayer-thumbnails/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;网友深思熟虑的考虑播放器使用 &lt;strong&gt;DPlayer&lt;/strong&gt; ，但是把官网上的Demo复制下来，放上自己的视频后，发现预览图都是白屏。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-dplayer-thumbnails/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我看了下 &lt;strong&gt;DPlayer&lt;/strong&gt; 文档后，发现是有个属性 &lt;strong&gt;thumbnails&lt;/strong&gt; 专门存放时间帧的预览图。给出的建议是自己去通过视频工具去切时间帧的图片。&lt;/p&gt;
&lt;h3&gt;重新理解需求&lt;/h3&gt;
&lt;p&gt;发现他的需求并没有描述清楚，常常因为需求没有描述清楚而导致做一些多余的事情&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-dplayer-thumbnails/03.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;嗯.... 他是java工程师，在工作中前端的活也要干。。但是居然想着前端切图，，，前端一个弱语言单线程，就算依赖Web Workers 也很不好吧。应用层并不适合处理视频这种场景。&lt;/p&gt;
&lt;h3&gt;进一步建议和最后的方案&lt;/h3&gt;
&lt;p&gt;看见 &lt;strong&gt;DPlayer&lt;/strong&gt; 作者有个库&lt;strong&gt;DPlayer-thumbnails&lt;/strong&gt;，专门处理视频预览图片的，是基于&quot;fluent-ffmpeg&quot;这个库来写的，可惜是Node的，他是java不太适用。但是ffmpeg 连 Node都有相关的库，java这么好的生态会没有? 毕竟不是java 慢慢引导下吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-dplayer-thumbnails/04.png&quot; alt=&quot;切图4&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-9-dplayer-thumbnails/05.png&quot; alt=&quot;切图5&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-9-dplayer-thumbnails/06.png&quot; alt=&quot;切图6&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-9-dplayer-thumbnails/07.png&quot; alt=&quot;切图7&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-9-dplayer-thumbnails/08.png&quot; alt=&quot;切图8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最终结束。。。虽然没帮到什么实际上的，至少思路给引导了下。我想应该足够了&lt;/p&gt;
&lt;h3&gt;后续&lt;/h3&gt;
&lt;p&gt;后一天问了下网友情况，如何生成 ，他打算的方案是循环生成图片再去拼接，我看官方写的插件Node版本的也是这样的，后来发现他的能力并不能做到相关的操作&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ffmpeg -y -i &quot;test.mp4&quot; -frames 1 -vf &quot;thumbnail=n=100,scale=-1:320,tile=4X6:padding=10:color=white&quot; thumbnail.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-dplayer-thumbnails/09.png&quot; alt=&quot;切图9&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>原型链的理解</title><link>https://blog.loli.wang/blog/2023-9-23-prototype/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-9-23-prototype/doc/</guid><description>原型链的理解</description><pubDate>Sat, 23 Sep 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;程序媛妹妹问我面试题，怎么好好的解释原型链(或许她问的什么是原型链)，嘛！毕竟写vue都要理解这玩意捏。&lt;/p&gt;
&lt;h2&gt;理解&lt;/h2&gt;
&lt;p&gt;每一个对象都有自己的原型链，有自己的内置对象，有自己的prototype 和 proto 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;prototype&lt;/strong&gt; 显式原型，指向一个构造函数
** &lt;strong&gt;proto&lt;/strong&gt; ** 隐式原型，是对象的属性&lt;/p&gt;
&lt;p&gt;如果想从对象中查找某个值，如果没有找到他们就会从原型实例，向上查找，直到找到Object.prototype 为止。&lt;/p&gt;
&lt;p&gt;示例解释&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
 Object.prototype.abc = 222;

 // 建立构造函数1
 function Test1Fun() {}

 // 构造函数中写入原型
 Test1Fun.prototype.test = &quot;111&quot;;

 // 建立构造函数2
 function Test2Fun() {}
  
 // 将Test2Fun的原型对象指向一个Test1Fun的实例，实现原型链继承
 Test2Fun.prototype = new Test1Fun();

// 创建一个Test2Fun的实例对象testObj
 var testObj = new Test2Fun();

//  从原型链中查找 test 
 console.log(testObj.test);
//  从原型链中查找 abc
 console.log(testObj.abc);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是否发现vue中 Vue是一个构造函数，我们经常在上面挂载原型，在所有地方都能方便使用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-23-prototype/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>vite 多页面应用配置</title><link>https://blog.loli.wang/blog/2023-9-22-multappliction/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-9-22-multappliction/doc/</guid><description>vite 多页面应用配置</description><pubDate>Fri, 22 Sep 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;一个程序媛妹妹问我vite如何配置多应用，我仔细想了想，什么是多应用？？，后来经过gpt搜索，百度资料，哦~ 原来叫做 &lt;strong&gt;多页面应用&lt;/strong&gt; ，然后这就去研究。&lt;/p&gt;
&lt;p&gt;想打包出来的目录结构是这样的:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-22-multAppliction/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;妹子是使用rollup配置的，看了下代码， 写的一个插件，导入到vite插件里面。看上去并不合理，也并不友善，同样的查看了vite文档 有相关的处理方案&lt;/p&gt;
&lt;h2&gt;自己动手&lt;/h2&gt;
&lt;p&gt;看文档，只需要根目录定义好相应的目录结构，在rollupOptions中配置好相应的入口，就可以成功&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-22-multAppliction/03.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是我们需要动态的读取目录，更好的读取应用, 所以需要使用fs去读取下文件夹内文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import fs from &quot;fs&quot;;

//...

  // 加载所有子目录
  const appliction = fs.readdirSync(&quot;./appliction&quot;);
  // 合并成input入口数组
  const input = Object.fromEntries(
    appliction.map((item) =&amp;gt; [
      item,
      path.resolve(`${__dirname}/appliction/${item}/index.html`),
    ])
  );

//...

// 获取出来的目录结构
// {
//     app1:&apos;xxx/xxx/appliction/app1/index.html&apos;
// }


//...
  return {
    base: &quot;./&quot;,
    outDir: &quot;dist&quot;,
    plugins: [vue()],
    build: {
      target: &quot;es2015&quot;,
      cssCodeSplit: true,
      assetsDir: &quot;appliction&quot;,
      rollupOptions: {
        input: {
          main: path.resolve(__dirname, &quot;index.html&quot;),
          ...input,
        }, 
      },
  },
//...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候我们就打包出来了相应的目录，结构&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-22-multAppliction/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不过有个缺点，目录结构都在assets内，想将打包出来的js文件css文件放到他们自己的文件结构内，例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    - dist
        - page1
            - static
            - index.html    
        - page2
            - static
            - index.html 
    index.html

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以需要配置下rollup的输出格式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
 return {
    base: &quot;./&quot;,
    outDir: &quot;dist&quot;,
    plugins: [vue()],
    build: {
      target: &quot;es2015&quot;,
      cssCodeSplit: true,
      assetsDir: &quot;appliction&quot;,
      rollupOptions: {
        input: {
          main: path.resolve(__dirname, &quot;index.html&quot;),
          ...input,
        },
        output: {
          entryFileNames: &quot;appliction/[name]/static/[name].[hash].js&quot;,
          chunkFileNames: &quot;appliction/[name]/static/[name].[hash].js&quot;,
          assetFileNames: &quot;appliction/[name]/static/[name].[hash].[ext]&quot;,
        },
      },
    },


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就打包出了我们所需要的目录结构&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-22-multAppliction/01.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;参考文档：
&lt;a href=&quot;https://github.com/itmowang/mw-cli/tree/master/packages/mw-create/template/multi-application-template&quot;&gt; 我的github仓库 &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[vite官方] (https://vitejs.dev/guide/build.html#multi-page-app)&lt;/p&gt;
</content:encoded></item><item><title>package.json 中 exports 的理解</title><link>https://blog.loli.wang/blog/2023-9-18-exportswhy/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-9-18-exportswhy/doc/</guid><description>package.json 中 exports 的理解</description><pubDate>Mon, 18 Sep 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h3&gt;起因&lt;/h3&gt;
&lt;p&gt;学习某低代码平台代码，看&lt;strong&gt;packages&lt;/strong&gt;中有个exports，指向了不同的js文件，疑惑为什么这么做&lt;/p&gt;
&lt;h3&gt;解惑&lt;/h3&gt;
&lt;p&gt;在通常情况下，我们会使用 main:&quot;index.js&quot; 指向单独指向一个所抛出的 &lt;strong&gt;exports&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是使用过 exports 后，会生成一个对应关系，抛出不同的模块，并消除替换了 &lt;strong&gt;mian&lt;/strong&gt; 字段的默认行为 例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; {
  &quot;exports&quot;: {
    &quot;.&quot;: &quot;./main.js&quot;,
    &quot;./core/test&quot;: &quot;./test.js&quot;,
  }
}

// 使用时可以使用如此的对应关系 分模块去使用

// &quot;.&quot;: &quot;./main.js&quot;,
import test from &apos;package&apos;

// &quot;./core/test&quot;: &quot;./test.js&quot;,
import test from &apos;package/core/test&apos;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;根据模块语法 引用不同的文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&quot;exports&quot;: {
    &quot;.&quot;: {
      &quot;import&quot;: &quot;./lib/esm/index.mjs&quot;,
      &quot;require&quot;: &quot;./main.js&quot;
    },
    &quot;./core/test&quot;: &quot;./test.js&quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;后续疑问&lt;/h3&gt;
&lt;p&gt;exports 为抛出不同的模块,那么ts中的tsconfig 的paths 和 vite的  resolve.alias 区别是什么？&lt;/p&gt;
</content:encoded></item><item><title>什么是swc</title><link>https://blog.loli.wang/blog/2023-9-13-why-swc/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-9-13-why-swc/doc/</guid><description>什么是swc</description><pubDate>Sun, 10 Sep 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;什么是 swc&lt;/h2&gt;
&lt;p&gt;在swc没出现之前，一直是使用 &lt;strong&gt;babel&lt;/strong&gt; 来处理转换旧版本 &lt;strong&gt;JavaScript&lt;/strong&gt; 的工具，也可以给&lt;strong&gt;typescript&lt;/strong&gt;使用，ast解析树，压缩等等.. 是前端工程换不可缺少的一环。&lt;/p&gt;
&lt;p&gt;而 &lt;strong&gt;Babel&lt;/strong&gt; 是JavaScript编写的，是JavaScript的编译器，SWC 同样也是JavaScript的编译器，不过是用Rust重写的，比Javascript快的多。性能得到了大幅度提升，目前很多常用的工具库都正在用rust重写。所以还不学Rust？&lt;/p&gt;
&lt;h2&gt;swc 的使用&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 命令

pnpm init

pnpm add @swc/core @swc/cli
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# async.js

const fetch = require(&quot;node-fetch&quot;);

async function getData() {
    let res = await fetch(&quot;https://jsonplaceholder.typicode.com/todos/1&quot;);
    let json = await res.json();
    console.log(&apos;data&apos;, json);
}

getData();

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置spack&lt;/h3&gt;
&lt;p&gt;在根目录创建 spack.config.js 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { config } = require(&quot;@swc/core/spack&quot;);


module.exports = config({
 // 入口
 entry: __dirname + &quot;/index.js&quot;,
 // 输出到哪儿
 output: {
   path: __dirname + &quot;/dist&quot;,
 },
 module:{}
 // swc编译配置
//   options: {
//     // 编译规则
//     jsc: {
//         parser: {}, // 解析配置
//         target: &quot;es5&quot;, // 转义目标
//         // ... 等可以去官网看具体配置 https://swc.rs/docs/configuration/swcrc
//     },
//     // 输出文件配置
//     module: {
//       type: &quot;commonjs&quot;,
//       strict: false,
//       strictMode: true,
//       lazy: false,
//       noInterop: false,
//       ignoreDynamic: false,
//     },
//   },
});

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;更改执行脚本&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# package.json

 &quot;scripts&quot;: {
   &quot;build&quot;: &quot;spack&quot;
 }

# 执行
pnpm build

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-13-why-swc/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-13-why-swc/03.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;参考文档：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://swc.rs/docs/usage/bundling&quot;&gt;SWC - 官方文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;或许准备些一个自己的swc插件后面持续记录&lt;/p&gt;
</content:encoded></item><item><title>mini-webpack - 学习 - 废弃已经学习了概念后续补充</title><link>https://blog.loli.wang/blog/2023-9-9-mini-webpack/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-9-9-mini-webpack/doc/</guid><description>mini-webpack - 学习</description><pubDate>Sat, 09 Sep 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;准备&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    fs # 操作文件
    @babel/parser # 就是babel 只不过是一个api的使用方式 babel是一个编译工具 将新的es语言转换为老的es语言
    @babel/traverse # babel的插件 用来遍历文件树 
    ejs # 模板生成器 可以将模块里的东西
    babel-preset-env 
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>个人Astro主题 - SXQ</title><link>https://blog.loli.wang/blog/2023-9-04-astroxxq/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-9-04-astroxxq/doc/</guid><description>个人Astro主题 - SXQ</description><pubDate>Tue, 05 Sep 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;个人Astro主题 - SXQ&lt;/h2&gt;
&lt;p&gt;一款 Astro 的二次元风格捏，应该会有人喜欢的，正在慢慢完善中，欢迎指出问题提出意见。&lt;/p&gt;
&lt;p&gt;Github 项目地址 ：&lt;a href=&quot;https://github.com/itmowang&quot;&gt;https://github.com/itmowang/sxq-astro&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;使用方式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    # 强烈要求 Node 版本在 18x + 
    npm i -g pnpm

    pnpm install
    
    pnpm build

    然后将docs目录内的文件部署到服务器上，同样的也可以直接使用 github 的 pages 根据自己的喜好来。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;预览图&lt;/h3&gt;
&lt;p&gt;包含移动端 PC端 平板端的不同样式&lt;/p&gt;
&lt;p&gt;预览地址: https://blog2.loli.wang&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-04-astroxxq/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-04-astroxxq/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-04-astroxxq/03.jpg&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-04-astroxxq/04.jpg&quot; alt=&quot;切图4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-04-astroxxq/05.png&quot; alt=&quot;切图5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-04-astroxxq/06.jpg&quot; alt=&quot;切图6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-04-astroxxq/07.jpg&quot; alt=&quot;切图6&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>JS之多线程Web Worker API</title><link>https://blog.loli.wang/blog/2023-8-23-webworker/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-23-webworker/doc/</guid><description>JS之多线程Web Worker API</description><pubDate>Thu, 31 Aug 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;为什么需要 Web Worker&lt;/h2&gt;
&lt;p&gt;由于 JavaScript 语言用的是单线程，同一时刻只能做一件事， 如果又多个同步任务执行完之前，下方代码不会执行。造成了堵塞，页面无响应。&lt;/p&gt;
&lt;p&gt;但如果把这段代码放到 Web Worker 中执行，这段逻辑在执行中依然可以执行下面的代码，用户的操作也就可以正常响应了&lt;/p&gt;
&lt;h2&gt;什么是 Web Worker&lt;/h2&gt;
&lt;p&gt;Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。但是他不可以操作 dom&lt;/p&gt;
&lt;h2&gt;Web Worker 的使用限制&lt;/h2&gt;
&lt;h3&gt;同源限制&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;分配给 Worker 线程运行的脚本文件，必须与主线程的脚本文件同源。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;文件限制&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Worker 无法读取本地文件(&apos;file://&apos;) 他加载的协议必须要来自网络
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;通信限制&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Worker 线程所仔的全局对象，与主线程不一样，区别是
1. 无法读取主线程所在的网页 DOM 对象
2. 无法使用 document 、window 、parent 这些对象

Worker 线程和主线程不在同一个环境，他们不能直接通信，必须通过消息完成 `postMessage` 和 `onMessage`
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;脚本限制&lt;/h3&gt;
&lt;p&gt;Worker 线程不能使用 alert() 和 confirm() 方法 ，但是可以使用 ajax 和定时器 setTimeout 等 API&lt;/p&gt;
&lt;h2&gt;基本使用&lt;/h2&gt;
&lt;p&gt;创建线程&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const worker = new Worker(aURL, options);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;worker.postMessage&lt;/code&gt; 向 worker 内部作用域发送一共消息，消息可以由JavaScript任何对象组成&lt;/p&gt;
&lt;p&gt;&lt;code&gt;worker.terminate&lt;/code&gt;  立刻终止worker。该方法并不会等待woker去完成剩余的操作，会立马停止&lt;/p&gt;
&lt;p&gt;&lt;code&gt;worker.onmessage&lt;/code&gt; 接收到消息会立马触发message消息&lt;/p&gt;
&lt;p&gt;&lt;code&gt;worker.onerror&lt;/code&gt; 当worker出现运行中错误时。会被调用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
worker.addEventListener(&apos;error&apos;, function (e) {
    console.log(e.message) // 可读性良好的错误消息
    console.log(e.filename) // 发生错误的脚本文件名
    console.log(e.lineno) // 发生错误时所在脚本文件的行号
})

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;多数使用场景&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    - 打包压缩 另外开辟线程去处理
    - 导出图片太大 另外开辟线程去处理
    - 压缩图片等等
    ....
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相关文档&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers&quot;&gt;MDN - Web Workers API&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>css新单位dvh，svh 解释</title><link>https://blog.loli.wang/blog/2023-08-30-csssvhdvh/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-08-30-csssvhdvh/doc/</guid><description>css新单位dvh，svh 解释</description><pubDate>Wed, 30 Aug 2023 15:27:24 GMT</pubDate><content:encoded>&lt;h2&gt;什么是vh，vw&lt;/h2&gt;
&lt;p&gt;在css中 &lt;code&gt;vh&lt;/code&gt; 表示窗口视图的高度百分比,  &lt;code&gt;vw&lt;/code&gt; 表示窗口视图的宽度度百分比 , &lt;code&gt;1vh&lt;/code&gt; 等于窗口视图高度 &lt;code&gt;1%&lt;/code&gt;，而&lt;code&gt;1vw&lt;/code&gt; 代表视图宽度的&lt;code&gt;1%&lt;/code&gt; ，一般我们使用 &lt;code&gt;100vh&lt;/code&gt; 来自定义我们的视图大小的高度，固定为100%，他对响应式是非常适用，因为他会根据窗口的大小自动适应尺寸。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-08-30-cssSvhDvh/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;遇见问题&lt;/h3&gt;
&lt;p&gt;发现新项目模板平板上会出现白屏溢出出现滚动条！！！&lt;strong&gt;不止是高度，宽度也是这样 会出现一定的问题&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-08-30-cssSvhDvh/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;为了解决这个方案出现了新的单位 &lt;code&gt;dvh&lt;/code&gt; &lt;code&gt;svh&lt;/code&gt;, 他们在移动端兼容上面会经常用到。也会对移动端进行一定的兼容&lt;/p&gt;
&lt;p&gt;参考:
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/101&quot;&gt;MDN - CSS&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>编写一个自己的 Cli 脚手架工具</title><link>https://blog.loli.wang/blog/2023-8-28-nodeclitools/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-28-nodeclitools/doc/</guid><description>编写一个自己的 Cli 脚手架工具</description><pubDate>Mon, 28 Aug 2023 13:22:24 GMT</pubDate><content:encoded>&lt;p&gt;具体完整代码可以去查看我的github项目，&lt;a href=&quot;https://github.com/itmowang/mw-cli&quot;&gt;mw-cli - 一个Nodejs脚手架工具&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;战斗准备&lt;/h2&gt;
&lt;p&gt;建立一个项目文件 &lt;strong&gt;打开命令窗口&lt;/strong&gt; 我们所使用的一切为pnpm&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install pnpm -g

# 初始化项目指令
pnpm init

# 安装我们所需要会用到的依赖 作用去查去查阅下吧！
pnpm add typescript  // Typescript 支持 💕 个人习惯
pnpm add commander  
pnpm add copy-dir
pnpm add cross-spawn
pnpm add fs
pnpm add kolorist
pnpm add minimist
pnpm add ora
pnpm add ts-node

# 生成ts配置文件 tsconfig.json
tsc --init

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;启动文件&lt;/h2&gt;
&lt;p&gt;创建&lt;strong&gt;bin&lt;/strong&gt;目录作为启动路径，创建index.ts文件，注意 &lt;strong&gt;#!/usr/bin/env&lt;/strong&gt; 是一个常见的约定，用于告诉操作系统在运行脚本时使用指定的解释器。在这种情况下，/usr/bin/env 是一个可执行文件，它会在环境变量中查找指定的解释器（在这里是 node），并使用它来执行脚本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /bin/index.ts

#!/usr/bin/env node

// 引用cross-spawn
const spawn = require(&quot;cross-spawn&quot;);

// 调用pkgjson
const pkg = require(&quot;../../package.json&quot;);

// 引用commander
const program = require(&quot;commander&quot;);

// 引用创建项目逻辑
const createProject = require(&quot;../src/core/create&quot;);

//版本号 -v --version 选项
program.version(pkg.version, &apos;-v,--version&apos;)

// 创建项目
program.command(&quot;create &amp;lt;projectName&amp;gt;&quot;).description(&quot;创建项目&quot;).action((projectName: string) =&amp;gt; {
    createProject(projectName);
});

program.parse(process.argv);

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;创建项目配置文件&lt;/h2&gt;
&lt;p&gt;建立&lt;strong&gt;src&lt;/strong&gt;目录,创建&lt;strong&gt;config&lt;/strong&gt;目录和&lt;strong&gt;core&lt;/strong&gt;目录，config 文件夹我们用于存放配置文件，core 文件夹用于存放核心逻辑文件。&lt;/p&gt;
&lt;p&gt;config 文件夹内建立 repo.config.ts , 用于存放我们项目模板地址，原本有考虑是用github的项目地址的，但是个人认为并不适合。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /src/config/repo.config.ts

const path = require(&quot;path&quot;);

// 模板地址
const repoUrl =  path.resolve(__dirname, &quot;../../template&quot;);

module.exports = repoUrl;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并且创建 &lt;strong&gt;template&lt;/strong&gt; 目录,存放我们项目模板&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-28-nodeCliTools/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;创建模板逻辑&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# /src/core/create.ts 

// 读取vue模版
const vueCommand = require(&quot;./vueCreate&quot;);
// 读取node模版
const nodeCommand = require(&quot;./nodeCreate&quot;);

const createCommand = async (projectName = &quot;&quot;) =&amp;gt; {
  const {
    blue,
    cyan,
    green,
    lightBlue,
    lightGreen,
    lightRed,
  } = require(&quot;kolorist&quot;);

  const prompts = require(&quot;prompts&quot;);

  const questions = [
    {
      type: &quot;text&quot;,
      name: &quot;projectName&quot;,
      message: &quot;请输入你需要创建的项目名称&quot;,
      initial: projectName,
    },
    {
      type: &quot;text&quot;,
      name: &quot;projectVersion&quot;,
      message: &quot;请输入你需要创建的项目版本号&quot;,
      initial: &quot;1.0.0&quot;,
    },
    {
      type: &quot;select&quot;,
      name: &quot;projectTemplate&quot;,
      message: &quot;请选择你需要创建的项目模板&quot;,
      choices: [
        {
          title: &quot;Vue&quot;,
          value: &quot;vue&quot;,
          description: blue(&quot;vue类型的一些项目模版&quot;),
        },
        {
          title: &quot;React&quot;,
          value: &quot;react&quot;,
          description: blue(&quot;react类型的一些项目模版&quot;),
        },
        {
          title: &quot;Node&quot;,
          value: &quot;node&quot;,
          description: blue(&quot;node类型的一些项目模,例如配置好mysql或者orm框架&quot;),
        },
      ],
    },
  ];

  (async () =&amp;gt; {
    const response = await prompts(questions);
    const { projectName, projectVersion, projectTemplate } = response;
    if (projectTemplate === &quot;vue&quot;) {
      vueCommand(response);
    } else if (projectTemplate === &quot;node&quot;) {
      nodeCommand(response);
    } else {
      console.log(lightRed(&quot;暂时只支持vue模版&quot;));
    }
  })();
};

module.exports = createCommand;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;创建vue模板逻辑&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# /src/core/vueCreate.ts 

interface Create {
  projectName: string; // 项目名称
  projectVersion: string; // 项目版本号
  projectTemplate: string; // 项目模版
}

// 读取vue模版
const vueCreate = async (create: Create) =&amp;gt; {
  const prompts = require(&quot;prompts&quot;);
  const config = require(&quot;../config/repo.config&quot;);
  const {
    blue,
    cyan,
    green,
    lightBlue,
    lightGreen,
    lightRed,
  } = require(&quot;kolorist&quot;);

  const questions = [
    {
      type: &quot;select&quot;,
      name: &quot;vueTemplate&quot;,
      message: &quot;请选择你需要创建的项目模板&quot;,
      choices: [
        {
          title: &quot;template-vue3-ts-vite&quot;,
          value: &quot;template-vue3-ts-vite&quot;,
          description: green(&quot;vue3 + ts + vite 项目模版&quot;),
        },
        {
          title: &quot;template-vue3-webpack-ts&quot;,
          value: &quot;template-vue3-webpack-ts&quot;,
          description: green(&quot;vue3 + ts + webpack 项目模版&quot;),
        },
      ],
    },
  ];

  (async () =&amp;gt; {
    const response = await prompts(questions);
    const { vueTemplate } = response;
    // 走copy-dir 不走github了 没意义
    const copydir = require(&quot;copy-dir&quot;);
    // 进度
    const ora = require(&quot;ora&quot;);
    const spinner = ora(blue(&quot;下载模版中...&quot;));

    copydir.sync(
      `${config}/${vueTemplate}`,
      `./${create.projectName}`,
      {
        utimes: true, // keep add time and modify time
        mode: true, // keep file mode
        cover: true, // cover file when exists, default is true
        filter: function (stat: string, filepath: any, filename: string) {
          return true; // remind to return a true value when file check passed.
        },
      },
      function (err: Error) {
        if (err) throw err;
        spinner.fail(lightRed(`项目模版创建失败`));
      }
    );

    spinner.succeed(lightGreen(&quot;项目模版创建成功&quot;));
  })();
};

module.exports = vueCreate;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从这里整体逻辑就已经编写完了 但是我们还需要在package.json中配置一下,并且修改下tsconfig.json&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# tsconfig.json
# ...
 &quot;outDir&quot;: &quot;lib&quot;
# ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改输出目录为lib , 这样我们使用tsc命令编译后的文件就会输出到lib目录下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;mw-create&quot;,
  &quot;version&quot;: &quot;1.0.14&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;./lib/bin/index.js&quot;,
  &quot;bin&quot;: {
    &quot;mwcli&quot;: &quot;./lib/bin/index.js&quot;
  },
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;ts-node ./bin/index&quot;,
    &quot;build&quot;: &quot;node ./bin/copyDir.ts &amp;amp;&amp;amp; tsc&quot;,
    &quot;minor&quot;: &quot;npm version minor&quot;,
    &quot;major&quot;: &quot;npm version major&quot;,
    &quot;patch&quot;: &quot;npm version patch&quot;
  },
  &quot;publishConfig&quot;: {
    &quot;access&quot;: &quot;public&quot;
  },
  &quot;repository&quot;: {
    &quot;type&quot;: &quot;git&quot;,
    &quot;url&quot;: &quot;git+https://github.com/itmowang/mw-cli.git&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;devDependencies&quot;: {
    &quot;@types/node&quot;: &quot;^20.4.9&quot;
  },
  &quot;dependencies&quot;: {
    &quot;commander&quot;: &quot;^11.0.0&quot;,
    &quot;copy-dir&quot;: &quot;^1.3.0&quot;,
    &quot;cross-spawn&quot;: &quot;^7.0.3&quot;,
    &quot;download-git-repo&quot;: &quot;^3.0.2&quot;,
    &quot;fs&quot;: &quot;0.0.1-security&quot;,
    &quot;kolorist&quot;: &quot;^1.8.0&quot;,
    &quot;minimist&quot;: &quot;^1.2.8&quot;,
    &quot;ora&quot;: &quot;5.4.1&quot;,
    &quot;prompts&quot;: &quot;^2.4.2&quot;,
    &quot;ts-node&quot;: &quot;^10.9.1&quot;
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们需要注意的是  &quot;main&quot;: &quot;./lib/bin/index.js&quot;, main字段指定了程序的主入口文件，bin字段指定了程序的命令名，npm会在全局环境下建立一个软链接，指向我们的主入口文件，这样我们就可以在命令行中使用mwcli命令了。&lt;/p&gt;
&lt;h2&gt;测试一下&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;
npm link

mwcli create test

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-28-nodeCliTools/02.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;一切大功告成&lt;/p&gt;
&lt;p&gt;具体代码参考github仓库 https://github.com/itmowang/mw-cli&lt;/p&gt;
</content:encoded></item><item><title>JavaScript-内置对象-Reflect</title><link>https://blog.loli.wang/blog/2023-8-27-reflect/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-27-reflect/doc/</guid><description>JavaScript-内置对象-Reflect</description><pubDate>Thu, 24 Aug 2023 14:31:24 GMT</pubDate><content:encoded>&lt;h2&gt;Reflect&lt;/h2&gt;
&lt;p&gt;Reflect 是一个&lt;strong&gt;内置的对象&lt;/strong&gt;，他提供了一些方法来 &lt;strong&gt;操作对象&lt;/strong&gt; 的属性和方法， 并且它还提供 &lt;strong&gt;拦截 JavaScript 操作&lt;/strong&gt; 的方法。这些方法与 proxy handler (en-US) 的方法相同。Reflect 不是一个函数对象，因此它是不可构造的。&lt;/p&gt;
&lt;h4&gt;Reflect.get(target, propertyKey[, receiver]) 获取对象的属性值&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;
    const obj = {name:&apos;Mowang&apos;,age:20}

    console.log(Reflect.get(obj,&apos;name&apos;)) // 输出 Mowang

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Reflect.set(target, propertyKey, value[, receiver]) 设置对象的属性值&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    const obj = {name:&apos;Mowang&apos;,age:20}
    
    Reflect.set(obj,&apos;age&apos;,22)

    console.log(obj[&apos;age&apos;]) // 输出 22
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Reflect.has(target, propertyKey) 判断对象是否具有指定的属性&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    const obj = {name:&apos;Mowang&apos;,age:20}
    
    const hasName = Reflect.has(obj,&apos;name&apos;)

    console.log(hasName) // 输出 true

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Reflect.deleteProperty(target, propertyKey) 删除对象的属性&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    const obj = {name:&apos;Mowang&apos;,age:20}
    
    Reflect.deleteProperty(obj,&apos;age&apos;)

    console.log(obj) // 输出 { name:&apos;Mowang&apos; }

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Reflect.construct(target, argumentsList[, newTarget]) 使用给定的参数列表创建一个对象实例&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    class Person {
        constructor(name,age){
            this.name = name;
            this.age = age
        }
    }

    const args = [&apos;Mowang&apos;,20];
    const person = Reflect.construct(Person,args)
    
    console.log(person) // 输出 {name:&apos;Mowang&apos;,age:20}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这只是他的常用方法，他还有一些其他的函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    - Reflect.apply(target, thisArgument, argumentsList)  # 通过指定的参数列表发起对目标 (target) 函数的调用
    - Reflect.get(target, propertyKey[, receiver]) # 从已有的对象种读取去属性值
    - Reflect.has(target, propertyKey) # 判断目标对象中是否存在这个属性
    - Reflect.set(target, propertyKey, value[, receiver]) # 修改或者设置一个属性
    - Reflect.isExtensible(target) # 判断这个对象是否可拓展 如果可以拓展我们就可以增加新属性 ，比如 Reflect || Object.preventExtensions(target) 设置过的属性就是不可拓展的
    - Reflect.ownKeys(target) # 返回一个以目标属性键值的数组 比如 const obj = {name:&apos;Mowang&apos;, age:20 }  Reflect.ownKeys(obj) 返回结果为 [&apos;name&apos;,&apos;age&apos;]
    ...
    // 举几个常用的 , 剩下的阅读MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reflect 是一个内置的 JS 对象，它提供了一系列方法，可以让开发者通过调用这些方法，访问一些 JS 底层功能。也就是说，&lt;strong&gt;从 Reflect 对象上可以拿到语言内部的方法&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;配合 proxy 使用&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;let p = {
    a: &apos;a&apos;
};

let handler = {
  set(target, key, value, receiver) {
    console.log(&apos;set&apos;);
    Reflect.set(target, key, value, receiver)
  },
  defineProperty(target, key, attribute) {
    console.log(&apos;defineProperty&apos;);
    Reflect.defineProperty(target, key, attribute);
  }
};

let obj = new Proxy(p, handler);
obj.a = &apos;A&apos;;
// set
// defineProperty

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 Proxy 对象和 Reflect 对象联合使用，前者&lt;strong&gt;拦截赋值&lt;/strong&gt;操作，后者完成&lt;strong&gt;赋值的默认行为&lt;/strong&gt;，而且传入了 &lt;strong&gt;receiver&lt;/strong&gt;，那么 &lt;strong&gt;Reflect.set 会触发 Proxy.defineProperty 拦截&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;参考文档  &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect&quot;&gt;MDN - Reflect&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>本站主题 - 待更新</title><link>https://blog.loli.wang/blog/2023-9-01-astro/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-9-01-astro/doc/</guid><description>css新单位dvh，svh 解释</description><pubDate>Sun, 30 Jul 2023 15:27:24 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-01-AsTro/02.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-01-AsTro/03.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-9-01-AsTro/04.png&quot; alt=&quot;切图3&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[工具] 代码文本比对神器 Beyond Compare</title><link>https://blog.loli.wang/blog/2023-8-23-beyondcompare/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-23-beyondcompare/doc/</guid><description>[工具] 代码文本比对神器 Beyond Compare </description><pubDate>Thu, 20 Jul 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;起因&lt;/h3&gt;
&lt;p&gt;沉冰A梦写了个新的主题，也就是本博客的主题，在使用中发生了很多功能缺失的问题，往往从一个 &lt;strong&gt;demo&lt;/strong&gt; 改为一个真正可用完整的东西的时候，少不了折腾，而我和沉冰不是用的一个代码仓库导致无法代码直接合并查看差异 ，而我又需要使用新功能，因此沉冰A梦推荐了一个工具 《Beyond Compare》&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-23-BeyondCompare/03.png&quot; alt=&quot;BeyondCompare&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;效果体验极佳&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-23-BeyondCompare/01.png&quot; alt=&quot;切图1&quot; /&gt;
&lt;img src=&quot;http://img.blog.loli.wang/2023-8-23-BeyondCompare/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;下载地址&lt;/h3&gt;
&lt;p&gt;https://www.scootersoftware.com/home  （这个应该是官方下载，中文站貌似是代理的）&lt;/p&gt;
</content:encoded></item><item><title>JS 微任务和宏任务 </title><link>https://blog.loli.wang/blog/2023-8-22-eventloop/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-22-eventloop/doc/</guid><description>JS 微任务和宏任务</description><pubDate>Sat, 01 Jul 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;JS 是单线程&lt;/h3&gt;
&lt;p&gt;js 特性单线程，js 是主要和用户互动，和操作 DOM，决定了他只能是个单线程，否则会出现很复杂的同步问题 。 单线程就意味着，所有任务必须要排队执行，只有执行完前面的，后面的才会执行， 如果前一个任务消耗时间很长， &lt;strong&gt;后面的任务就必须等待前面的任务等着&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;JS 中的同步异步&lt;/h3&gt;
&lt;p&gt;同步&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;必须要从上至下执行完毕，后一个任务必须要等待上一个任务执行完毕
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;异步&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;前一个任务没有执行完， 没有关系 ， 他也会执行下一个任务，直到执行结束
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;微任务和宏任务&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;对同步异步有一定的划分后， 已经知道同步任务是按顺序执行，从上至下。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么异步任务也有他的执行顺序的， 他也是从上至下， 但是在异步任务里，还有进一层的划分，就是微任务和宏任务，&lt;strong&gt;[微任务比宏任务优先执行]&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;微任务代表&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;process.nextTick、 Promise、 MutationObserver 等
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;宏任务代表&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;setTimeout、 setInterval、 setImmediate、 IO操作等
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意： Promise有个特殊性，&lt;strong&gt;Promise构造函数中函数体的代码都是立即执行的&lt;/strong&gt;，而Promise.then() 和 Promise.catch() 属于微任务， 也就是resolve() 和reject()&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-22-EventLoop/01.awebp&quot; alt=&quot;掘金补图&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;参考资料&lt;/h4&gt;
&lt;p&gt;掘金-辉夜真是太可爱了  https://juejin.cn/post/6886602875225833480&lt;/p&gt;
</content:encoded></item><item><title>React之路 - 第一篇</title><link>https://blog.loli.wang/blog/2023-8-24-reactexperience/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-24-reactexperience/doc/</guid><description>React之路 - 知识累计篇</description><pubDate>Sat, 01 Jul 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;常用的 React 库&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;- react   // 不必多说
- react-dom  // React的官方渲染库，用于将React组件渲染到浏览器中
- react-router-dom // React 官方路由库
- react-redux // React 官方状态管理库
- react-query // 用于管理和处理数据的React库
- @tanstack/react-query 是 react-query 的一个扩展库，由 TanStack 维护 有一些react-query 没有的功能
- redux-persist 用于持久化存储Redux状态的库，会将Redux的状态缓存到loaclStorage sessionStorage中 能够使页面刷新保留状态
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;函数式组件写法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 参数1父组件传递进来的参数接收
// 参数2用于访问组件实例的方法和属性，或者直接操作 DOM 元素 React.forwardRef(MyComponent)

const 组件名(props,ref){
    const {name} = props;
    return &amp;lt;div&amp;gt; {name} &amp;lt;/div&amp;gt;
}

export default 组件名
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;类组件写法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;

class 组件名 extends React.Component{
    constructor(props){
        super(props);
        this.state={
            count: 0
        }
    }
    
    // 组件生命周期方法
    componentDidMount() {
        // 组件挂载后执行的操作
    }

    render(){
        return (
            &amp;lt;div&amp;gt; {this.state.count} &amp;lt;/div&amp;gt;
        )
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常用的Hook&lt;/h3&gt;
&lt;h5&gt;1. useState&lt;/h5&gt;
&lt;p&gt;用来存储状态变量变量 [something, setSomething] 用解构方式取值
something 为状态变量，setSomething 为改变状态变量的方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 示例
import React, { useState } from &quot;react&quot;;

const [something, setSomething] = useState(0); 
const [something1, setSomething1] = useState({
    test:222
});
const [something2, setSomething2] = useState(&quot;222&quot;);

# 修改状态变量something的方法
setSomething(1);

# 修改状态变量something1的方法
setSomething1({
    something1， // 这里的something1 为上面的状态变量   
    test:333
});

# 修改状态变量something2的方法
setSomething2(&quot;333&quot;);

&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2. useEffect&lt;/h5&gt;
&lt;p&gt;用来处理副作用，比如异步请求，定时器等&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 示例
import React, { useState, useEffect } from &quot;react&quot;;

const [something, setSomething] = useState(0);

useEffect(() =&amp;gt; {
    // ....用来处理需要的副作用的处理
}, [something]); // something 为依赖项，当something发生变化时，useEffect会重新执行

#如果依赖项为空数组，他只会执行一次，相当于componentDidMount
useEffect(() =&amp;gt; {
    // ....用来处理需要的副作用的处理
}, []);

&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;3. useContext&lt;/h5&gt;
&lt;p&gt;用来处理跨组件传值，类似于vue中的provide/inject ,方便数据各种组件之间的传递，也可以叫做上下文传递&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 示例
import React, { useState, useContext } from &quot;react&quot;;

const [something, setSomething] = useState(0);

const Context = React.createContext(null);

# 组件声明

&amp;lt;Context.Provider value={context}&amp;gt;
    &amp;lt;组件名 /&amp;gt;
&amp;lt;/Context.Provider&amp;gt;

# 组件接收

const context = useContext(Context);

&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;4. useRef&lt;/h5&gt;
&lt;p&gt;用来获取DOM元素或者保存变量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# 示例
import React, { useState, useRef } from &quot;react&quot;;

const [something, setSomething] = useState(0);

const ref = useRef(null);

# 获取DOM元素
&amp;lt;div ref={ref}&amp;gt;&amp;lt;/div&amp;gt;

# 获取变量
ref.current

&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;5. useReducer&lt;/h5&gt;
&lt;p&gt;useReducer 是 useState 的替代方案,用来进行性能优化，用于处理复杂的状态逻辑,他不会改变原有的状态，而是返回一个新的状态。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 示例

import React, { useReducer } from &quot;react&quot;;

// 初始化状态
const initState = { name: &apos;123&apos; }

// reducer 函数
const reducer = (state: any, action: any) =&amp;gt; {
    return action.type === &apos;add&apos; ? { name: &apos;456&apos; } : { name: &apos;789&apos; }
}

// useReducer 接收两个参数，第一个参数为reducer函数，第二个参数为初始化状态
const [state, dispatch] = useReducer(reducer, initState);

# 修改状态
dispatch({ type: &apos;add&apos; })

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-24-ReactExperience/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;下一次写脚手架的时候再去填充。。。。&lt;/p&gt;
&lt;p&gt;参考文档 &lt;a href=&quot;https://www.ruanyifeng.com/blog/2019/09/react-hooks.html&quot;&gt;阮一峰博客&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Vue3源码学习 - 1.搭建项目雏形</title><link>https://blog.loli.wang/blog/2023-8-25-vuemini1/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-25-vuemini1/doc/</guid><description>Vue3源码学习</description><pubDate>Sat, 01 Jul 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Vue3 源码准备&lt;/h1&gt;
&lt;p&gt;项目项目源码地址：&lt;a href=&quot;https://github.com/itmowang/mini-vue&quot;&gt;itmowang/mini-vue&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;项目所用包管理工具请一切使用 PNPM&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm install pnpm -g
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;项目目录结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;packages/ 
┣ compiler-core/  # 编译器核心模块
┃ ┣ src/
┃ ┃ ┗ index.ts
┃ ┗ README.md
┣ compoler-dom/  # 浏览器部分编译模块
┃ ┣ src/
┃ ┃ ┗ index.ts
┃ ┗ README.md
┣ reactivity/    # 响应性模块
┃ ┣ src/
┃ ┃ ┗ index.ts
┃ ┗ README.md
┣ runtime-core/  # 运行时核心模块
┃ ┣ src/
┃ ┃ ┗ index.ts
┃ ┗ README.md
┣ runtime-dom/   # 浏览器部分运行时模块
┃ ┣ src/
┃ ┃ ┗ index.ts
┃ ┗ README.md
┣ shared/        # 共享公共方法模块
┃ ┣ src/
┃ ┃ ┗ index.ts
┃ ┗ README.md
┗ vue/           # 打包测试项目整体入口模块
  ┣ src/
┃ ┃ ┗ index.ts
  ┗ README.md

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;安装Typescript&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;
 pnpm install typescript

 tsc --init # 生成配置文件

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;{
  // 编辑器配置
  &quot;compilerOptions&quot;: {
    // 根目录
    &quot;rootDir&quot;: &quot;.&quot;,
    // 严格模式标志
    &quot;strict&quot;: true,
    // 指定类型脚本如何从给定的模块说明符查找文件。
    &quot;moduleResolution&quot;: &quot;node&quot;,
    // https://www.typescriptlang.org/tsconfig#esModuleInterop
    &quot;esModuleInterop&quot;: true,
    // JS 语言版本
    &quot;target&quot;: &quot;es5&quot;,
    // 允许未读取局部变量
    &quot;noUnusedLocals&quot;: false,
    // 允许未读取的参数
    &quot;noUnusedParameters&quot;: false,
    // 允许解析 json
    &quot;resolveJsonModule&quot;: true,
    // 支持语法迭代：https://www.typescriptlang.org/tsconfig#downlevelIteration
    &quot;downlevelIteration&quot;: true,
    // 允许使用隐式的 any 类型（这样有助于我们简化 ts 的复杂度，从而更加专注于逻辑本身）
    &quot;noImplicitAny&quot;: false,
    // 模块化
    &quot;module&quot;: &quot;esnext&quot;,
    // 转换为 JavaScript 时从 TypeScript 文件中删除所有注释。
    &quot;removeComments&quot;: false,
    // 禁用 sourceMap
    &quot;sourceMap&quot;: false,
    // https://www.typescriptlang.org/tsconfig#lib
    &quot;lib&quot;: [&quot;esnext&quot;, &quot;dom&quot;],
  },
  // 入口
  &quot;include&quot;: [&quot;packages/*/src&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安装rollup 打包工具&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pnpm install rollup

pnpm install @rollup/plugin-node-resolve # 模块导入路径补全

pnpm install @rollup/plugin-commonjs # 将代码引用转换为 es2015

pnpm install @rollup/plugin-typescript # ts 支持

pnpm install tslib 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
import resolve from &apos;@rollup/plugin-node-resolve&apos;
import commonjs from &apos;@rollup/plugin-commonjs&apos;
import typescript from &apos;@rollup/plugin-typescript&apos;

// rollup的配置文件
export default [
  {
    // 入口文件
    input: &apos;packages/vue/src/index.ts&apos;,
    // 打包的输出文件
    output: [
      {
        // 开启sourcemap
        sourcemap: true,
        file: &apos;packages/vue/dist/vue.js&apos;,
        format: &apos;iife&apos;,
        name: &apos;Vue&apos;
      }
    ],
    plugins: [
      // ts 支持
      typescript({
        sourceMap: true
      }),
      // 模块导入路径补全
      resolve(),
      // commonjs 模块转换成 es2015
      commonjs()
    ]
  }
]


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在package.json文件中新加一个scripts&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &quot;build&quot;:&quot;rollup -c -w&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果出现以下这样证明成功了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-25-vuemini1/02.png&quot; alt=&quot;rollup打包成功&quot; /&gt;&lt;/p&gt;
&lt;p&gt;项目的雏形，一个标准的monorepo项目。&lt;/p&gt;
</content:encoded></item><item><title>Vue3源码学习 - 2.编写中</title><link>https://blog.loli.wang/blog/2023-8-26-vuemini2/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-26-vuemini2/doc/</guid><description>Vue3源码学习</description><pubDate>Sat, 01 Jul 2023 16:00:00 GMT</pubDate><content:encoded/></item><item><title>React之路 - 第二篇 </title><link>https://blog.loli.wang/blog/2023-8-29-reactexperience2/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-29-reactexperience2/doc/</guid><description>React之路 - 知识累计篇</description><pubDate>Sat, 01 Jul 2023 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;也算是用React写过几个项目了，不管是实际业务上面感觉都有一定的把握。特此准备写一个脚手架项目模板。&lt;/p&gt;
&lt;h2&gt;项目准备&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;建立 react-admin 文件夹

pnpm init 

# 安装依赖 
pnpm add typescript
pnpm add vite 
pnpm add react 
pnpm add react-router-dom
pnpm add @vitejs/plugin-react-swc

# 生成ts配置文件 
tsc --init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 &lt;strong&gt;vite.config.ts&lt;/strong&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
import { defineConfig, PluginOption } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react-swc&quot;;

export default defineConfig({
  resolve: {
    alias: {
      &quot;@&quot;: &quot;/src&quot;,
    },
  },
  plugins: [react()] as PluginOption[],
  server: {
    port: 8081,
  },
  preview: {
    port: 3000,
  },
});

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;.......可能介绍不完了 原本打算边写边做记录的 发现代码量和细节太多了&lt;/p&gt;
&lt;h3&gt;React Admin&lt;/h3&gt;
&lt;p&gt;源自于想后续去写一些小的项目，提前给自己写一套模板，利用了空闲时间抽出来了基础部分，方便后续DIY。( 不适应用于开发，参考学习！ )&lt;/p&gt;
&lt;p&gt;依赖&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  - react (react18)
  - react-router (6x版本)
  - redux
  - antd
  - axios
  - react-query
  - less
  - redux-persist
  - rematch
  - rematch/persist 
  - typescript
  - mock
  - vite
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;预览&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-29-reactexperience2/01.png&quot; alt=&quot;切图1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-29-reactexperience2/02.png&quot; alt=&quot;切图2&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>.nojekyll 文件是什么</title><link>https://blog.loli.wang/blog/2023-8-20-nojekyll-why/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-20-nojekyll-why/doc/</guid><description>.nojekyll 文件是什么 </description><pubDate>Fri, 01 Jul 2022 16:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;.nojekyll 文件是什么&lt;/h2&gt;
&lt;p&gt;使用 Nuxt 的过程中，发现在 generate 生成的 dist 文件夹下会有一个名为 .nojekyll 的空白文件，它是干什么用的呢？&lt;/p&gt;
&lt;p&gt;Github Pages 默认是基于 Jekyll 构建，Jekyll 是一个将纯文本转换为静态网站的工具，它构建的网站下各种目录都是特定的以下划线开头命名的文件夹，例如 _layouts、_posts ，它会忽略掉其它的以下划线开头的文件夹和文件。&lt;/p&gt;
&lt;p&gt;.nojekyll 就是告诉 Github Pages 当前网站不是基于 Jekyll 构建的，不要忽略掉下划线开头的文件和文件夹。&lt;/p&gt;
&lt;p&gt;可见 .nojekyll 主要就是用于 Github Pages 这种有默认规则的网站部署平台，如果是部署在自己的服务器上，可以把它删掉。&lt;/p&gt;
&lt;p&gt;反之，如果你的网站不是 Jekyll 构建的，要部署到 Github Pages ，并且包含下划线开头的文件或文件夹，那么你就需要在根目录添加一个 .nojekyll 空文件。&lt;/p&gt;
&lt;p&gt;参考：
Jekyll 官网 &lt;a href=&quot;http://jekyllcn.com/&quot;&gt;http://jekyllcn.com/&lt;/a&gt;
Bypassing Jekyll on GitHub Pages [https://github.blog/2009-12-29-bypassing-jekyll-on-github-pages/]&lt;/p&gt;
</content:encoded></item><item><title>使用 BroadcastChannel 跨页面通信</title><link>https://blog.loli.wang/blog/2023-8-21-broadcastchannel/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-21-broadcastchannel/doc/</guid><description>使用 BroadcastChannel 跨页面通信</description><pubDate>Fri, 01 Jul 2022 16:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;为什么会有需要用到跨浏览器通信的需求&lt;/h3&gt;
&lt;p&gt;因公司老项目一次线上出现bug，发现一个用户开了多个浏览器窗口， 发现登录不同用户不同存在浏览器缓存的token已经更换， 但是用户已经更改，页面没有登出，原本的页面还是可以提交最新的数据， 但是因为token变化了， 后端拿取提交人出错， 出现不可描述的问题 。&lt;/p&gt;
&lt;h3&gt;使用&lt;/h3&gt;
&lt;p&gt;因为是vue项目 我为了在所有地方都可以访问，选择全局挂载&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// 构建全局广播

const channel = new BroadcastChannel(&quot;channel-name&quot;);

Vue.prototype.$channel = channel;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;点击退出登录， 发送广播&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;logout(){
    // 发送通知
    this.$channel.postMessage(`logout`);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在全局地方编写接收广播的逻辑， 例如Vue的App.vue内&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;created() {
    // 通知其他浏览器窗口是否有退出登录
    this.$channel.onmessage = function(e) {
      if (e.data == &quot;logout&quot;) {
        location.href = &quot;/&quot;;
      }
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为本质上退出登录会走接口， 也清理了Store中的数据， 我们只需要通知其他窗口退出即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-21-BroadcastChannel/02.png&quot; alt=&quot;结果&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;参考资料&lt;/h4&gt;
&lt;p&gt;MDN - BroadcastChannel https://developer.mozilla.org/zh-CN/docs/Web/API/BroadcastChannel&lt;/p&gt;
</content:encoded></item><item><title>CloudFlare Worker 反向代理 github 给静态博客做图床</title><link>https://blog.loli.wang/blog/2023-8-21-cfworkerproxy/doc/</link><guid isPermaLink="true">https://blog.loli.wang/blog/2023-8-21-cfworkerproxy/doc/</guid><description>CloudFlare Worker 反向代理 github 给静态博客做图床 </description><pubDate>Fri, 01 Jul 2022 16:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;CloudFlare Worker 反向代理 github 博客做图床&lt;/h2&gt;
&lt;p&gt;目前老师写了一个新的 BLOG 主题，支持纯静态部署的，想到了 github pages 功能，部署静态博客后,发现图片不知道怎么处理，一般来说一个正常的系统图片都会上传到自己的服务器上面，或者私有图床，但是这种纯静态网站是无法做到这种功能的。如果用图床又怕哪天图片挂了或者其他问题，所以决定采取老师的方案，使用 CloudFlare 的 worker 功能做图床功能&lt;/p&gt;
&lt;h4&gt;实现&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;
addEventListener(&quot;fetch&quot;, event =&amp;gt; {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  // Cloudflare Workers 分配的域名
  // 同时绑定自己的域名
  const cf_worker_host = new RegExp(&quot;imgcdn.loli5.workers.dev|img.blog.loli.wang&quot;,&quot;g&quot;);
  // GitHub 仓库文件地址
  const github_host = &quot;raw.githubusercontent.com/itmowang/blog-astro/main/src/content/blog/&quot;;
  // 替换
  const url = request.url.replace(cf_worker_host, github_host);
  return fetch(url);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-21-cfworkerProxy/02.png&quot; alt=&quot;如何配置&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-21-cfworkerProxy/03.png&quot; alt=&quot;md中如何使用&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://img.blog.loli.wang/2023-8-21-cfworkerProxy/04.png&quot; alt=&quot;一切就绪&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样一切就大功告成了！&lt;/p&gt;
</content:encoded></item></channel></rss>