2026年2月


                “Linux之父把AI泡沫喷了个遍”思维导图

“Linux之父把AI泡沫喷了个遍”思维导图模板获取链接

一、核心主题确定

确定核心主题为“Linux之父把AI泡沫喷了个遍”,围绕这一主题,收集和整理Linux之父Linus Torvalds对AI的看法、AI的发展现状、优缺点、炒作周期、分类与作用、未来预测等相关内容,形成思维导图的核心内容框架。

二、导图结构设计

  1. 博文核心观点:作为思维导图的主要分支,涵盖对AI炒作的态度、AI发展现状、AI的优点、AI炒作周期分析、AI的分类与作用、AI的未来预测六个子分支,每个子分支下再细分具体观点和内容。
  2. 博文观点分析:对博文核心观点进行合理性及局限性两方面的分析,每个方面下再细分具体分析内容,形成逻辑严密的论证结构。
  3. 个人观点补充:包含对AI炒作的理解以及对AI未来发展的期待两个子分支,每个子分支下同样细分具体观点,展现个人对AI领域的深入思考。

三、导图样式设计

  1. 颜色搭配:采用绿色作为背景色,给人清新、科技感的视觉感受;不同层级的文字和分支使用不同颜色进行区分,如核心主题用黑色加粗字体,一级分支用深绿色背景白色字体,二级及以下分支用浅绿色背景黑色字体保持视觉一致性。可借鉴图形天下思维导图提供的17套配色方案,选择适合科技主题的配色,增强视觉吸引力。
  2. 形状布局:整体采用树状表格布局,从核心主题向右侧延伸出主要分支,各分支下的子内容以列表形式呈现,层次分明,逻辑清晰。图形天下思维导图提供的12类42种图形布局,可根据内容特点灵活选择,使布局更加专业和有条理。
  3. 字体和字号:选择简洁易读的字体,核心主题字号最大,一级分支字号次之,二级及以下分支字号相对较小,通过字号大小体现内容的层级关系。

“Linux之父把AI泡沫喷了个遍”思维导图模板在线免费体验链接

四、导图工具与流程

  • 工具选择:使用图形天下思维导图软件,该软件不仅提供了丰富的模板、图标、颜色设置等功能,还支持多模态AI生成思维导图,能极大提升创作效率。
  • 创作流程

    • 收集资料:查阅Linus Torvalds关于AI的相关言论、报道以及AI领域的发展现状、技术分析等资料。
    • 整理内容:对收集到的资料进行整理和归纳,提取关键信息,形成各个分支下的具体内容。
    • 创建导图:在图形天下思维导图软件中,先输入核心主题,然后依次创建一级分支、二级分支等。
    • 样式调整:利用软件的树型表格布局,将博文核心观点下的各子分支及其内容以表格形式清晰呈现。同时,利用软件提供的17套配色方案对导图的颜色进行调整。
    • 检查完善:检查导图内容是否完整、逻辑是否连贯、有无错别字等,对不足之处进行修改和完善。

图形天下思维导图软件免费下载链接

五、总结

在本次思维导图的创作过程中,通过运用图形天下思维导图软件的树型表格布局,成功将复杂的内容以清晰、有条理的方式呈现出来。同时,借助软件提供的配色方案预设风格,使导图在视觉上更加吸引人。整个创作流程高效顺畅,充分展现了图形天下思维导图软件在知识管理和思维可视化方面的强大能力。

访问图形天下思维导图模板库教程资源,获取更多免费导图素材与实操指南,激发你的无限创意。

最近入职了一家公司几个月,和一个美女关系不错,经常会两个人聊天。

有一天中午,我俩在茶水间,她让我帮忙和她一起抢演唱会门票。

我心里就顿时有种抵触,不晓得为啥,但是还是帮忙了。

过了一周,我因为突发事件离职,我俩还是有联系。

然后又让我帮她抢演唱会门票。

我心里就非常不舒服。。。

我其实一直没搞懂,为什么我非常不喜欢帮别人抢演唱会的门票,但是别的忙我也愿意帮,就单独抢门票这件事。。。

1. Scrapy的概览与核心价值

想象一下,如果你需要从成千上万个网页中提取结构化数据,用传统的requests + BeautifulSoup方式就像用勺子挖土——虽然可行,但效率低下且难以维护。Scrapy正是为解决大规模、高性能数据抓取需求而生的工业级爬虫框架。

在Python生态系统中,Scrapy占据了不可替代的地位。它不仅仅是一个爬虫库,更是一个完整的爬虫开发框架,将数据抓取的整个流程——从请求调度、网页下载、数据提取到持久化存储——封装成了一套标准化的流水线系统。这种模块化设计让开发者能够专注于"爬什么"而非"怎么爬",极大提升了开发效率。

Scrapy的独特价值在于其基于Twisted异步网络框架的事件驱动架构,能够以单线程实现高并发请求处理,在不增加硬件资源的前提下获得10倍于传统爬虫的抓取速度。同时,它内置的请求去重、自动重试、用户代理轮换等反爬机制,让开发者能够快速构建稳定可靠的爬虫系统。

2. 环境搭建与"Hello, World"

安装Scrapy

Scrapy支持Python 3.7及以上版本,推荐使用Python 3.8+以获得最佳兼容性。安装方式如下:

# 使用pip安装(推荐使用国内镜像源加速)
pip install scrapy -i https://pypi.douban.com/simple

# 验证安装是否成功
scrapy version

如果看到类似Scrapy 2.11.0的版本号输出,说明安装成功。对于Windows用户,可能需要先安装Microsoft Visual C++ Build Tools以解决某些依赖包的编译问题。

第一个Scrapy爬虫

让我们创建一个最简单的爬虫来抓取quotes.toscrape.com网站的励志名言:

import scrapy

class QuotesSpider(scrapy.Spider):
    # 爬虫的唯一标识符
    name = 'quotes'
    
    # 起始URL列表
    start_urls = ['http://quotes.toscrape.com/page/1/']
    
    def parse(self, response):
        # 遍历页面中的每个名言
        for quote in response.css('div.quote'):
            # 提取名言内容、作者和标签
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('small.author::text').get(),
                'tags': quote.css('a.tag::text').getall(),
            }
        
        # 查找下一页链接并继续爬取
        next_page = response.css('li.next a::attr(href)').get()
        if next_page is not None:
            yield response.follow(next_page, callback=self.parse)

代码逐行解析:

  • class QuotesSpider(scrapy.Spider): 继承Scrapy的Spider基类,所有自定义爬虫都必须这样做
  • name = 'quotes': 定义爬虫名称,运行爬虫时会用到这个标识符,必须在项目中唯一
  • start_urls = [...]: 定义爬虫的起始URL列表,Scrapy会自动为每个URL创建请求
  • def parse(self, response): 默认的回调函数,处理响应的函数名固定为parse(除非你指定其他回调)
  • response.css(...): 使用CSS选择器提取数据,Scrapy支持CSS和XPath两种选择器
  • yield {...}: 生成字典数据,这些数据会被传递给Item Pipeline进行后续处理
  • response.follow(): 创建新的请求来跟进链接,第一个参数是URL,第二个参数是回调函数

运行结果:

在终端中执行以下命令运行爬虫:

scrapy crawl quotes -o quotes.json

运行后,Scrapy会自动从第一页开始抓取,提取每条名言的信息,并自动翻页直到抓取完所有页面。最终数据会保存在quotes.json文件中,格式如下:

[
    {
        "text": "“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”",
        "author": "Albert Einstein",
        "tags": ["change", "deep-thoughts", "thinking", "world"]
    },
    ...
]

3. 核心概念解析

Scrapy的核心架构围绕几个关键组件展开,理解这些组件的职责和交互方式是掌握Scrapy的关键。

3.1 Spider(爬虫)

Spider是用户编写的核心逻辑模块,定义了:

  • 如何爬取网站(起始URL、如何跟进链接)
  • 如何解析页面内容(提取数据)
  • 如何处理提取到的数据(生成Item或新的Request)

每个Spider必须继承scrapy.Spider基类,并至少实现parse()方法。Spider的典型工作流程是:接收Response对象 → 解析页面 → 提取数据或生成新Request → yield出去。

3.2 Item(数据项)

Item是Scrapy提供的数据容器,类似于Python字典但提供了字段验证功能。通过预定义数据结构,Item能够避免字段拼写错误和类型混乱。

import scrapy

class QuoteItem(scrapy.Item):
    text = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()

使用Item的好处包括:

  • 字段定义清晰,便于团队协作
  • 支持数据验证和类型检查
  • 与Pipeline配合,实现数据清洗的标准化流程

3.3 Pipeline(管道)

Pipeline负责处理Spider提取的Item,典型操作包括:

  • 数据清洗(去除空格、转换格式)
  • 数据验证(检查必填字段)
  • 数据去重(避免重复存储)
  • 持久化存储(存入数据库或文件)
class CleanPipeline:
    def process_item(self, item, spider):
        # 去除文本首尾空格
        item['text'] = item['text'].strip()
        return item

class DatabasePipeline:
    def __init__(self):
        self.db_conn = None
    
    def open_spider(self, spider):
        # 爬虫启动时建立数据库连接
        self.db_conn = create_database_connection()
    
    def process_item(self, item, spider):
        # 将item存入数据库
        self.db_conn.insert(item)
        return item
    
    def close_spider(self, spider):
        # 爬虫关闭时释放资源
        self.db_conn.close()

核心组件关系图

graph TD
    A[Spider] -->|生成Request| B[Engine]
    B -->|传递Request| C[Scheduler]
    C -->|返回待爬Request| B
    B -->|传递Request| D[Downloader]
    D -->|返回Response| B
    B -->|传递Response| A
    A -->|yield Item| B
    B -->|传递Item| E[Pipeline]
    A -->|yield新Request| B
    E -->|处理Item| F[Database/File]
    
    style A fill:#e1f5ff
    style B fill:#fff4e1
    style C fill:#ffe1e1
    style D fill:#e1ffe1
    style E fill:#f0e1ff

Scrapy的工作流程是一个闭环:Spider生成初始Request → Engine调度 → Scheduler排队 → Downloader下载 → Engine传递响应 → Spider解析 → 提取数据或生成新Request → 循环往复。

4. 实战演练:解决一个典型问题

让我们通过一个完整的项目来实战Scrapy的核心功能。我们将爬取豆瓣电影Top250的信息,包括电影名称、评分、导演和简介。

需求分析

目标网站:https://movie.douban.com/top250
需要提取的数据:电影标题、评分、导演、简介
特殊需求:实现翻页功能,爬取所有250部电影

方案设计

选择Scrapy的原因:

  • 高效的异步并发能力,能够快速爬取25页数据
  • 内置的Request去重机制,避免重复爬取
  • 灵活的Pipeline设计,便于数据清洗和存储

技术方案:

  • 使用CSS选择器提取数据
  • 通过翻页链接的规律实现自动翻页
  • 将数据保存为CSV文件便于后续分析

代码实现

步骤1: 创建项目

scrapy startproject douban_movie
cd douban_movie

步骤2: 定义数据结构(items.py)

import scrapy

class MovieItem(scrapy.Item):
    title = scrapy.Field()    # 电影标题
    rating = scrapy.Field()   # 评分
    director = scrapy.Field() # 导演
    intro = scrapy.Field()    # 简介

步骤3: 编写爬虫(spiders/movie_spider.py)

import scrapy
from douban_movie.items import MovieItem

class MovieSpider(scrapy.Spider):
    name = 'douban_top250'
    allowed_domains = ['douban.com']
    start_urls = ['https://movie.douban.com/top250']
    
    def parse(self, response):
        # 提取当前页的所有电影条目
        movie_list = response.css('ol.grid_view li')
        
        for movie in movie_list:
            item = MovieItem()
            
            # 提取电影标题(可能存在中英文名,取第一个)
            item['title'] = movie.css('span.title::text').get()
            
            # 提取评分
            item['rating'] = movie.css('span.rating_num::text').get()
            
            # 提取导演信息
            info = movie.css('div.bd p::text').getall()
            if info:
                director_info = info[0].strip()
                # 导演信息格式:导演: 张三 主演: 李四 王五
                item['director'] = director_info.split('主演:')[0].replace('导演:', '').strip()
            
            # 提取简介(可能不存在)
            item['intro'] = movie.css('span.inq::text').get() or '暂无简介'
            
            yield item
        
        # 处理翻页
        next_page = response.css('span.next a::attr(href)').get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

步骤4: 配置settings.py

# 模拟浏览器User-Agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'

# 不遵守robots协议(豆瓣的robots.txt禁止爬取)
ROBOTSTXT_OBEY = False

# 下载延迟,避免被封IP
DOWNLOAD_DELAY = 2

# 启用Pipeline
ITEM_PIPELINES = {
    'douban_movie.pipelines.DoubanMoviePipeline': 300,
}

运行说明

执行以下命令启动爬虫:

scrapy crawl douban_top250 -o movies.csv

运行过程中你会看到类似以下的日志输出:

2024-06-15 10:00:00 [scrapy.core.engine] INFO: Spider opened
2024-06-15 10:00:02 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://movie.douban.com/top250>
2024-06-15 10:00:04 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://movie.douban.com/top250?start=25&filter=>
...
2024-06-15 10:01:30 [scrapy.statscollectors] INFO: Closing spider (finished)

爬取完成后,movies.csv文件将包含所有250部电影的信息:

title,rating,director,intro
肖申克的救赎,9.7,导演: 弗兰克·德拉邦特,希望让人自由。
霸王别姬,9.6,导演: 陈凯歌,风华绝代。
阿甘正传,9.5,导演: 罗伯特·泽米吉斯,人生就像一盒巧克力。
...

整个爬取过程大约需要1-2分钟,相比传统串行爬虫速度提升了数倍。Scrapy自动处理了并发、去重、重试等复杂问题,让我们能够专注于数据提取逻辑本身。

5. 最佳实践与常见陷阱

5.1 常见错误及规避方法

错误1: 覆盖parse方法导致CrawlSpider失效

# ❌ 错误做法
class MySpider(CrawlSpider):
    name = 'my_spider'
    rules = [Rule(LinkExtractor(), callback='parse')]
    
    def parse(self, response):
        # 自定义parse方法会覆盖CrawlSpider的内置逻辑
        pass
# ✅ 正确做法
class MySpider(CrawlSpider):
    name = 'my_spider'
    rules = [Rule(LinkExtractor(), callback='parse_item')]
    
    def parse_item(self, response):
        # 使用不同的回调函数名
        pass

错误2: 忘记返回Item导致Pipeline无法接收数据

# ❌ 错误做法
def process_item(self, item, spider):
    self.db.insert(item)
    # 忘记返回item,后续Pipeline无法接收到数据
# ✅ 正确做法
def process_item(self, item, spider):
    self.db.insert(item)
    return item  # 必须返回item或抛出DropItem

错误3: 直接修改Request的meta中保留键

# ❌ 错误做法
yield scrapy.Request(url, callback=self.parse, meta={'redirect_urls': [...]})
# ✅ 正确做法
yield scrapy.Request(url, callback=self.parse, meta={'custom_data': {...}})
# 避免使用Scrapy保留的meta键名,如redirect_urls、cookiejar等

5.2 最佳实践建议

1. 合理设置下载延迟

# 根据目标网站的负载能力调整延迟
DOWNLOAD_DELAY = 2  # 对于豆瓣这样的网站,2秒较为合理
AUTOTHROTTLE_ENABLED = True  # 启用自动限速

2. 使用Item Loader简化数据提取

from scrapy.loader import ItemLoader

def parse(self, response):
    loader = ItemLoader(item=MovieItem(), response=response)
    loader.add_css('title', 'span.title::text')
    loader.add_css('rating', 'span.rating_num::text')
    yield loader.load_item()

3. 配置日志级别便于调试

# 开发环境使用DEBUG级别
LOG_LEVEL = 'DEBUG'

# 生产环境使用INFO或WARNING级别
LOG_LEVEL = 'INFO'

4. 使用管道链处理复杂数据流

ITEM_PIPELINES = {
    'myproject.pipelines.ValidationPipeline': 100,  # 数据验证
    'myproject.pipelines.DeduplicationPipeline': 200,  # 去重
    'myproject.pipelines.StoragePipeline': 300,  # 存储
}

5.3 注意事项

  • 遵守robots协议:虽然可以设置ROBOTSTXT_OBEY = False,但建议尽量遵守网站的robots.txt规定,做一个文明的爬虫
  • 控制并发数:默认并发数为16,对于小型网站建议降低到8或更低,避免给服务器造成过大压力
  • 处理异常:在parse方法中使用try-except捕获异常,避免个别页面解析失败导致整个爬虫中断
  • 善用Scrapy Shell:使用scrapy shell URL命令调试选择器,确保提取逻辑正确后再写入爬虫代码
  • 监控爬虫状态:使用Scrapy提供的stats collector监控爬虫运行状态,及时发现异常

6. 进阶指引

掌握了Scrapy的基础用法后,你可以继续探索以下高级特性:

1. 中间件(Middleware)
中间件提供了在请求/响应处理过程中插入自定义逻辑的钩子。典型应用场景包括:

  • 动态切换User-Agent和代理IP
  • 实现请求重试和异常处理
  • 修改请求头和响应内容

2. 分布式爬虫
通过scrapy-redis扩展,可以实现分布式爬虫,多个爬虫节点共享同一个Redis队列,协同处理大规模爬取任务。

3. 动态网页渲染
对于需要JavaScript渲染的页面,可以集成scrapy-splashscrapy-playwright,实现动态内容的抓取。

4. 数据存储扩展
除了CSV和JSON,Scrapy Pipeline可以轻松对接各种数据库:

  • MySQL/PostgreSQL:使用pymysqlpsycopg2驱动
  • MongoDB:使用pymongo驱动
  • Redis:使用redis驱动

学习资源推荐:

  • 官方文档:https://docs.scrapy.org - 最权威和全面的学习资料
  • GitHub仓库:https://github.com/scrapy/scrapy - 查看源码和提交问题
  • Stack Overflow:搜索scrapy标签,解决具体问题
  • 实战项目:尝试爬取不同类型的网站(电商、新闻、社交媒体),积累实战经验

Scrapy的学习曲线虽然略陡,但一旦掌握,你就拥有了构建高性能爬虫系统的强大工具。从简单的数据采集到复杂的分布式爬虫,Scrapy都能胜任。开始你的Scrapy之旅吧!

各位 V 友,大家好。

我一直觉得“买课容易学完难”。过去一年,为了提升自己,我花了不少钱在各种独立开发、SEO 和商业实战的内容上。但时间久了发现,如果不把这些知识内化并输出,它们很快就会变成收藏夹里的灰尘。

所以我最近上线了一个名为 LuckyHYP Hub 的项目。我的思路是:通过二次学习和实践,把学到的精华以分享的方式整理出来。

项目初心:
我想打造一个纯粹的学习空间。把那些零散的、高价的知识体系,经过我的实践验证和逻辑重构后,免费分享给社区里同样在路上的朋友。

为什么值得一看?

  • 全站默认免费:大部分经过我深度重构、带有个人见解的高质量内容都是直接开放阅读的。
  • 极致阅读体验:基于 Next.js 16 + Tailwind 4 打造,全站深色模式 + 玻璃拟态设计,无广告,只为沉浸式学习。
  • 实战导向:不只是简单的搬运,更多的是我按照课程逻辑跑通后的 SOP 总结和个人心得。

关于付费墙(版权保护说明):
在浏览过程中,你可能会发现极少数深度内容设置了“访问门槛”。这主要是为了规避版权风险。因为部分内容涉及到原课程的一些核心保密细节或敏感素材,为了保护原作者利益,我通过这种方式设置了一道准入机制。建议大家优先看站内的免费文章,已经足够丰富了。

项目地址https://luckyhyp.com

想听听大家的建议:

  1. 这种“学习笔记 Hub”的形式,大家是更看重内容的体系化程度,还是更看重实战案例?
  2. 如果内容涉及到敏感版权,除了“设置访问门槛”,大家还有什么既能分享又能保护原作者的好办法吗?

欢迎各位大佬指点吐槽,也希望这些笔记能帮到正在学习相关领域的朋友。

CentOS 7 老树开新花:从零部署 Dify 全栈应用(含 Go/Rust/GCC 升级避坑)

本文档适用于在 CentOS 7 环境下使用源代码部署 Dify 应用,对应版本 1.9.2。由于系统较旧,部分依赖需手动升级或通过容器化方式解决兼容性问题。

一、安装与配置 Docker

1. 卸载旧版本 Docker(如有)

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2. 安装必要依赖

sudo yum install -y yum-utils device-mapper-persistent-data lvm2

3. 添加 Docker 官方 YUM 源

sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

4. 安装 Docker Engine 及相关组件

sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

5. 启动并设置开机自启

sudo systemctl start docker
sudo systemctl enable docker

6. 配置国内镜像加速器

<!-- more -->

创建 /etc/docker/daemon.json 文件:

sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://docker.registry.cyou",
    "https://docker-cf.registry.cyou",
    "https://dockercf.jsdelivr.fyi",
    "https://docker.jsdelivr.fyi",
    "https://dockertest.jsdelivr.fyi",
    "https://mirror.aliyuncs.com",
    "https://dockerproxy.com",
    "https://mirror.baidubce.com",
    "https://docker.m.daocloud.io",
    "https://docker.nju.edu.cn",
    "https://docker.mirrors.sjtug.sjtu.edu.cn",
    "https://docker.mirrors.ustc.edu.cn",
    "https://mirror.iscas.ac.cn",
    "https://docker.rainbond.cc",
    "https://do.nark.eu.org",
    "https://dc.j8.work",
    "https://gst6rzl9.mirror.aliyuncs.com",
    "https://registry.docker-cn.com",
    "http://hub-mirror.c.163.com",
    "http://mirrors.ustc.edu.cn/",
    "https://mirrors.tuna.tsinghua.edu.cn/",
    "http://mirrors.sohu.com/"
  ]
}
EOF
⚠️ 注意:修改后需重载配置并重启 Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker

7. 将当前用户加入 docker 用户组(避免每次使用 sudo

# 创建 docker 组(若不存在)
sudo groupadd docker

# 将当前用户加入 docker 组
sudo usermod -aG docker $USER

# 刷新组权限(关键!否则需重新登录)
newgrp docker

二、部署 Dify API 服务

1. 准备中间件服务(如 Redis、PostgreSQL 等)

  • 修改 docker-compose.middleware.yamlmiddleware.env 中的数据卷路径
  • 上传整个 docker/ 目录到服务器
启动中间件
cd /data/dify/docker
docker compose -f docker-compose.middleware.yaml up -d
停止命令:docker compose -f docker-compose.middleware.yaml down

2. 安装构建依赖环境

原因:Dify 使用的 wandb >= 0.16.0 要求本地存在 Go 编译环境;同时 numpy==2.4.1 需要 GCC ≥ 9.3,而 CentOS 7 默认 GCC 仅为 4.8.5。
(1) 安装 Go(1.23.0)
# 下载(使用国内镜像)
wget -O go1.23.0.linux-amd64.tar.gz https://golang.google.cn/dl/go1.23.0.linux-amd64.tar.gz

# 解压到 /usr/local
sudo tar -C /usr/local -xzf go1.23.0.linux-amd64.tar.gz

# 配置 PATH
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc

# 验证
go version
(2) 安装 Rust(使用 rsproxy.cn 镜像)
# 下载安装脚本
wget -O rustup-init.sh https://rsproxy.cn/rustup-init.sh
chmod +x rustup-init.sh

# 设置国内镜像源
export RUSTUP_DIST_SERVER=https://rsproxy.cn
export RUSTUP_UPDATE_ROOT=https://rsproxy.cn/rustup

# 静默安装(不修改 PATH)
./rustup-init.sh -y --no-modify-path

# 临时加载环境变量
source "$HOME/.cargo/env"

# 验证
rustc --version
cargo --version
(3) 升级 GCC 至 9.3+
# 启用 SCL 源
sudo yum install -y centos-release-scl

# 安装 devtoolset-9
sudo yum install -y devtoolset-9-gcc devtoolset-9-gcc-c++

# 启用新 GCC(仅当前 shell 有效)
scl enable devtoolset-9 bash

# 验证
gcc --version  # 应显示 9.3.x
建议:将 scl enable devtoolset-9 bash 加入 ~/.bashrc 以持久生效(但注意可能影响其他程序)。
(4) 安装 uv(现代 Python 包管理器)
curl -LsSf https://astral.sh/uv/install.sh | sh
source "$HOME/.local/bin/env"

3. 部署 API 服务

  • 修改 .env 文件中的数据库地址、存储路径、日志目录等配置。
  • 上传 api/ 目录到服务器(首次上传时请注释掉 scp-api.sh 中的启动逻辑)。
首次启动流程
cd /data/dify/api

# 安装依赖
uv sync

# 执行数据库迁移(首次必须运行)
flask db upgrade

# 后台启动 API 服务
nohup gunicorn -w 4 -k gevent --bind 0.0.0.0:5019 app:app > dify-api.log 2>&1 &
启动 Celery Worker
cd /data/dify/api

# 后台启动 Worker
nohup uv run celery -A app.celery worker -P gevent -c 1 --loglevel INFO \
  -Q dataset,generation,mail,ops_trace > dify-worker.log 2>&1 &

🔁 后续重启:只需执行

# 启动API服务
nohup gunicorn -w 4 -k gevent --bind 0.0.0.0:5019 app:app > dify-api.log 2>&1 &
# 启动worker
nohup uv run celery -A app.celery worker -P gevent -c 1 --loglevel INFO \
  -Q dataset,generation,mail,ops_trace > dify-worker.log 2>&1 &

三、部署 Dify Web 前端

说明:CentOS 7 无法原生安装 Node.js 20+,因此采用 Docker 容器化部署

1. 构建 Web 镜像(在开发机上操作)

(1) 本地编译(需 Node.js ≥ 22)
# 安装依赖
pnpm install --frozen-lockfile

# 构建(内存不足时增加堆大小)
NODE_OPTIONS="--max_old_space_size=4096" NEXT_CONCURRENT_BUILD_LIMIT=1 pnpm build

DIR1="web/.next/standalone/.next"

# 创建目录(-p 表示递归创建,且不报错如果已存在)
mkdir -p "$DIR1" 
cp -r web/.next/static web/.next/standalone/.next/static && cp -r web/public web/.next/standalone/public 
构建产物位于 standalone/ 目录。
(2) 编写 Dockerfile
# 使用官方 Node.js 22 Alpine 镜像
FROM node:22-alpine

WORKDIR /app

# 复制构建产物
COPY standalone ./

EXPOSE 3000

CMD ["node", "server.js"]

2. 在服务器部署 Web 服务

cd /data/dify/web

# 1. 清理旧容器与镜像
docker stop my-dify-web && docker rm my-dify-web && docker rmi my-dify-web

# 2. 解压新构建包(覆盖 standalone/)
tar -xzf dify-web-standalone.tar.gz

# 3. 构建新镜像
docker build -t my-dify-web .

# 4. 启动容器
docker run -d \
  --name my-dify-web \
  -p 3000:3000 \
  my-dify-web

3. 配置 Web 环境变量

  • 修改 standalone/.env.local 中的 NEXT_PUBLIC_API_URLNEXT_PUBLIC_WEB_URL,指向实际 API 与 Web 地址。
🔄 更新 Web 服务:重复上述“清理 → 解压 → 构建 → 启动”流程,或封装为脚本自动化。

四、注意事项

  1. 权限问题:确保 /data/dify/ 目录对当前用户可读写。
  2. 防火墙:开放 5019(API)、3000(Web)、以及中间件所需端口(如 6379、5432 等)。
  3. 日志监控:定期检查 dify-api.logdify-worker.log
  4. 环境持久化:若使用 scl enable,建议在 ~/.bashrc 中添加 alias 或 wrapper 脚本。

✅ 至此,Dify 已在 CentOS 7 上完整部署。
如遇问题,请优先检查依赖版本、网络连通性及配置文件路径。


希望这份部署文档能帮助你和团队更高效地完成部署!

本文由mdnice多平台发布

平时使用谷歌搜索,搜索 12315 网站出来的第一条竟然是假的,我没注意看域名直接点进去了。
假冒网站的网址: http://36.110.229.139/
真正的网址: https://www.12315.cn/

而且还尝试注册了,收到验证码还发给它了,验证码也是模仿层 12315 的,不知道有什么后果。

下面直接进入核心,不兜圈子。


美国高防云服务器能防御哪些攻击?(蓝易云 CDN 场景解析)

在当前的网络安全对抗环境中,美国高防云服务器的价值不在“能不能扛”,而在于<span style="color:red">能否在攻击持续、手法演进的情况下保持业务可用</span>。从工程实践角度看,它主要覆盖以下几大类攻击面。


一、DDoS 洪水型攻击(核心防御能力) 🌊

这是高防服务器存在的首要理由。

可防御类型

  • UDP Flood
  • ICMP Flood
  • TCP SYN Flood
  • ACK / RST Flood
  • 混合型反射放大攻击

防御原理(简化逻辑)

异常流量 → 清洗中心 → 特征识别 → 丢弃攻击包 → 回源正常流量
  • 大带宽承载用于吸收流量峰值
  • 清洗节点通过包速率、协议异常、源分布判断攻击
  • 正常请求被保留并回源

📌 关键点在于:
<span style="color:red">防的是“量级 + 持续性”,而不是单点规则</span>


二、CC 攻击(应用层消耗型攻击) 🧠

CC 攻击不靠带宽,靠“像人一样访问”。

常见形式

  • 高频 HTTP GET/POST
  • 慢连接(Slow Request)
  • 模拟正常浏览路径的并发请求

美国高防云服务器的应对方式

防御手段说明
访问频率限制单 IP / 单会话 QPS 控制
行为分析识别非人类访问节奏
会话校验Cookie / Token 校验
挑战机制动态验证请求有效性

📌 本质是:
<span style="color:red">让攻击成本无限接近真实用户成本</span>


三、协议层畸形攻击(低层但致命) ⚙️

这类攻击流量不大,但直接打协议实现漏洞。

可防御类型

  • TCP 半连接耗尽
  • 畸形 TCP Flag 组合
  • 非法 MSS / Window Size
  • 重放包攻击

防御机制说明

  • 协议栈参数硬化
  • 状态表容量保护
  • 异常包即时丢弃

📌 这类防御极度依赖底层网络与内核调优,普通云服务器基本无解。


四、反射与放大攻击 🔁

典型特征:
小请求 → 大响应 → 目标被淹没

常见攻击源

  • NTP
  • DNS
  • SSDP
  • Memcached

高防服务器的处理逻辑

识别反射特征 → 阻断响应回程 → 清洗异常源

📌 防御重点不是“挡住请求”,而是:
<span style="color:red">阻断被利用的回包路径</span>


五、扫描、探测与撞库类攻击 🔍

虽然不是传统意义的大流量攻击,但对业务风险极高。

可防御行为

  • 端口扫描
  • 服务指纹探测
  • 登录接口撞库
  • 异常路径探测

防御手段

行为防护方式
高频扫描自动封禁源
异常路径规则阻断
登录异常访问节流

📌 目标只有一个:
<span style="color:red">不让攻击者摸清你的系统结构</span>


六、与 CDN + 高防联动时的攻击覆盖面 🚀

当美国高防云服务器与 CDN 架构配合时,防御能力会发生质变。

联动后的效果

  • 攻击被提前在边缘节点拦截
  • 源站 IP 完全隐藏
  • CC 攻击被拆散到多个节点
攻击者 → CDN 节点 → 清洗 → 高防服务器 → 业务

📌 实战价值在于:
<span style="color:red">攻击永远打不到真正的源头</span>


七、能力边界说明(务实,不吹)⚠️

必须说清楚,美国高防云服务器不解决所有安全问题

不属于防御范围原因
业务逻辑漏洞属于代码层问题
内部权限滥用非网络攻击
程序自身 Bug需开发修复

📌 高防解决的是:
<span style="color:red">可用性与抗压能力</span>,而不是代码安全本身。


八、总结(一句话定性)🎯

**美国高防云服务器的核心价值在于:
在面对 <span style="color:red">大规模、持续、多形态网络攻击</span> 时,
依然能让业务保持“能访问、不中断、不崩溃”。**

对蓝易云 CDN 这类业务来说,它不是“可选项”,而是抗风险的基础设施

从工程视角看,这不是“买防御”,而是为业务争取生存时间

下面这件事必须先说清楚本质,否则后面全是弯路。


一、先给结论:Redis 里根本没有“文件夹”这个概念

Redis 是内存型 Key-Value 数据库,不是文件系统。
不存在目录、文件夹、层级路径,只有:

Key(键) → Value(值)

你在 Redis 客户端里看到的“文件夹感”,只是 Key 的命名规则造成的视觉假象

👉 所谓“在 Redis 中设置文件夹名”,本质就是:
通过 Key 的命名规范,模拟“文件夹 / 目录层级”结构


二、Redis“文件夹”的正确实现方式(核心原理)🧠

✅ 统一规则:用分隔符组织 Key 的命名空间

企业级 Redis 约定俗成的做法是:

  • 使用 :/ 作为逻辑分隔符
  • 每一段代表一层“目录语义”

例如:

cdn:file:hash:12345
cdn:file:meta:12345
cdn:config:node:beijing

你看到的是“文件夹”,Redis 看到的是:

一个普通字符串 Key

三、标准推荐的 Key 命名规范(务实版)

🔑 统一结构公式

系统名:业务模块:子模块:唯一标识

📌 蓝易云 CDN 场景示例

bluecdn:file:content:md5
bluecdn:file:meta:md5
bluecdn:cache:node:ip
这种结构的好处:
  • <span style="color:red">可读性强</span>
  • <span style="color:red">避免 Key 冲突</span>
  • <span style="color:red">方便按“文件夹”批量管理</span>

四、Java 中“设置文件夹名”的标准写法(实战)☕️

示例 1:最基础的“文件夹 Key”

String key = "bluecdn:file:content:abc123";
redisTemplate.opsForValue().set(key, "文件内容");

解释(逐行)👇

  • bluecdn:系统命名空间,避免与其他业务混用
  • file:逻辑“文件夹”
  • content:子模块
  • abc123:唯一标识(如 MD5、文件ID)

📌 Redis 不会创建任何目录,只是存了一个字符串 Key。


示例 2:用 Hash 模拟“文件夹下多个文件” 📂

String folderKey = "bluecdn:file:meta:abc123";
redisTemplate.opsForHash().put(folderKey, "size", "1024");
redisTemplate.opsForHash().put(folderKey, "type", "jpg");

解释 👇

  • folderKey:逻辑“文件夹”
  • Hash 的 field:相当于“文件属性”
  • Hash 的 value:属性值

📌 一个 Hash = 一个逻辑目录


五、如何“查看某个文件夹下的内容”?(关键点)🔍

Redis 不能像文件系统那样 ls 目录,只能靠 Key 匹配规则

Java 中正确姿势

Set<String> keys = redisTemplate.keys("bluecdn:file:*");

解释 👇

  • *:通配符
  • 匹配所有 bluecdn:file: 开头的 Key
  • 相当于“查看这个文件夹下的所有文件”

⚠️ 生产环境注意
大 Key 数量场景应避免 keys,应使用 SCAN(迭代扫描)。


六、推荐的“目录结构设计表”(企业级)📊

业务场景Key 示例说明
文件内容bluecdn:file:content:id文件主体
文件元数据bluecdn:file:meta:id大小、类型
节点缓存bluecdn:node:cache:ip节点状态
防护规则bluecdn:waf:rule:idWAF 规则

👉 <span style="color:red">目录是逻辑的,规则才是核心</span>


七、常见误区,必须避开 ❌

❌ 误区 1:以为 Redis 会创建目录

👉 Redis 不会、也不需要

❌ 误区 2:Key 命名随意

👉 后期无法维护、无法清理、无法迁移

❌ 误区 3:用 / 当真目录

👉 /: 没有任何功能差异


八、一句话总结(给决策者的)🎯

**Redis 没有文件夹,只有 Key。
所谓“文件夹名”,本质是 <span style="color:red">Key 命名空间设计能力</span>。
设计得好,Redis 就是高性能目录树;
设计得乱,它就是内存垃圾堆。**

对于人生,存在无数个碎片化的“垃圾”时间,有没有什么方法利用碎片化时间提升自己?

发现身边有些大佬都是时间管理 dai 师。但是很多人往往在时间长河里 eclipse 了。

目前来说,受制于工作,我尽量把一些事务性工作放在碎片时间里做,一些重要的事项/提升自我的活动尽量保证成块大块的时间去做。了解到很多高手走路或者刷牙或者上厕所或者洗澡都会思考深刻的问题,我也观察到我的直属上司可以并行多线程做事,感觉我还是太弱了太渺小了。

性能不足,无法多线程,只能单线程而且效率不够高,连带导致时间复杂度高,进而觉得每天时间不够用。又想提升自己,加剧了这个矛盾。

免费,免费,免费 开蹬,kiro 逆向分组,全部 0 倍率 全部 0 倍率

注册地址
[https://terminal.pub/register]https://terminal.pub/register

注册后回帖 抽奖送 50 刀
回帖请带上您的 id

以下周 5 上证指数收盘后两位,作为 hash 取模,抽取 5 位幸运伙伴,赠送 50 刀

今早突然收到邮件

尊敬的用户:

您好!
我们在您的服务器产品 xxx 中检测到了违规代理程序 sxxx-bxx 。
根据《互联网信息服务管理办法》第十五条规定九不准内容、《 xx 云计算使用协议》之条款 4.5.1 及管局通知文件精神要求,xx 云计算现停止对您提供服务。

感谢您对我们的理解与支持。
xx 云计算 

服务器本来是用来 easytier p2p 组网,方便我访问实验室资源。按月支付,用了一年之后觉得还挺稳定,就续了一年😢。现在是暂停计费状态,不确定以后会不会恢复。

代理程序是测试绕过 wifi login 页面的时候装的,协议忘了是 hy2 还是 vless ,但是之后都没怎么使用,感觉不太可能被扫到端口定位出使用的程序。

工单里面问了一下,说不能退款。可以开放 24 小时迁移数据。

请问一下各位大哥,这种情况下我应该怎么做呢?申请退款试试?开放 24 小时上去删数据?



Docker 本地镜像里面多了一个这个 meilisearch/meilisearch ,我是没有安装过这个的,我已经把所有的 docker 全部停止了,还是删除不了,这个是中招了吗?

又是一年春节要来到,在考虑自己写挥春、准备除夕夜和伴侣及朋友的聚餐同时,我也在规划着如何打扫自己的居所,希望在各个方面都以干净整洁的姿态,迎接朋友们的来访。

但给一个近 100 平米的三室一厅做大扫除并不容易,尤其是这个家还是个爱喝咖啡的二猫家庭(还是最掉毛的英短)。平时能够用吸尘器和拖把清洁到的地方还好,但那些卫生死角所累积下来的垃圾和灰尘,要处理起来就莫名地棘手。然而,我们已经长大了,不再有一个万能的、不被看见的妈妈来帮我们整理咖啡角的工具,或是替我们处理掉角落的脏污、扫掉沙发上占的毛,最终,家务事还得靠我们自己来解决。

而且,看着工作室、走廊和房间里的东西越来越多,刚好也时候在年前「断舍离」一番,让家里更亮堂一些。

尽管每个人的居住情况和习惯、装修、清洁工具乃至清洁标准都各不相同,而且这篇文章可能也算不上很完整的「指南」,但无论如何确实也是这次大扫除之后我的感受和经验总结,希望它能对想要来一次大扫除的你有些帮助。

你想要打扫得多干净?

每次我妈来家里看我的时候,都会嫌弃我像是住在「垃圾堆」里,但我却觉得我打扫得已经可以了 —— 显然,我和我妈对于「干净」的标准是完全不同的。我自己平时生活并不规律,加上人也习惯邋遢,所以对「干净」的要求并没有到一尘不染的地步,而我妈是连地板上有根头发都忍受不了的。因此,年前的大扫除如果不是只有你一个人,那么提前沟通统一一下大家对于干净的意见就很有必要了。

在这个过程里,总会有人「妥协」。但我想说的是,还是尽可能以最爱干净的那位成员为标准,因为实际上在这个标准之下大家都会觉得舒心。如果按照「短板」来草草了事,扫不干净是其次,可能还会引发争吵。而且,那个最爱干净的家庭成员大概率也会给身后的人「擦屁股」。

但当你实际了解了家务事的辛苦付出之后,其实就很容易理解 ta 的选择。或许某个区域搞完之后,按照自己的标准已经很干净了,可 ta 看到之后还觉得不行,这种时候吵架和抱怨解决不了问题,不如大家一起听听 ta 觉得哪里不干净,然后一起把这些不行的东西处理掉,减轻 ta 负担的同时,还能让让家里的卫生情况更进一步。

目前,我家的卫生标准大致如下:

  • 起居室:地板、沙发套和家具表面没有明显的脏污,允许不打扫的几天内有一些猫毛、头发和灰尘的存在,每天铲屎避免积味;
  • 厨房:灶台、地板和瓷砖 / 抽油烟机表面没有油污,洗手池不能有厨余垃圾,做完饭及时清扫,允许有少量的未清洗碗筷,等同一天攒一波进洗碗机;
  • 卫生间:地板、马桶和洗手台面没有脏污,浴巾挂在淋浴间外,有接触水的地方及时擦干或通过通风及热风浴霸保持干燥,垃圾桶在接近满是就倒掉,保持卫生间内没有异味;
  • 工作室和卧室:地板没有明显脏污,不允许猫进来。卧室需要干脚且脱鞋进入,避免带进污水和猫毛,允许东西凌乱但不能堆脏东西;
  • 阳台:放飞自我,允许地面和栏杆、防盗网积灰(根本扫不过来),想起来的时候扫一次。

你的家里是怎样的?

我现住的小区房位于珠三角,楼层是 27 楼,三房两卫一厅的格局,大约是 98 平方米。其中,阳台、起居室、厨房和客卫是公共区域,其它地方是私人区域。

众所周知,珠三角的天气并不是那么「讨喜」:夏天湿度大热得像蒸笼,冬天如果干冷能把人电麻,如果湿冷就更吓人,多少件大衣都抵不过寒风和水汽的魔法攻击。然而,这些都只是额外赠礼,最棘手的问题其实还是一年四季无处不在的灰尘。虽然开发商预装了新风系统但体验一般,因此平时还是会打开窗通风,导致灰尘偏大。

不过小区环境好、楼层高也有好处。首先,整个小区的装修环境都比较现代化,房型四四方方直来直去,墙边、吊顶和台面这些地方没有过多的硬装修饰,不容易出现天然的清洁死角。其次,小区除了垃圾站周围也基本看不到蟑螂和老鼠,更不用说会跑到家里,相比以前住在 6 楼要舒适得多。

来到家里,家具的颜色、风格和复杂程度也会影像我们对于大扫除的标准和难易度。我购置的家居也都是简约风格,全都是直溜溜的线条和横平竖直的平面,最多就是在边缘加点银色金属点缀,丝毫不吃老广们钟爱的酸枝、红木那一套。这些家具在大扫除时打理起来就要比那些复杂华丽的家居要省心得多,基本上用吸尘器吸一吸抹布抹一抹就完事了,而不必像那些雕刻了各种图案和装饰的家具那样,需要用更细致的工具来清洁缝隙。

所以,如果希望你在大扫除时能更省时省力,如果有条件,不如先从选房子和家具做起。如果已经不能选择了,就需要对自己家的状况有个明确的了解。建议从家里的家装布局、影响卫生的首要问题和打扫的难易度开始整理,明确哪些地方是好打扫的、哪些地方需要多个人一起弄、哪些地方是比较细枝末节的。

工欲善其事,必先利其器

我相信一整年都不打扫一次的人少之又少,是个人住的家里多多少少都有一些清洁工具。但要搞大阵仗了,还是得准备点趁手的工具。这些工具不仅能提高打扫效率,更重要的是它们能帮我们节省不少体力。

但首先要提醒你的是,临近春节,快递和商家该休息的也都休息了,要买啥工具得赶紧。有了上面两步,相信你已经对家里要扫得多干净、要重点解决哪些卫生问题和需要哪些工具有了大致的了解,所以这时候就可以查缺补漏一下你目前有的工具,如果有新的工具需求就赶紧入手,免得扫着扫着突然发现搞不定。

其次是一些好用的工具。当然,好用这些因人而异,我这里分享的是我用过的觉得还不错的一些工具,而我也基本靠它们就完成了这次的大扫除:

有线吸尘器

平时如果只是吸一部分的地面,戴森还算靠谱,但大扫除这种时候戴森真靠不住,甚至可能还比不上几百块的有线吸尘器。对于大扫除来说,再强劲的吸力也没有持续的吸力来得重要。

手持吸尘器

我认为只靠一个吸尘器就想解决问题也是不切实际的。一般来说,吸地板、墙面、吊顶或天花板的吸尘器是不能处理一些家具之间的缝隙区域的,这时候小型的手持吸尘器会排上大用场。例如,对于掉在柜子缝里的咖啡粉和猫毛,手伸进去一吸就能解决,同时对于一些不方便用吸尘器的操作台面,手持吸尘器也是非常好用。不过由于续航原因,建议可以多备一到两个,甚至可以家庭成员人手一个(因为真的特别好用…)。

非常好用,强烈推荐

柠檬酸除垢剂

对于平时经常会因为沾水会留下水渍的地方,比如浴室玻璃、洗手台和水龙头,用醋其实效果不佳,但用柠檬酸除垢剂兑水后一擦可以立竿见影,多擦几次简直光洁如新,平时用来对付烧水壶、洗碗机、咖啡机和制冰机的水垢也非常有效,建议家中常备。不过千万注意的是,在选购的时候一定要买食品级的,我还真的因为室友洗完制冰机后忘了将溶液倒出被制成冰块之后而误食过……

基本上食品级的就够用了,工业级的大都用不上

大麻袋

也可以替换成超大的废弃纸箱或不用的蛇皮袋之类。相信我,在大扫除的过程中你一定会丢掉或者制造一大堆需要丢到楼下的垃圾,如果你一件一件倒真的很费体力和时间,长期折腾下来还可能会影响你们打扫的心情。建议在开始清洁之前准备好几个坚实的大麻袋,有垃圾都往里扔,然后等装满或者一个区域的清洁结束后,几个人一起拎着把它丢到楼下垃圾站;

除此之外,还有一些我觉得可以准备的各类清洁或辅助工具。这里不做具体的产品推荐,你可以根据自己的消费习惯和需求选购:

清洁剂:

  • 油污净:用于擦拭抽油烟机、灶台、厨房台面等;
  • 下水道疏通剂:用于溶解管道中的毛发、纸巾等,避免堵塞;
  • 草酸清洗剂:用于清洗浴室下水口及马桶中的污垢;
  • 除胶剂:用于除去物体避免残留的胶带痕迹,要注意它们能用在哪些表面上。

需要注意的是,这些清洁剂大都用了浓度比较高的化学溶剂,对皮肤有一定刺激,并且大概率会有难闻的气味。操作时请做好防护,如果不慎沾到请及时冲洗,并根据情况看是否需要去医院。

辅助工具:

  • 小刷子:可以用不用的电动牙刷或普通牙刷;
  • 铲子:用来铲除一些角落的污垢或残留的胶印;
  • 梯子:用于擦一些较高物体的表面,如冰箱、橱柜和衣柜等。

防护用品:

  • 手套:普通劳保手套即可;
  • 口罩:减少清洁剂的刺鼻气味,阻挡灰尘吸入鼻腔,让像我一样的过敏性鼻炎患者避免连打 30 个喷嚏(……);
  • 发绳:对于你家留长发的成员非常有用。

常见的清洁工具这里就不表了,当然,你也可以多备一些以防临时需要。现在,你已经有了理论准备和工具助阵,我们正式进入到清理环……再等等 ——

清洁规划

其实,开始行动之前拟定一个规划,要比拎着工具哐哐就干要有条理许多。在我看来,规划是确保清洁高效和全面的关键。如果你是 J 人,制定一个完整的清洁规划或许还能大大激发你的干劲,但当它落到我这个 100% 的 P 人头上的时候,这份规划就不用多么清晰详细,但你总得在心里有个数。

以下是我在大扫除过程中总结出来的规划经验,从房间划分、清洁顺序、晾晒安排和重新整理四个层面入手,帮你整理一份清洁规划的思路。

房间与责任划分

第一步是根据房间和每个人负责的区进行划分。如家里的客厅、卧室、厨房、卫生间和阳台,每个区域需要完成哪些清洁的步骤、需要打扫到什么地步,然后将清洁任务分解到具体的房间和人身上。在这里,你可以对上面谈好的清洁标准作为基础延展:

  • 客厅:主要清理墙面、天花板、灯具、沙发、地毯和家具表面,重点处理灰尘和杂物堆积,对于被重物压住的角落也尽可能搬开进行清洁;
  • 卧室:主要清洁地板和落灰的衣柜顶层,同时完成房间内物品和衣柜内衣服的整理、床品清洁和窗帘洗涤;
  • 厨房:油污重灾区,需重点清洁油烟机、灶台、台面及橱柜内部。同时,对冰箱进行整理,清空过期食品并除去异味。另外,对洗碗机和制冰机进行一次全面清理,解决藏在机器内的水垢及脏污。
  • 卫生间:重点清洁马桶、洗手台、镜面、瓷砖缝隙。淋浴房内用草酸清洁剂进行全面清洗,再用柠檬酸除垢剂对镜面、玻璃表面和水龙头表面进行清洁,确保无异味、无污垢。
  • 阳台:清理洗衣机、晾衣架、栏杆及地板,保持通风和整洁即可。

清洁顺序

虽然不知道是谁提出来的,但我总体还是遵循大家所说的「从上到下、从内到外、从少用到常用」的顺序来清理。一来可以避免家庭成员的重复劳动,二来还能提高效率。

从上到下,一般指的是先集中人力清洁最麻烦的天花板和灯具,毕竟在梯子上举着手劳动真不是一件容易事,大家一起接力完成要比一个人单独负责要快速和安全得多。等啃完了这根最硬的骨头,再逐步清理墙面和家具,等这些地方都清洁完毕了,灰尘都掉到了地板上之后,最后一并和地面的灰尘处理就能搞定。

从内到外,则是指先从家中最深处的房间开始,比如阳台、卧室等,避免将灰尘带回已清洁区域。而我自己的习惯则是把卫生间排在最外(也算是最后吧),毕竟在打扫的过程中大家肯定会进进出出卫生间好多次,如果先把卫生间打扫了,那没一会就又被踩脏了。

从少用到常用,意思是优先清洁使用频率较低的区域,如客房或储物间,最后清洁厨房和客厅等常用区域,原因和上面的从内到外差不多。而且,这些区域很可能你一年也不会进去几次,所累积的灰尘可能也要比想象的多。因此,提前先把它们搞完也是有必要的。

规划晾晒

清洁过程中,必然有许多东西是需要洗洗晒晒的。但阳台只有那么大,哪怕你有烘干机,空间也依旧有限。因此,合理规划晾晒顺序和空间,可以很好地避免待晾晒织物在洗衣机内发臭。

我觉得,如果你家不是有一个大庭院或大露台能给你一下晾晒完所有的床单被套窗帘啥的,那么大扫除其实不必在一天搞完。分批次进行,不仅能保证晾晒的织物更有阳光的味道,也能让你们不那么累。

目前,我自己的习惯是首先清洁常用卧室的窗帘和床品,再接着是次卧和客卧等不常有人区域的窗帘和床品,根据自己的阳台容量,提前拆下清洗,晾晒后即可重新挂会 / 套回原处,不占空间。

随后,就可以来到抱枕、毯子、沙发套等物品。它们有大有小,小物品可以用衣架一次性晾完,大的物品就遵循上面窗帘和床品的原则,晒干就归位原处,避免占据阳台空间。

最后是需要清洗的衣物。总有一些衣物是你一年都没穿过担忧不舍得丢的,这时候也给它们一次焕然一新的机会吧。

重新整理

大扫除的过程,也是重新整理自己物品和断舍离的过程。我们对于卫生的评价除了干净无异味之外,往往还有整洁这一维度。通过重新整理家中大大小小的物品,将它们归位到应该呆着的位置,不仅能让视觉上的空间更显宽阔,更重要的是,一个整齐的家,真的会让人看着赏心悦目。

在我看来,重新整理的过程其实会深入到大扫除的每个时刻,我们在清洁的过程中必然会不自觉地将一些东西「放到他们该呆的地方」。那么首先,分类归位就很重要:按功能将物品进行分类,然后将它们集体收纳到柜子或抽屉中。对于小件物品,则可以使用收纳箱或分隔盒集中存放,避免后续总是出现找不到的情况。

在我们整理的过程中,物品也会在经过一次筛选。实际上,很多物品并没有我们想象地那么有价值,对于这些物品,或许你真的需要狠狠心。一般来说,如果一个物品我已经有三年的时间没有再打开过,以及再见到它时也没有多么情绪的起伏,那么它就可以被归类为不需要的物品被丢弃,例如已经删好友的朋友在大学送的杯子、前任的记事本等等。

而对于已过期的和无法再使用的物品就没什么好说的了,毫不犹豫地丢掉就行。

在整理过程中,大概率也会遇到一个物品不知道放在哪里合适的难题。遇到这个情况时,不妨找找自己还有没有更多的储物空间,或者是搜寻一下家中现有的储物工具,为它开辟一个新区域。我认为,对于大部分人来说,家庭的空间是足够用于收纳的,只是某个物品你还没有想好让它去哪里而已。这时候,放进一个「未分类」的盒子里或许是很好的解决办法 —— 我相信,很多人的家里真的需要一个这样的盒子。

不过,「未分类」的盒子更应该是个中转站,是让你临时逃避问题的选择。当一个物品进了未分类区域后,不是说就丢在那不管不顾了。我的建议是每个月定期到这里来看看,或许经过时间的打磨之后,你就给一些物品找到了新家。

哦对了,重新整理的过程其实也是发现故障的过程。有时候你可能会在整理的过程中发现某些物品出现了故障,比如电器损坏、插座没法用等等。如果能自己解决,就顺带在大扫除里解决掉。实在没法解决,而且又比较着急用的,那就赶紧喊师傅上门,不然人家回家过年了,你也只能对着它们干瞪眼。

正式开工!

其实到了这里就没有理论存在的土壤了,有了上面提到的工具和计划的加持,好好干就行。如果中途遇到了自己解决不了的问题,及时和家庭成员沟通,或是上网找找解决办法,总能有一条路是通的。

不过在这部分,更多问题可能出现在「人」自身。工作都有人摸鱼,这种重体力劳动的大扫除肯定也会有人摸鱼,如果不是太过分,开个玩笑提醒下就好。而对于体力较小的家庭成员,让 ta 负责一些轻松的工作也无可厚非,实在不行,大家一起多休息就好,毕竟这事也不着急。

还有一点就是,我喜欢在大扫除的时候同时开着全屋 6 个 HomePod 放歌或听播客。这样不仅可以转移一点注意力,也能让大扫除的过程有点乐趣,减少过程中的的痛苦。

全都点上

总结

无论这个年和谁过,我相信大扫除都是一种无可替代的仪式感。在大扫除结束之后,哪怕此刻的你已经腰酸背痛,但也肯定会有一种十分轻快愉悦的心情。面对着干净如新的家和与你一起大扫除完瘫坐在家的家人,想必你们已经更加有了共居的实感和过年的氛围。

希望这篇文章能够帮到你,让你也能在大扫除之前找到一些经验和妙招。尽管这里的经验必然没法适用于所有人,但也算是我自己大扫除下来的整理盒和思考,也算是抛砖引玉吧。

最后,祝我们新年快乐,祝我们和自己在意的人在新一年可以平静、舒心、安稳。^ ^

> 关注 少数派小红书,感受精彩数字生活 🍃

> 实用、好用的 正版软件,少数派为你呈现 🚀

    最近在思考一个反人性的操作:与其考验主观耐力,不如从客观上切断退路。
    我想找这样一个工具:

    1. 本地生成一个新的钱包私钥。
    2. 对私钥进行“时间锁加密”( Time-lock Encryption )。设定一个解密难度或者依赖未来的某个时间戳(比如 Drand 信标)才能解密。
    3. 把币转进去,然后彻底删除明文私钥,只保留那个“打不开”的加密文件。
    4. 这样在设定时间到达之前,哪怕比特币涨到 20 万或者跌到 1 万,我也只能干瞪眼,没有任何办法卖出。
      现在的智能合约(如 CLTV )虽然能做到锁定,但往往操作门槛高,而且要把币转到合约里总觉得不放心。我想问问大家:

    ● 这种“加密后丢弃钥匙”的方案,目前有成熟的开源工具吗?

    ● 这种做法最大的风险是不是如果不小心把加密文件也删了,就彻底归零了?

    ● 还有没有比这更绝的“防手贱”方案?

    大家好,我是良许。

    在嵌入式开发中,中断是一个非常重要的概念。

    它允许 MCU 在执行主程序的同时,能够及时响应外部事件,比如按键按下、传感器信号变化等。

    今天我们就来深入学习 STM32 的 EXTI 外部中断事件控制器,这是每个 STM32 开发者都必须掌握的核心知识。

    1. EXTI 外部中断事件控制器概述

    1.1 什么是 EXTI

    EXTI 是 STM32 中用于管理外部中断和事件的控制器。

    它可以检测 GPIO 引脚上的电平变化,并在满足触发条件时产生中断或事件。

    简单来说,EXTI 就像是一个"门卫",时刻监视着外部世界的变化,一旦发现符合条件的信号,就立即通知 CPU 去处理。

    在实际项目中,我曾经用 EXTI 来处理紧急停止按钮。

    当操作人员按下急停按钮时,系统必须在几微秒内做出响应,停止所有运动部件。

    如果用轮询的方式去检测按钮状态,可能会因为主程序正在执行其他任务而延迟响应,但使用 EXTI 中断就能保证最快的响应速度。

    1.2 EXTI 的主要特性

    STM32 的 EXTI 控制器具有以下特性:

    1. 支持多达 23 条外部中断/事件线(具体数量因芯片型号而异)
    2. 每条中断线都可以独立配置触发方式:上升沿、下降沿或双边沿触发
    3. 每个 GPIO 引脚都可以配置为外部中断源
    4. 支持软件触发中断
    5. 具有独立的挂起状态位和屏蔽位
    6. 可以产生中断请求或事件请求

    需要注意的是,STM32 的 EXTI 有一个重要的限制:相同编号的 GPIO 引脚共享同一条 EXTI 线。

    比如 PA0、PB0、PC0 都连接到 EXTI0 线,这意味着你不能同时将 PA0 和 PB0 都配置为外部中断,只能选择其中一个。

    2. EXTI 工作原理

    2.1 EXTI 的内部结构

    EXTI 控制器主要由以下几个部分组成:

    1. 边沿检测电路:负责检测输入信号的上升沿、下降沿或双边沿
    2. 软件中断事件寄存器:允许通过软件触发中断
    3. 挂起请求寄存器:记录哪些中断线有挂起的中断请求
    4. 中断屏蔽寄存器:控制哪些中断线被使能
    5. 事件屏蔽寄存器:控制哪些事件线被使能

    当外部信号满足触发条件时,EXTI 会将对应的挂起位置 1,如果该中断线没有被屏蔽,就会向 NVIC(嵌套向量中断控制器)发送中断请求。

    2.2 中断与事件的区别

    EXTI 可以产生两种类型的输出:中断和事件。

    很多初学者容易混淆这两个概念。

    中断:会触发 CPU 执行中断服务程序(ISR),需要软件介入处理。

    当中断发生时,CPU 会暂停当前任务,跳转到中断服务函数执行,处理完成后再返回主程序。

    事件:不会触发 CPU 中断,而是产生一个脉冲信号,可以触发其他外设的操作,比如启动 ADC 转换、触发 DMA 传输等,整个过程不需要 CPU 参与,实现了硬件级的联动。

    在我做汽车电子项目时,经常使用事件模式来触发 ADC 采样。

    比如每隔固定时间需要采集传感器数据,我会用定时器产生 EXTI 事件,然后这个事件直接触发 ADC 开始转换,整个过程不占用 CPU 资源,效率非常高。

    3. EXTI 配置步骤

    3.1 使用 HAL 库配置 EXTI 的基本流程

    使用 STM32 HAL 库配置 EXTI 外部中断主要包括以下步骤:

    1. 使能 GPIO 时钟
    2. 配置 GPIO 引脚为输入模式
    3. 配置 EXTI 中断线
    4. 配置 NVIC 中断优先级
    5. 编写中断服务函数

    下面我用一个实际的按键中断例子来说明整个配置过程。

    3.2 按键外部中断配置示例

    假设我们使用 PA0 引脚连接一个按键,按键按下时引脚电平为低,松开时为高(上拉输入)。

    我们希望在按键按下(下降沿)时触发中断。

    /* 1. GPIO初始化配置 */
    void MX_GPIO_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        
        /* 使能GPIOA时钟 */
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        /* 配置PA0为输入模式,上拉,外部中断模式 */
        GPIO_InitStruct.Pin = GPIO_PIN_0;
        GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发中断
        GPIO_InitStruct.Pull = GPIO_PULL_UP;          // 上拉
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        /* 配置NVIC中断优先级 */
        HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
        
        /* 使能EXTI0中断 */
        HAL_NVIC_EnableIRQ(EXTI0_IRQn);
    }
    ​
    /* 2. 中断服务函数 */
    void EXTI0_IRQHandler(void)
    {
        /* 调用HAL库的中断处理函数 */
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
    }
    ​
    /* 3. 中断回调函数 */
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        if(GPIO_Pin == GPIO_PIN_0)
        {
            /* 按键按下,执行相应操作 */
            // 这里可以添加你的业务逻辑
            // 比如翻转LED状态
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        }
    }

    3.3 配置参数详解

    在上面的代码中,有几个关键的配置参数需要理解:

    GPIO\_MODE\_IT\_FALLING:这个参数指定了中断触发方式。

    HAL 库提供了以下几种选择:

    • GPIO_MODE_IT_RISING:上升沿触发
    • GPIO_MODE_IT_FALLING:下降沿触发
    • GPIO_MODE_IT_RISING_FALLING:双边沿触发

    GPIO\_PULL\_UP:配置 GPIO 的上拉/下拉电阻。

    选项包括:

    • GPIO_NOPULL:无上拉下拉
    • GPIO_PULLUP:上拉
    • GPIO_PULLDOWN:下拉

    HAL\_NVIC\_SetPriority:设置中断优先级。

    第二个参数是抢占优先级,第三个参数是子优先级。

    抢占优先级高的中断可以打断抢占优先级低的中断,而子优先级只在抢占优先级相同时才起作用。

    4. EXTI 中断优先级管理

    4.1 NVIC 中断优先级分组

    STM32 使用 NVIC 来管理所有中断,包括 EXTI 中断。

    NVIC 支持中断优先级分组,通过 HAL_NVIC_SetPriorityGrouping() 函数来配置。

    /* 配置中断优先级分组为组2 */
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);

    不同的优先级分组方式决定了抢占优先级和子优先级的位数分配:

    • NVIC_PRIORITYGROUP_0:0 位抢占优先级,4 位子优先级
    • NVIC_PRIORITYGROUP_1:1 位抢占优先级,3 位子优先级
    • NVIC_PRIORITYGROUP_2:2 位抢占优先级,2 位子优先级
    • NVIC_PRIORITYGROUP_3:3 位抢占优先级,1 位子优先级
    • NVIC_PRIORITYGROUP_4:4 位抢占优先级,0 位子优先级

    4.2 合理设置中断优先级

    在实际项目中,合理设置中断优先级非常重要。

    一般遵循以下原则:

    1. 紧急程度高的中断设置高优先级:比如急停按钮、故障检测等
    2. 执行时间短的中断可以设置高优先级:避免长时间占用 CPU
    3. 相关性强的中断设置相近的优先级:便于管理和调试

    在我做的一个电机控制项目中,优先级设置如下:

    /* 急停按钮 - 最高优先级 */
    HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
    ​
    /* 编码器脉冲 - 高优先级 */
    HAL_NVIC_SetPriority(EXTI1_IRQn, 1, 0);
    ​
    /* 普通按键 - 中等优先级 */
    HAL_NVIC_SetPriority(EXTI2_IRQn, 2, 0);
    ​
    /* 通信接收 - 较低优先级 */
    HAL_NVIC_SetPriority(USART1_IRQn, 3, 0);

    5. EXTI 使用注意事项

    5.1 按键消抖处理

    在使用 EXTI 处理按键输入时,必须考虑按键抖动问题。

    机械按键在按下或松开的瞬间,触点会产生多次通断,导致产生多次中断。

    有两种常用的消抖方法:

    方法一:软件延时消抖

    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        if(GPIO_Pin == GPIO_PIN_0)
        {
            /* 简单延时消抖 */
            HAL_Delay(10);  // 延时10ms
            
            /* 再次检测按键状态 */
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
            {
                /* 确认按键按下,执行操作 */
                // 你的业务逻辑
            }
        }
    }

    但是这种方法有个问题:在中断服务函数中使用延时会阻塞其他中断,不推荐在实际项目中使用。

    方法二:定时器消抖(推荐)

    uint32_t last_interrupt_time = 0;
    #define DEBOUNCE_TIME 50  // 50ms消抖时间
    ​
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        if(GPIO_Pin == GPIO_PIN_0)
        {
            uint32_t current_time = HAL_GetTick();
            
            /* 检查距离上次中断的时间间隔 */
            if((current_time - last_interrupt_time) > DEBOUNCE_TIME)
            {
                last_interrupt_time = current_time;
                
                /* 执行按键处理 */
                // 你的业务逻辑
            }
        }
    }

    这种方法利用系统滴答定时器来判断时间间隔,不会阻塞其他中断,是更好的选择。

    5.2 中断服务函数的编写原则

    编写 EXTI 中断服务函数时,需要遵循以下原则:

    1. 尽量简短:中断服务函数应该尽快执行完毕,避免长时间占用 CPU
    2. 避免使用延时函数:不要在中断中使用 HAL_Delay() 等阻塞函数
    3. 避免复杂运算:复杂的计算应该在主程序中完成
    4. 使用标志位:可以在中断中设置标志位,在主程序中检测标志位并处理
    volatile uint8_t button_pressed = 0;  // 按键按下标志
    ​
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        if(GPIO_Pin == GPIO_PIN_0)
        {
            /* 只设置标志位,不做复杂处理 */
            button_pressed = 1;
        }
    }
    ​
    int main(void)
    {
        /* 系统初始化 */
        HAL_Init();
        SystemClock_Config();
        MX_GPIO_Init();
        
        while(1)
        {
            /* 在主循环中检测标志位 */
            if(button_pressed)
            {
                button_pressed = 0;  // 清除标志
                
                /* 执行复杂的处理逻辑 */
                process_button_event();
            }
            
            /* 其他任务 */
        }
    }

    5.3 多个 EXTI 中断的处理

    当使用多个外部中断时,需要注意中断线的分配。

    STM32 的 EXTI0 到 EXTI4 各有独立的中断向量,而 EXTI5 到 EXTI9 共享一个中断向量(EXTI9\_5\_IRQn),EXTI10 到 EXTI15 共享另一个中断向量(EXTI15\_10\_IRQn)。

    /* EXTI5-9共享中断处理函数 */
    void EXTI9_5_IRQHandler(void)
    {
        /* 检查是哪个引脚触发的中断 */
        if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_5) != RESET)
        {
            HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
        }
        
        if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_6) != RESET)
        {
            HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
        }
        
        // 其他引脚的处理...
    }
    ​
    /* 回调函数中区分不同的引脚 */
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        switch(GPIO_Pin)
        {
            case GPIO_PIN_5:
                /* 处理PIN5的中断 */
                break;
                
            case GPIO_PIN_6:
                /* 处理PIN6的中断 */
                break;
                
            default:
                break;
        }
    }

    6. EXTI 实战应用案例

    6.1 旋转编码器接口

    旋转编码器是嵌入式系统中常用的输入设备,通常有 A、B 两相输出。

    通过检测 A、B 相的相位关系可以判断旋转方向和速度。

    使用 EXTI 可以很好地实现编码器接口。

    #define ENCODER_A_PIN GPIO_PIN_0
    #define ENCODER_B_PIN GPIO_PIN_1
    #define ENCODER_PORT GPIOA
    ​
    volatile int32_t encoder_count = 0;
    ​
    void Encoder_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        /* 配置A相为外部中断 */
        GPIO_InitStruct.Pin = ENCODER_A_PIN;
        GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        HAL_GPIO_Init(ENCODER_PORT, &GPIO_InitStruct);
        
        /* 配置B相为普通输入 */
        GPIO_InitStruct.Pin = ENCODER_B_PIN;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        HAL_GPIO_Init(ENCODER_PORT, &GPIO_InitStruct);
        
        HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(EXTI0_IRQn);
    }
    ​
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        if(GPIO_Pin == ENCODER_A_PIN)
        {
            /* 读取A相和B相的状态 */
            uint8_t a_state = HAL_GPIO_ReadPin(ENCODER_PORT, ENCODER_A_PIN);
            uint8_t b_state = HAL_GPIO_ReadPin(ENCODER_PORT, ENCODER_B_PIN);
            
            /* 根据相位关系判断旋转方向 */
            if(a_state == b_state)
            {
                encoder_count++;  // 正转
            }
            else
            {
                encoder_count--;  // 反转
            }
        }
    }

    6.2 红外遥控接收

    红外遥控器发送的是脉宽调制信号,通过测量脉冲宽度可以解码出按键信息。

    使用 EXTI 配合定时器可以实现红外信号的解码。

    #define IR_PIN GPIO_PIN_2
    #define IR_PORT GPIOA
    ​
    volatile uint32_t ir_start_time = 0;
    volatile uint32_t ir_pulse_width = 0;
    volatile uint8_t ir_data_ready = 0;
    ​
    void IR_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        GPIO_InitStruct.Pin = IR_PIN;
        GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        HAL_GPIO_Init(IR_PORT, &GPIO_InitStruct);
        
        HAL_NVIC_SetPriority(EXTI2_IRQn, 2, 0);
        HAL_NVIC_EnableIRQ(EXTI2_IRQn);
    }
    ​
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        if(GPIO_Pin == IR_PIN)
        {
            uint32_t current_time = HAL_GetTick();
            
            if(ir_start_time == 0)
            {
                /* 记录起始时间 */
                ir_start_time = current_time;
            }
            else
            {
                /* 计算脉冲宽度 */
                ir_pulse_width = current_time - ir_start_time;
                ir_start_time = current_time;
                ir_data_ready = 1;
                
                /* 根据脉冲宽度解码数据 */
                // 这里添加解码逻辑
            }
        }
    }

    7. 总结

    EXTI 外部中断事件控制器是 STM32 中非常重要的外设,掌握它对于开发响应式的嵌入式系统至关重要。

    通过本文的学习,我们了解了 EXTI 的工作原理、配置方法以及实际应用技巧。

    在实际开发中,使用 EXTI 需要注意以下几点:首先要合理设置中断优先级,确保重要的中断能够及时响应;其次要注意按键消抖等实际问题,避免误触发;最后要遵循中断服务函数简短高效的原则,复杂的处理逻辑应该在主程序中完成。

    我在多年的嵌入式开发经验中,EXTI 几乎是每个项目都会用到的功能。

    从简单的按键检测到复杂的编码器接口、红外遥控接收,EXTI 都能很好地胜任。

    希望这篇文章能帮助大家更好地理解和使用 STM32 的 EXTI 功能,在实际项目中灵活运用。

    更多编程学习资源