Skip to main content

4 posts tagged with "tutorial"

View All Tags

· 14 min read
Allen
此内容根据文章生成,仅用于文章内容的解释与总结

内容为最近一年的个人硬件产品的结构设计的对比分析,作为DIY爱好者,必然会有不足之处,欢迎指正。

假设你想自己制作一个桥梁模型送给朋友,手工制作一个当然很好,使用木头、或者捏一个土坯然后烧制、又或者直接使用车床加工。但是这需要一定的技艺,以及各种器具。

但是除了手工制作之外,你依然有很多种方式可以选择:

  • 乐高搭建,你只需要知道乐高拼搭基础,设计出图纸后导出并购买零件(正版与非正版价格差距极大)。

  • 3D打印,3D打印有多种不同的原理,材料可选类型也比较多,设计出模型之后切片打印即可。

  • 激光切割,使用激光切割机切割亚克力板或者木板,之后拼搭。

简短对比 :

技术乐高3D打印(FDM)激光切割
特点模块化设计,易于组装和拆解可以打印出复杂的三维结构可以精确地切割出复杂的二维结构
结构强度玩具级工具级工具级
设计难度小时级小时级小时级
调试难度儿童中等
设备价格千元级千元级
材料价格十元级十元级百元级
制作速度小时级小时级分钟级
气味有(根据材料不同,可能有塑料熔化的气味)有(切割过程可能产生烧焦的气味)
噪音有(打印过程可能会产生噪音)有(切割过程可能会产生噪音)
优点总结1. 适合所有年龄层,易于上手
2. 可重复使用,具有高度的灵活性
3. 无需特殊工具或设备
1. 可以制作出复杂的三维结构
2. 可以打印出定制的零件,适合个性化设计
1. 可以精确地切割出复杂的二维结构
2. 结构稳固,适合制作承受重负的结构
缺点总结1. 结构可能不够稳固,不适合制作大型或承受重负的结构
2. 设计和功能可能受到乐高模块种类和数量的限制
1. 打印速度较慢,大型结构可能需要很长时间
2. 需要一定的设计和操作技能,学习曲线较陡峭
1. 设备价格高昂,运行和维护成本也较高
2. 需要一定的设计和操作技能,学习曲线较陡峭

乐高类

优点

  • 设计快速、简单,易于拆卸,非常适合快速验证创新想法。

  • 乐高模型可以随时调整,拆解后的部件可再次使用,避免了材料的直接损耗。相比之下,3D打印和激光切割等技术一旦出现错误,调整难度较大,材料利用率相对较低。

缺点

  • 乐高结构的强度有限,不适合承受较大的力,因此更适合轻量级的应用。

  • 乐高零件种类有限,对复杂结构的设计造成一定限制。

  • 乐高零件的精度也有限,主要的结构单位和半个单位,这意味着你的模型尺寸需要是0.5个单位长度的倍数。

学习心得

搜索一些搭建图纸,可以帮助你更好地理解乐高的设计原理和技巧。不用管书籍的语言是德语还是英语。

软件

乐高结构设计软件:https://studiohelp.bricklink.com/hc/en-us

优点:软件可以导出零件名称,一键跳转采购。

总结

乐高设计快,但是采购之后自己拼搭比较耗时,遇到问题时可以先思考我之前有没有见过类似的图纸,没有见过而且比较复杂的话,一般选择3D打印。

3D打印

3D打印是个人制造的一种重要方式,它可以将数字模型转化为实体物体,广泛应用于工业设计、医疗、教育等领域。

3D打印分为多种技术,常见的有FDM(熔融沉积成型)、SLA(光固化成型)、SLS(激光烧结成型)等。其中FDM是最常见的3D打印技术,也是我使用的技术。

在3D打印过程中,材料的选择对打印效果和性能有重要影响。我使用的是创想三维K1C,相比同价位拓竹优势一些,下面是它支持的材料及其特点。

  1. ABS(丙烯腈-丁二烯-苯乙烯共聚物):ABS是一种常用的热塑性塑料,具有强度高、耐热性好、易加工等特点,适用于一般性的3D打印项目。

  2. PLA(聚乳酸):PLA是一种生物降解塑料,通常采用玉米等植物原料制成,具有环保、易于打印、无毒等特点,适用于一般性的3D打印项目。

  3. PETC(PETG,改性聚对苯二甲酸乙二醇酯):PETC是一种耐热、透明度高的塑料,具有良好的耐化学性和强度,适用于需要透明或耐热性能的打印项目。

  4. PET(聚对苯二甲酸乙二醇酯):PET是一种透明度高、耐热性好的塑料,常用于食品包装等领域,适用于需要透明性能的打印项目。

  5. TPU(热塑性聚氨酯):TPU具有弹性好、耐磨性强的特点,通常用于制作柔软的零件或弹性组件。

  6. PAASA(聚酰胺酸酯):PAASA是一种工程塑料,具有优异的耐热性和机械性能,适用于要求高强度和耐热性的打印项目。

  7. PC(聚碳酸酯):PC具有优异的抗冲击性和透明度,常用于制作耐用的零件或透明的构件。

  8. PLA-CF(碳纤维增强聚乳酸):PLA-CF是碳纤维增强的聚乳酸材料,具有更高的强度和刚性,适用于要求更高机械性能的打印项目。

  9. PA-CF(碳纤维增强聚酰胺):PA-CF是碳纤维增强的聚酰胺材料,具有更高的强度和耐热性,适用于要求更高机械性能的打印项目。

  10. PET-CF(碳纤维增强聚对苯二甲酸乙二醇酯):PET-CF是碳纤维增强的PET材料,具有更高的强度和刚性,适用于要求更高机械性能的打印项目。

建模软件

建模软件是3D打印的基础,可选的非常多,譬如3D oneTinkercadFusion 360

模型可以从0开始建,也在别人的基础上修改。

成品模型库:

优点

  • 只需要一个晚上就可以验证你的复杂创意。

  • 提升空间大,可以选择一体成型,或者碳纤维材料,也可以使用暂停打印嵌入螺丝、螺母、磁石、金属棒等来创作一些强度极高的混合材质的工具。

缺点

  • 连接处的强度不够需要使用人工介入使用特殊手段提升。

  • 打印时间长而且偶尔成品有瑕疵。

学习心得

3D打印的知识相对来说比较碎片化,需要持续学习与实战验证。可以关注一些3D打印博主、逛逛3D打印社区,可以学习到很多建模、切片、机器的调试、嵌入等技巧。

软件

3D打印需要用到2种软件:3D建模与切片

建模软件就是构建出你要打印的物品的模型的软件。

切片软件则是把这个模型切割为一层一层,控制3D打印机的喷嘴运动路径等参数的软件。目前我的3D打印机只认切片后的文件。

这里建模软件推荐找个教程,教程上用什么跟着学也用什么。

切片软件一般厂家有提供。

激光切割

激光切割机主要用于切割和雕刻材料,如亚克力板、金属片、木板等。激光切割机通过激光束对材料进行加热,使其熔化或气化,然后通过气流将熔化或气化的材料吹走,从而实现切割。

可以通过调整激光头的运行速度和激光强度来实现穿透切割和表面雕刻。

激光强度大,激光头移动速度慢,可以实现穿透切割,即将材料完全切断(速度:10分钟级别)。

激光强度小,激光头移动速度快,可以实现表面雕刻,即在材料表面刻出图案或文字(速度:秒级别)。

优点

  • 刻字速度极快,立等可取。

  • 支持材料多样。

缺点

  • 设备占地较大,气味明显需要单独通风,水冷型的需要偶尔换水。

  • 部分材料切割容易边缘不光滑、发黑。

学习心得

激光切割主要是调试激光能量强度与材料的关系。常见的材料有以下几种:

亚克力板,比较常见的像一块30*30cm 5mm厚的透明板材

个人很喜欢的材料,透明的材质用来装日用摆件类的单片机很有“探索版”的感觉。做工具用比较脆,有一定韧性,但保存不当会产生划痕。

金属片:强度比木板要好不少,相比于亚克力板的硬、脆,金属片可以在切割后折弯,用来制作需要弯曲且强度要求高的结构。

无可替代的优势,你永远可以相信金属。

木板:木板具有良好的硬度和强度,面积较大的区域有一定韧性,但细的地方极其容易断裂。另外木板在潮湿的环境下可能会变形,因此需要在适当的环境中存储和使用。

用来制作各种家居装饰、艺术品和模型。木板的颜色和纹理使得切割出来的产品具有自然的美感和温馨的氛围。此外,木板也可以通过砂纸或者涂料进行后期处理,以改变其颜色和质感,增加产品的美观性和耐用性。

有些材料理论上可行,但是我实际工作中没有使用,不便评价:玻璃和陶瓷、织物和皮革、塑料和橡胶

相比前面两项个人级别的制作,激光切割家用较少,其一是气味较大,需要单独通风,有些激光切割机还需要用水桶接冷凝水。其二是占地面积较大,3D打印机可以放在桌子上,激光切割机需要放在地上,且需要有一定的安全距离。

在学校、工作室等场所,通常会有激光切割机,可以提供激光切割服务。

软件

厂家附赠非开源软件。

· 7 min read
Allen
此内容根据文章生成,仅用于文章内容的解释与总结

群晖是一款非常优秀的 NAS 产品,它可以提供文件存储、多媒体服务、远程访问等功能。一般来说,一台群晖的寿命 4-6 年,折合下来比服务器便宜一点点。因此,我决定做云时代的逆行者,将服务器上的一些服务迁移到群晖上。

注意:云服务器的优点有很多,包括:更加安全、更加灵活、更加便宜等等,迁移需谨慎。

原环境分析

租用的服务器是阿里云产品,选择了宝塔面板进行管理。

网站使用了前后端分离的设计模式,前端基于 Vue 生成的静态页面,后端则基于 Java。后端允许上传一些文件,使用的是 3000 端口进行通信,文件传输则在 8000 端口,路径设置为/upload/。通过 Nginx 进行了反向代理,将/upload/路径的请求转发到 8080 端口,将其他请求(80 端口)转发到 3000 端口。因此,对于这个网站,通信主要通过 80 端口和 8080 端口进行。

tip

如果你遇到了类似的问题,只需要梳理出我们最终通过宝塔面板的哪些端口访问就可以了。

迁移策略

服务器迁移

创建一个 Docker 容器(如果原本用的是宝塔面板,那就继续使用宝塔面板),并按照原来的部署文档在容器内部重新运行服务。

将相关的命令设置为开机自启动。

另外,重要的文件等资料需要单独挂载,并可以设置为只读模式,以防止数据丢失同时更加安全。

端口映射

Docker 的镜像可以通过端口映射的方式,将容器内的端口映射到宿主机(群晖)的端口上。

路由器可以将外网流量转发到宿主机(群晖)上,因此只需要在路由器上设置端口转发即可。

tip

需要注意的是,群晖的部分端口可能已被群晖自身使用,可以通过群晖官网查询。外网的部分端口(如 80 端口)可能被运营商封掉,或者路由器自身需要使用。因此,我们需要选择一些不常用的端口,以避免出现服务异常。

因此有了以下的端口映射规则(你可以先停止容器再设置):

容器内端口(宝塔)宿主机端口(群晖)外网端口
8040804080
808080804880

域名解析

现在我们需要将域名解析到服务器上,由于 80 端口不能直接访问,并且小区和域名供应商都会要求备案,这就需要我们使用 Cloudflare 的 DNS 解析服务。Cloudflare 可以充当中间人,将流量转发到服务器上。

即无法通过 http://www.xxx.com 直接访问到服务器,只能通过域名+端口号如 http://www.xxx.com:8080 访问。

在 cloudflare 中添加域名,全程按照提示操作添加即可。

添加完成后,选择规则->回源(Origin Rules)。因为 Java 用到了两个端口,所以需要添加两个端口转发规则:

  • 当满足条件时(访问域名且路径不以/upload/开头),将流量转发到服务器的 4080 端口。

  • 当满足条件时(访问域名且路径以/upload/开头),将流量转发到服务器的 4880 端口。

结果

通过以上步骤,我们成功地将网站迁移到了新服务器上。用户访问网站时,流程如下:

  • 通过 DNS 解析,找到 cloudflare 的服务器。
  • cloudflare 根据规则,把 80 流量转发到路由器的 4080 上。
  • 服务器接收到流量,把流量转发到群晖的 4080 端口。
  • 群晖接收到流量,把流量转发到容器的 80 端口。
  • 容器接收到流量,通过 Nginx 分配数据。
  • Nginx 根据规则,把流量转发到容器内的 8080 端口。
  • 数据按照原本的路径逐一返回至 cloudflare。
  • cloudflare 把数据返回给访问者。

如法炮制可以继续迁移其他站点。

这种迁移方式不仅保证了原有服务的连续性,也确保了服务器不会被外网直接访问,从而提高了网络安全性。即使网站被黑,也不会影响到其他服务,只需重启这个容器即可恢复原状。

后话

这次的迁移过程体现了分层思想的重要性,这主要来自《白帽子讲 Web 安全》这本书。整个过程没有遇到什么问题,只需要对容器化和 Cloudflare 有一定的了解。我希望我的经验能给读者带来一些启示。

· 8 min read
Allen
此内容根据文章生成,仅用于文章内容的解释与总结

前段时间偶然间看到了一些生成式 AI 文本摘要项目,觉得很有意思。个人不太信任第三方服务,于是就加到待办里,想着自己也实现一个,最近终于有空了。

逻辑上的核心功能是:自动生成,无需人工干预,一次生成,再次生成消耗 key

样式上的核心功能是:逐字显示,好像是个机器人真的在实时生成。

本篇文章将记录如何实现这个功能。

原型

博客是基于 Docusaurus 搭建的,而 Docusaurus 是基于 React 的,文章内容是通过 markdown 文件写的,所以需要设计一个 React 组件,传入 markdown 文件内的文本内容,每次有请求时,将文章内容转换为文本摘要。

但是这样做一些问题,主要的是重复的每次请求都会消耗 key,因此需要储存已请求内容。

判断条件可以设为如果内容不存在,则直接调用,否则就重新生成,然后存储。

由此可知我们至少需要:内容(用来判断是否重复)、摘要(用来显示)

{
"This is the text to summarize": "This is the summary",
"This is the text to summarize 2": "This is the summary 2",
}

如果储存是需要成本的,我们可以使用hash值来判断内容是否相同,如果hash值相同,那么就不需要重新生成摘要了。这样不要存储一篇文章,只需要存储hash值和摘要就可以了。

{
"248ae1890a0084b3bbc30bd3c0c2e17e": "summary"
}

如果有多个文章如何每次请求只请求指定的文章呢?

我们可以使用路径来区分不同的文章,在服务器上我们的方法就太多了。

但是静态的话我使用文件名来区分不同的文章。将文章路径中的/替换为_,然后加上.json后缀,就可以了。

blog_1.json
{
"248ae1890a0084b3bbc30bd3c0c2e17e": "summary"
}

把这个代码逻辑插入到 React 组件中就可以实现了,根据你调用的API不同,你也许可以设置返回的摘要长度等参数。

记得别直接把key写在代码里,而是通过环境变量传入。如果你的项目通过github pages部署,那么可以在项目的setting中设置环境变量REACT_APP_API_KEY,然后在代码中通过process.env.REACT_APP_API_KEY来获取。

实现

当然,这只是一个比较粗糙的想法,接下来让我们完善下代码细节,让它优雅的同时,可以在博客中使用。

逻辑功能

我在reflex-chat#20里提交了关于百度API的实现,在这个仓库里你应该能找到其他API的操作方式。

main.py
import os
import json
import time
import hashlib
import pathlib
import requests
import feedparser
from parsel import Selector
from datetime import datetime
from jinja2 import Environment, FileSystemLoader
class BaiduAI:
def __init__(self):
self.BAIDU_API_KEY = os.getenv("BAIDU_API_KEY")
self.BAIDU_SECRET_KEY = os.getenv("BAIDU_SECRET_KEY")
self.token = self.get_access_token()

def get_access_token(self):
"""
:return: access_token
"""
url = "https://aip.baidubce.com/oauth/2.0/token"
params = {
"grant_type": "client_credentials",
"client_id": self.BAIDU_API_KEY,
"client_secret": self.BAIDU_SECRET_KEY,
}
return str(requests.post(url, params=params).json().get("access_token"))

def get_result(self, text: str):
messages = json.dumps(
{
"messages": [
{
"role": "user",
"content": "阅读下面的博文,然后尽可能接近50个词的范围内,提供一个总结。只需要回复总结后的文本:{}".format(
text
),
}
]
}
)
session = requests.request(
"POST",
"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro?access_token="
+ self.token,
headers={"Content-Type": "application/json"},
data=messages,
)
json_data = json.loads(session.text)
if "result" in json_data.keys():
answer_text = json_data["result"]
return answer_text


class Jsonsummary:
def __init__(self):
root = pathlib.Path(__file__).parent.resolve()
self.json_file_path = os.path.join(root,"summary")
self.url = "https://jiangmiemie.com/"
self.pages = []

def load_json(self):
# 加载JSON文件
loaded_dict = {}
for file in os.listdir(self.json_file_path):
with open(os.path.join(self.json_file_path, file), "r", encoding="utf-8") as json_file:
loaded_dict[self.url + file.replace("_", "/").replace(".json", "")] = json.load(json_file)
return loaded_dict

def save_json(self,loaded_dict):
# 将字典存入JSON文件
for key in loaded_dict:
key_path = key.replace(self.url, "").replace("/", "_") + ".json"
save_path = os.path.join(self.json_file_path, key_path)
with open(save_path, "w", encoding="utf-8") as json_file:
json.dump(loaded_dict[key], json_file, indent=4)

def clean_json(self):
# 根据RSS结果清理JSON文件
for file in os.listdir(self.json_file_path):
if file not in self.pages:
os.remove(os.path.join(self.json_file_path, file))

def blog_summary(feed_content):
jsdata = Jsonsummary()
loaded_dict = jsdata.load_json()

for page in feed_content:
url = page["link"].split("#")[0]
jsdata.pages.append(url.replace(jsdata.url, "").replace("/", "_") + ".json")
# 剪切掉摘要部分,仅保留正文
content = page["content"][0]["value"]
selector = Selector(
text=content.split("此内容根据文章生成,仅用于文章内容的解释与总结")[1]
)
content_format = "".join(selector.xpath(".//text()").getall())
content_hash = hashlib.md5(content_format.encode()).hexdigest()
if (
loaded_dict.get(url)
and loaded_dict.get(url).get("content_hash") == content_hash
):
continue
else:
ai = BaiduAI()
summary = ai.get_result(content_format)
loaded_dict.update(
{url: {"content_hash": content_hash, "summary": summary}}
)
jsdata.save_json(loaded_dict)
jsdata.clean_json()

def fetch_blog():
content = feedparser.parse("https://jiangmiemie.com/blog/rss.xml")["entries"]
blog_summary(content)


if __name__ == "__main__":
fetch_blog()

BAIDU_API_KEYBAIDU_SECRET_KEY传入git action的环境中的示例:

- name: Update
run: python build_readme.py
env:
BAIDU_API_KEY: ${{ secrets.BAIDU_API_KEY }}
BAIDU_SECRET_KEY: ${{ secrets.BAIDU_SECRET_KEY }}

完整代码参考我的github仓库

这样我访问部署网址/summary/博客路径就可以精准得到对应的摘要了,接下来就是在博客中使用了。

样式功能

样式上的核心功能是:逐字显示,好像是个机器人真的在实时生成。可以更详细的拆为:获取摘要、逐字显示、放入框架。

//逐字显示
const TypingComponent = ({ text, speed = 100 }) => {
const [displayedText, setDisplayedText] = useState('');

useEffect(() => {
let index = 0;

const typingInterval = setInterval(() => {
setDisplayedText((prevText) => {
if (index < text.length) {
return prevText + text[index++];
} else {
clearInterval(typingInterval);
return prevText;
}
});
}, speed);

return () => clearInterval(typingInterval);
}, [text, speed]);

return <>{displayedText}</>;
};
// 获取摘要
const JsonReader = ({
fieldToMatch,
}) => {
// 替换url与/
const path = fieldToMatch.replace(/https:\/\/jiangmiemie.com\//, "").replace(/\//g, "_");
const url = `https://jiangmiemie.com/jiangyangcreate/summary/${path}.json`;
const [jsonData, setJsonData] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const data = await response.json();
setJsonData(data);
} catch (error) {
console.error("Error fetching JSON:", error);
}
};

fetchData();
}, [url]);

const getFieldData = () => {
if (!jsonData) {
return <TypingComponent text='摘要生成中...' speed={100} />;
}
// 根据字段进行匹配
const matchingField = jsonData["summary"];
return (
<>
<TypingComponent text={matchingField} speed={100} />
</>
);
};

return <>{getFieldData()}</>;
};

// 放入框架
const Aisummary = ({ children }) => (
<div class="post-ai">
<div class="ai-title">
<a
class="ai-title-left"
href="/blog/2024/1/31/"
title="查看详情"
data-pjax-state=""
>
<div class="ai-title-text">文章摘要</div>
</a>
</div>
<div class="ai-explanation" style={{ display: "block" }}>
<JsonReader fieldToMatch = {children}/>
</div>
<div class="ai-suggestions"></div>
<div class="ai-bottom">
<div class="ai-tips">此内容根据文章生成,仅用于文章内容的解释与总结</div>
</div>
</div>
);

以上所有代码构成了你现在在本篇文章中看到的效果。

· 6 min read
Allen
此内容根据文章生成,仅用于文章内容的解释与总结

写博客对我而言,是一种爱好,可以追溯到 2009 年,这篇文章记录了一些博客写作过程之中的实践。

设计博客

广泛的查看别人的博客

设计博客好比画画,从零开始画出一幅好画比较困难,但是如果临摹大师的作品就会相对容易一些。你可以搜索一些博客聚合类站点,查看成员的博客配置,对博客站点的设计有个大概的印象。这类站点通常有比较好的可迁移性。

不需要买域名和服务器

我建议个人博客使用 markdown 编写,存在 GitHub 并绑定自己默认是个非常好的选择。如果你从服务器开始搭建,不光会耗尽初始的热情,也会由于更新不便,服务器异常而法专注于内容。

博客美化切记过度

起初,写技术博客对我来说是一件容易的事,因为我无时无刻都有很多想法。我添加许多炫酷的特效在我的博客上,包括但不限于鼠标特效、点击特效、全局画布、一言、看板娘、音乐播放器、随机背景图、各种悬浮点击渐变特效。但这些美化难以做到不同设备上的兼容。此时我开始删减博客中我曾经认为“增色”的部分:内容不是越多越好。

更新方式

周更

周更的使用者是阮一峰老师,他从 2018 年开始每周都会定期更新,周更压力在于:不知道这周写什么。

双周更

双周更理论上能够很好的保持足够的输入,但实际操作中更容易遇到一整周都很忙的情况。

月更

月更是我坚持最久的更新方式,一个月足以输入足够的知识和内容。

载体选择

纯文字

纯文字的内容往往更能加载更快、获得国际流量的青睐、非常易于检索。

多媒体

只在必要的地方加入多媒体。注意:我并不是在否定文字以外的媒介,越来越多的知识不局限于通过书籍的方式传播:视频、音频、图片、动态网页、互动游戏。

整理博客

好的博客离不开定期整理,包括:

  • 清除无法访问的链接
  • 汇总合并类似的章节
  • 将碎片的知识串联成体系

标签分类

我个人建议:表头的栏目推荐为 4-5 个,如有折叠展开:展开内容为 3-5 个。我们信息加工能力的局限1

风格化

这一步是要将你的站点与其他站点区分开来,风格化过程中会涉及到一些编程相关的知识,但主要是审美。

Live Editor
// 一个足够简单的单元,配上无数次的重复即可呈现一个有趣的画面
// 一张小巧无缝矢量图即可实现用极小的内存平铺满整个背景。
function example(props) {
  // 使用 XPath 查询选择输出框
  const xpathSelector =
    "/html/body/div/div[2]/div/div/main/article/div/div[2]/div[4]";
  const myElement = document.evaluate(
    xpathSelector,
    document,
    null,
    XPathResult.FIRST_ORDERED_NODE_TYPE,
    null
  ).singleNodeValue;
  // 你可以在这里查看或修改这个SVG图片
  // 譬如 https://jiangmiemie.com/img/logo-192.svg
  myElement.style.backgroundImage =
    'url("https://jiangmiemie.com/img/protruding-squares.svg")';
  myElement.style.backgroundColor = "ee5522"; // 使用 backgroundColor,而不是 background-color
  // 添加一个时钟
  const [date, setDate] = useState(new Date());
  useEffect(() => {
    const timerID = setInterval(() => tick(), 1000);

    return function cleanup() {
      clearInterval(timerID);
    };
  });

  function tick() {
    setDate(new Date());
  }
  return (
    <div
      style={{
        color: 'white',
        height: "200px", // 适当调整高度
      }}>
    <h1>{date.toLocaleTimeString()}</h1>
    
    </div>
  );
}
Result
Loading...

放平心态

由于各种问题都会发生,譬如国内忽然不能访问 Github 了,那么容灾和冗余就决定了你是否能够快速恢复站点(如果不能的话,对你的打击会非常大)

博客的流量和短视频相比差的太多了,数年无人问津更是常态。不要急于求成,否则只会适得其反。这里推荐几个真正在玩博客的前辈:

  • 苏洋博客 —— 一个 real man 一个乐于分享的前辈。
  • 阮一峰的网络日志 —— 科技爱好者周刊已经成了我每周必看的内容,阮老师是真正的布道者。

Footnotes

  1. Miller, G. A. (1956). 神奇的数字:7±2;我们信息加工能力的局限(The magical number seven, plus or minus two: Some limits on our capacity for processing information)