<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>veyliss | Blog</title><description>记录技术学习、工程实践与长期思考</description><link>https://blog.veyliss.top/</link><language>zh-CN</language><item><title>redis核心原理与实战应用</title><link>https://blog.veyliss.top/blog/redis/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/redis/</guid><pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Redis 常被用作缓存、分布式锁、排行榜、计数器和会话存储。它的核心优势不是“能存数据”这么简单，而是用内存访问、丰富数据结构、持久化和高可用机制，支撑高并发场景下的读写性能与系统稳定性。&lt;/p&gt;
&lt;p&gt;这篇文章按实际使用路径整理 Redis：先看基础能力，再看高可用与缓存问题，最后整理一致性、性能优化和典型场景。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;一redis-基础概念&quot;&gt;一、Redis 基础概念&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;数据类型支持&quot;&gt;数据类型支持&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Redis 支持多种常用数据结构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;String&lt;/strong&gt;：适合缓存简单值、计数器、分布式锁标记。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hash&lt;/strong&gt;：适合存储对象字段，例如用户信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;List&lt;/strong&gt;：适合队列、消息列表、时间线等场景。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set&lt;/strong&gt;：适合去重、集合交并差。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sorted Set（ZSet）&lt;/strong&gt;：适合排行榜、权重排序、延迟队列。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些结构让 Redis 不只是一个 Key-Value 缓存，而是可以承接一部分高频读写业务逻辑。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;持久化机制&quot;&gt;持久化机制&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Redis 主要有两种持久化方式：RDB 和 AOF。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RDB（快照）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定期生成内存快照，例如通过 &lt;code dir=&quot;auto&quot;&gt;bgsave&lt;/code&gt; 命令。&lt;/li&gt;
&lt;li&gt;优点：文件体积较小，数据恢复速度快。&lt;/li&gt;
&lt;li&gt;缺点：如果 Redis 异常退出，可能丢失最后一次快照之后的数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;AOF（追加日志）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;记录每一次写操作命令，例如 &lt;code dir=&quot;auto&quot;&gt;SET key value&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;优点：数据安全性更高，可以配置更高频率的落盘。&lt;/li&gt;
&lt;li&gt;缺点：文件体积更大，恢复速度通常慢于 RDB。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实际生产中常见策略是 &lt;strong&gt;RDB + AOF 组合使用&lt;/strong&gt;：RDB 用于快速恢复，AOF 用于降低数据丢失风险。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;二主从同步与高可用&quot;&gt;二、主从同步与高可用&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;主从同步方式&quot;&gt;主从同步方式&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Redis 主从同步分为全量同步和增量同步。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;全量同步&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通常发生在从节点初次连接主节点，或断线时间过长无法增量追赶时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主节点生成 RDB 文件。&lt;/li&gt;
&lt;li&gt;主节点把 RDB 文件发送给从节点。&lt;/li&gt;
&lt;li&gt;从节点清空旧数据并加载 RDB。&lt;/li&gt;
&lt;li&gt;主节点继续把同步期间缓冲区中的写操作发送给从节点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;增量同步&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当从节点短暂断线后重新连接，可以通过 &lt;code dir=&quot;auto&quot;&gt;psync&lt;/code&gt; 根据复制偏移量（offset）同步缺失的写操作，避免重新做一次全量同步。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;高可用实现&quot;&gt;高可用实现&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Redis 常见高可用方案主要有两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;哨兵模式（Sentinel）&lt;/strong&gt;：监控主从节点状态，在主节点故障时自动完成故障转移。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集群模式（Cluster）&lt;/strong&gt;：通过分片存储数据，提升容量上限，并支持水平扩展。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;哨兵更偏向主从高可用，集群更偏向容量扩展和分片治理。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;三缓存问题与解决方案&quot;&gt;三、缓存问题与解决方案&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;缓存击穿&quot;&gt;缓存击穿&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;缓存击穿指的是一个热点 Key 在高并发访问时突然过期，大量请求同时打到数据库。&lt;/p&gt;
&lt;p&gt;常见解决方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;热点 Key 不设置过期时间&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;div&gt;&lt;h3 id=&quot;缓存穿透&quot;&gt;缓存穿透&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;缓存穿透指的是大量请求访问不存在的数据，缓存无法命中，请求直接落到数据库。&lt;/p&gt;
&lt;p&gt;常见解决方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;布隆过滤器&lt;/strong&gt;：在请求进入缓存和数据库之前过滤明显不存在的 Key。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存空对象&lt;/strong&gt;：对不存在的数据短时间缓存空值，避免重复打到数据库。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数校验&lt;/strong&gt;：拦截非法 ID、异常参数和明显无效请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;缓存雪崩&quot;&gt;缓存雪崩&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;缓存雪崩指的是大量 Key 在同一时间过期，导致请求集中打到数据库。&lt;/p&gt;
&lt;p&gt;常见解决方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;随机过期时间&lt;/strong&gt;，避免大量 Key 同时失效。&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;div&gt;&lt;h2 id=&quot;四数据一致性与-redis-mysql-同步&quot;&gt;四、数据一致性与 Redis-MySQL 同步&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;双写一致性方案&quot;&gt;双写一致性方案&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;业务中常见的数据源组合是 MySQL 存储主数据，Redis 作为缓存。这里的核心问题是：数据库更新后，缓存如何保持一致。&lt;/p&gt;
&lt;p&gt;常见方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;先写 MySQL，再更新 Redis&lt;/strong&gt;：逻辑直接，但并发下可能出现旧值覆盖新值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先写 MySQL，再删除 Redis&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;：通过消息队列或 Canal 监听 MySQL Binlog，把数据变更异步同步到 Redis。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;业务场景适配&quot;&gt;业务场景适配&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;对实时一致性要求很高的场景，需要更严格的事务或强一致方案。&lt;/li&gt;
&lt;li&gt;对短暂延迟可接受的场景，通常使用缓存删除、消息队列、Binlog 同步等最终一致性方案。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缓存一致性没有绝对通用答案，重点是结合业务对“延迟、正确性、复杂度”的要求做取舍。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;五redis-性能优化&quot;&gt;五、Redis 性能优化&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;慢查询排查&quot;&gt;慢查询排查&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Redis 提供 &lt;code dir=&quot;auto&quot;&gt;slowlog&lt;/code&gt; 用于排查慢查询。常见优化方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;避免 &lt;code dir=&quot;auto&quot;&gt;KEYS *&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;SORT&lt;/code&gt;、大范围 &lt;code dir=&quot;auto&quot;&gt;SUNION&lt;/code&gt; 等高复杂度命令。&lt;/li&gt;
&lt;li&gt;对批量操作使用 Pipeline，减少网络往返。&lt;/li&gt;
&lt;li&gt;控制单次命令处理的数据规模，避免阻塞主线程。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;内存淘汰策略&quot;&gt;内存淘汰策略&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;当内存达到 &lt;code dir=&quot;auto&quot;&gt;maxmemory&lt;/code&gt; 限制时，Redis 会根据配置的淘汰策略处理 Key。&lt;/p&gt;
&lt;p&gt;常见策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;noeviction&lt;/strong&gt;：内存不足时拒绝写入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;volatile-lru&lt;/strong&gt;：只在设置了过期时间的 Key 中淘汰最近最少使用的 Key。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;volatile-lfu&lt;/strong&gt;：只在设置了过期时间的 Key 中淘汰最近最不常用的 Key。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;allkeys-lru&lt;/strong&gt;：在所有 Key 中淘汰最近最少使用的 Key。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;allkeys-lfu&lt;/strong&gt;：在所有 Key 中淘汰最近最不常用的 Key。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;allkeys-random&lt;/strong&gt;：在所有 Key 中随机淘汰。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缓存场景通常更常见 &lt;code dir=&quot;auto&quot;&gt;allkeys-lru&lt;/code&gt; 或 &lt;code dir=&quot;auto&quot;&gt;allkeys-lfu&lt;/code&gt;，但具体选择要看访问分布和业务容忍度。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;bigkey-与-hotkey&quot;&gt;BigKey 与 HotKey&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;BigKey&lt;/strong&gt; 指单个 Key 占用过大内存或包含过多元素，会导致网络传输、删除、迁移和持久化变慢。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HotKey&lt;/strong&gt; 指少数 Key 被高频访问，容易造成单点压力。&lt;/p&gt;
&lt;p&gt;优化方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;拆分过大的 Hash、List、Set、ZSet。&lt;/li&gt;
&lt;li&gt;避免一次性读取或删除大 Key。&lt;/li&gt;
&lt;li&gt;对热点数据做本地缓存、多副本缓存或请求合并。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;六线程模型与内存管理&quot;&gt;六、线程模型与内存管理&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;单线程模型&quot;&gt;单线程模型&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Redis 的命令执行主要是单线程模型，这让它避免了大量锁竞争，命令执行顺序也更容易理解。&lt;/p&gt;
&lt;p&gt;Redis 6.0 之后引入多线程处理网络 I/O，但命令执行本身仍然保持单线程语义。因此，慢命令、大 Key 操作、复杂聚合仍然可能阻塞 Redis。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;过期键删除策略&quot;&gt;过期键删除策略&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Redis 对过期 Key 主要使用两种删除策略：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;惰性删除&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;获取 Key 时检查是否过期，过期则删除。&lt;/li&gt;
&lt;li&gt;优点：不主动消耗额外资源。&lt;/li&gt;
&lt;li&gt;缺点：如果过期 Key 长时间不被访问，可能继续占用内存。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;定期删除&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Redis 定期随机抽样部分 Key，删除其中已经过期的 Key。&lt;/li&gt;
&lt;li&gt;优点：可以主动清理过期数据。&lt;/li&gt;
&lt;li&gt;缺点：不保证所有过期 Key 都会立刻被删除。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Redis 默认使用 &lt;strong&gt;惰性删除 + 定期删除&lt;/strong&gt; 的组合策略。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;七redis-变慢的排查方向&quot;&gt;七、Redis 变慢的排查方向&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Redis 变慢时，可以从以下方向排查：&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;硬件资源&quot;&gt;硬件资源&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;检查内存是否耗尽，是否发生 Swap，例如使用 &lt;code dir=&quot;auto&quot;&gt;free -m&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;检查磁盘 I/O，尤其是 AOF 写入和重写期间。&lt;/li&gt;
&lt;li&gt;使用 SSD 替代传统磁盘，降低持久化带来的 I/O 压力。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;命令复杂度&quot;&gt;命令复杂度&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;排查是否使用了 &lt;code dir=&quot;auto&quot;&gt;KEYS&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;SORT&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;SUNION&lt;/code&gt; 等高复杂度命令。&lt;/li&gt;
&lt;li&gt;检查是否存在大批量读取、大批量删除或大集合遍历。&lt;/li&gt;
&lt;li&gt;对批量请求使用 Pipeline，减少网络延迟。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;aof-重写阻塞&quot;&gt;AOF 重写阻塞&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;AOF 重写期间可能与 &lt;code dir=&quot;auto&quot;&gt;fsync&lt;/code&gt; 竞争磁盘资源。可以根据场景配置：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;no-appendfsync-on-rewrite yes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这个配置可以降低 AOF 重写期间的写入阻塞风险，但也会增加极端情况下的数据丢失窗口，需要结合业务容忍度评估。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;缓存雪崩或击穿&quot;&gt;缓存雪崩或击穿&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;如果数据库和 Redis 同时出现压力波动，需要排查是否存在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大量 Key 同时过期。&lt;/li&gt;
&lt;li&gt;热点 Key 突然失效。&lt;/li&gt;
&lt;li&gt;缓存重建逻辑没有互斥保护。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;八redis-vs-memcached&quot;&gt;八、Redis vs Memcached&lt;/h2&gt;&lt;/div&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;特性&lt;/th&gt;&lt;th&gt;Redis&lt;/th&gt;&lt;th&gt;Memcached&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;数据类型&lt;/td&gt;&lt;td&gt;String、Hash、List、Set、ZSet 等&lt;/td&gt;&lt;td&gt;主要支持简单 Key-Value&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;持久化&lt;/td&gt;&lt;td&gt;支持 RDB、AOF&lt;/td&gt;&lt;td&gt;不支持持久化&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;内存回收&lt;/td&gt;&lt;td&gt;支持多种淘汰策略&lt;/td&gt;&lt;td&gt;支持 LRU 等缓存淘汰机制&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&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;/td&gt;&lt;td&gt;命令执行以单线程为主，Redis 6.0 后支持 I/O 多线程&lt;/td&gt;&lt;td&gt;多线程网络模型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;场景适用&lt;/td&gt;&lt;td&gt;复杂缓存、计数、排行榜、分布式锁&lt;/td&gt;&lt;td&gt;简单缓存、高吞吐 Key-Value&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;简单说：如果只是做非常简单的缓存，Memcached 也可以胜任；如果需要更丰富的数据结构、持久化、高可用和分布式能力，Redis 更适合。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;九典型场景应用&quot;&gt;九、典型场景应用&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Redis 常见业务场景包括：&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;：存储用户 Session 或登录态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;排行榜&lt;/strong&gt;：通过 ZSet 实现 Top N 榜单。&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;code dir=&quot;auto&quot;&gt;SET key value NX PX&lt;/code&gt; 等命令实现基础锁能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;延迟队列&lt;/strong&gt;：通过 ZSet 分数存储执行时间，实现定时任务调度。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Redis 的核心价值在于 &lt;strong&gt;高性能访问、灵活数据结构、持久化能力和分布式支持&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;实际使用 Redis 时，需要重点关注四件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数据结构是否选对。&lt;/li&gt;
&lt;li&gt;缓存异常场景是否有兜底方案。&lt;/li&gt;
&lt;li&gt;Redis 与数据库之间是否能接受最终一致性。&lt;/li&gt;
&lt;li&gt;是否避免了慢命令、BigKey、HotKey 和集中过期。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;理解这些核心机制后，Redis 就不只是一个缓存组件，而是高并发系统中非常重要的性能与稳定性基础设施。&lt;/p&gt;</content:encoded><category>redis</category><category>数据库</category><category>sql</category></item><item><title>接触 Vibe Coding 八个多月后的感受</title><link>https://blog.veyliss.top/blog/vibe_coding_eight_months/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/vibe_coding_eight_months/</guid><pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;接触 Vibe Coding 已经八个多月了。&lt;/p&gt;
&lt;p&gt;回头看，这段时间给我带来的变化非常大。它不只是让我多认识了一些 AI 工具，也不只是让我写代码的速度变快了。更重要的是，它改变了我看待开发这件事的方式。&lt;/p&gt;
&lt;p&gt;以前我更关注技术本身。&lt;/p&gt;
&lt;p&gt;我会想这个功能应该怎么实现，代码怎么写，框架怎么选，接口怎么设计，数据库结构怎么拆。很多时候，注意力会自然落在“如何把代码写出来”这件事上。&lt;/p&gt;
&lt;p&gt;而现在，我越来越多地开始关注产品本身。&lt;/p&gt;
&lt;p&gt;这个功能为什么要做？用户会怎么使用？流程是不是顺？页面是不是清楚？这个需求背后真正要解决的业务问题是什么？这些问题慢慢变得比“代码怎么写”更靠前。&lt;/p&gt;
&lt;p&gt;这就是 Vibe Coding 对我最大的影响。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;从开发辅助开始&quot;&gt;从开发辅助开始&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;在更早的时候，我使用 AI 的方式其实很简单。&lt;/p&gt;
&lt;p&gt;去年的上半年，AI 对我来说更多还是一个开发辅助工具。它主要停留在 Web 式的 Chat 形态里，我会问它一些问题，让它帮我解释概念、检索资料、分析报错、生成一些代码片段。&lt;/p&gt;
&lt;p&gt;那个阶段的 AI 很像一个随时在线的助手。&lt;/p&gt;
&lt;p&gt;它能帮我查东西，也能帮我补充思路，但大多数时候，真正的开发过程还是由我自己主导。我要自己拆任务、自己打开项目、自己修改文件、自己调试和验证。&lt;/p&gt;
&lt;p&gt;AI 参与了过程，但没有真正进入开发工作流的中心。&lt;/p&gt;
&lt;p&gt;那时候我对它的理解也很朴素：它可以提高效率，可以减少搜索成本，可以帮我更快理解一些不熟悉的知识。&lt;/p&gt;
&lt;p&gt;但我还没有意识到，它会在后面改变整个开发方式。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;agent-概念开始爆发&quot;&gt;Agent 概念开始爆发&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;后来，Agent 的概念越来越火。&lt;/p&gt;
&lt;p&gt;我开始看到各种 CLI 工具出现，也开始频繁听到 &lt;code dir=&quot;auto&quot;&gt;token&lt;/code&gt;、上下文、模型网关、提示词、工具调用、代码代理这些词。AI 不再只是一个聊天窗口，它开始进入终端、进入编辑器、进入项目目录，甚至可以直接阅读代码、修改文件、运行命令、检查结果。&lt;/p&gt;
&lt;p&gt;这和以前完全不一样。&lt;/p&gt;
&lt;p&gt;以前是我把问题复制给 AI，然后把答案再搬回项目里。现在则更像是 AI 直接坐进了项目现场，和我一起看代码、改代码、验证代码。&lt;/p&gt;
&lt;p&gt;这时我也开始加入 Vibe Coding 的行列。&lt;/p&gt;
&lt;p&gt;刚开始的时候，我其实并不知道这些工具应该怎么用。面对各种模型、API、CLI、代理配置，我会有点茫然。它们看起来都很强，但真正落到自己的项目里，还是需要一段适应过程。&lt;/p&gt;
&lt;p&gt;我需要理解它们的边界。&lt;/p&gt;
&lt;p&gt;哪些事情可以交给它？哪些事情必须自己判断？什么时候应该让它改代码？什么时候只是让它分析？上下文应该怎么给？任务应该怎么拆？&lt;/p&gt;
&lt;p&gt;这些并不是看一篇教程就能立刻掌握的。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;工具链慢慢成形&quot;&gt;工具链慢慢成形&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;后来我逐渐知道了 New API 这类整合型网关，也开始理解它们在 AI 工作流中的意义。&lt;/p&gt;
&lt;p&gt;模型越来越多，不同模型有不同能力、价格和使用限制。如果每一个工具都单独配置，就会很分散。整合型网关的意义在于，它能把不同模型入口统一起来，让工具调用变得更稳定，也更容易管理。&lt;/p&gt;
&lt;p&gt;再后来，我接触到了 Claude Code 这类工具。&lt;/p&gt;
&lt;p&gt;它让我真正感受到“AI 参与编码”这件事和普通问答的区别。&lt;/p&gt;
&lt;p&gt;普通问答更像是你问一句，它答一句。CLI 编码工具则更像是你把它放进项目里，它可以沿着任务往前走：阅读文件、理解结构、修改代码、运行检查、再根据结果继续调整。&lt;/p&gt;
&lt;p&gt;这时候，AI 就不只是回答问题，而是在参与完成工作。&lt;/p&gt;
&lt;p&gt;当然，这并不意味着我可以完全放手。&lt;/p&gt;
&lt;p&gt;相反，我越来越感觉到，使用这类工具时，人要承担更高层次的判断。你要知道目标是什么，知道验收标准是什么，知道哪里不能乱动，知道生成的代码是否符合项目长期维护的方向。&lt;/p&gt;
&lt;p&gt;AI 可以很快，但方向仍然要由人来定。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;从关注代码到关注产品&quot;&gt;从关注代码到关注产品&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;过去我做一个东西，第一反应常常是技术问题。&lt;/p&gt;
&lt;p&gt;页面怎么写？接口怎么接？状态怎么管理？样式怎么调？&lt;/p&gt;
&lt;p&gt;现在我会先想产品问题。&lt;/p&gt;
&lt;p&gt;这个页面存在的目的是什么？用户第一眼应该看到什么？如果他想继续阅读，路径是不是顺？如果他在移动端打开，会不会困惑？如果内容越来越多，列表是否还能承载？如果未来要部署、维护、持续写文章，流程是不是足够轻？&lt;/p&gt;
&lt;p&gt;这种变化很明显。&lt;/p&gt;
&lt;p&gt;因为当 AI 可以承担大量具体编码工作后，我的注意力就被释放出来了。我不再需要把所有精力都压在每一行代码上，而是可以站得稍微高一点，看整个产品的结构和体验。&lt;/p&gt;
&lt;p&gt;这并不是说技术不重要。&lt;/p&gt;
&lt;p&gt;技术仍然重要，而且越到后面越重要。只是技术不再是唯一中心。它更像是实现产品目标的手段，而不是最终目的。&lt;/p&gt;
&lt;p&gt;以前我可能会因为某个技术点很有意思就想做点东西。现在我会先问：这个东西解决了什么问题？它对用户、对内容、对长期维护有什么价值？&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;业务流程变得更重要&quot;&gt;业务流程变得更重要&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Vibe Coding 也让我更关注业务流程。&lt;/p&gt;
&lt;p&gt;一个功能不是孤立存在的。它前面有入口，后面有结果，中间有状态变化和用户决策。只把某个页面写出来，并不代表功能真的完成。&lt;/p&gt;
&lt;p&gt;比如一个博客站，不只是能展示文章就够了。&lt;/p&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;li&gt;文章页如何让阅读体验稳定。&lt;/li&gt;
&lt;li&gt;部署后如何只专注维护内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些都不是单纯的代码问题，而是产品流程问题。&lt;/p&gt;
&lt;p&gt;当 AI 能帮我更快完成具体实现后，我反而会花更多时间思考这些流程是否合理。&lt;/p&gt;
&lt;p&gt;我会更在意一个功能放在系统里是不是自然，一个页面是不是为后续内容增长留好了空间，一个交互是不是符合读者直觉。&lt;/p&gt;
&lt;p&gt;这其实是更接近产品视角的思考。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;ai-让我更聚焦结果&quot;&gt;AI 让我更聚焦结果&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这八个多月里，我最大的感受是：AI 把很多“执行层面的阻力”变小了。&lt;/p&gt;
&lt;p&gt;以前想到一个功能，可能要先考虑技术栈、查文档、写样板代码、调样式、修报错。很多时候，还没真正验证想法，就已经被实现细节消耗掉了。&lt;/p&gt;
&lt;p&gt;现在不同了。&lt;/p&gt;
&lt;p&gt;我可以更快把想法变成可运行的东西，再通过实际效果判断它是否值得继续优化。&lt;/p&gt;
&lt;p&gt;这会让开发节奏发生变化。&lt;/p&gt;
&lt;p&gt;以前更像是先想很久，再动手实现。现在更像是先做出一个版本，然后不断观察、调整、迭代。&lt;/p&gt;
&lt;p&gt;AI 给我的不是简单的偷懒，而是更短的反馈周期。&lt;/p&gt;
&lt;p&gt;当反馈周期变短，人就更容易围绕结果做判断，而不是长时间停留在假设里。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;但人依然是关键&quot;&gt;但人依然是关键&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;使用 Vibe Coding 越久，我越觉得人并没有变得不重要。&lt;/p&gt;
&lt;p&gt;相反，人变得更重要。&lt;/p&gt;
&lt;p&gt;因为 AI 可以生成代码，但它不一定知道什么是适合你的。它可以给出方案，但它不知道你真正想要的产品气质。它可以完成任务，但它不会天然理解你的长期规划。&lt;/p&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;li&gt;验收结果。&lt;/li&gt;
&lt;li&gt;维护产品方向。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果没有这些判断，AI 很容易把事情做得很快，但不一定做得正确。&lt;/p&gt;
&lt;p&gt;这也是我慢慢学到的一点：Vibe Coding 不是随便让 AI 写代码，而是学会用清晰的目标和上下文引导它，把人的判断和 AI 的执行力结合起来。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;对我的改变&quot;&gt;对我的改变&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这段经历让我发生了几个明显变化。&lt;/p&gt;
&lt;p&gt;第一，我更愿意从产品角度看问题。&lt;/p&gt;
&lt;p&gt;我不再只问“这个功能怎么写”，而是会先问“这个功能为什么存在”。&lt;/p&gt;
&lt;p&gt;第二，我更重视流程。&lt;/p&gt;
&lt;p&gt;页面、内容、工具、部署、维护，它们应该连成一条顺畅的链路，而不是一个个孤立的点。&lt;/p&gt;
&lt;p&gt;第三，我对学习 AI 相关知识更有动力。&lt;/p&gt;
&lt;p&gt;从模型到上下文，从 CLI 工具到网关，从提示词到任务拆解，这些内容不再只是概念，而是会真实影响我每天的开发方式。&lt;/p&gt;
&lt;p&gt;第四，我开始更相信个人项目的可能性。&lt;/p&gt;
&lt;p&gt;以前一个人做完整产品会觉得很重。现在虽然仍然不轻松，但至少很多原本消耗人的细节可以被 AI 分担。一个人的上限，正在被工具重新拉高。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;写在最后&quot;&gt;写在最后&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;接触 Vibe Coding 的这八个多月，对我来说像是一次开发方式的迁移。&lt;/p&gt;
&lt;p&gt;我从把 AI 当作问答工具，慢慢转向把它当作开发协作者。也从更关注技术实现，逐渐转向关注产品本身、业务流程和最终结果。&lt;/p&gt;
&lt;p&gt;这种变化不是一夜之间发生的。&lt;/p&gt;
&lt;p&gt;它是在一次次尝试工具、配置模型、修改项目、验证结果的过程中慢慢形成的。&lt;/p&gt;
&lt;p&gt;现在的我依然还在学习 AI，也还在摸索更适合自己的工作流。但有一点已经很明确：未来的开发不会再回到过去那种完全依赖手工推进的状态。&lt;/p&gt;
&lt;p&gt;AI 会继续进入开发流程，而我需要做的，是学会站在更高的位置使用它。&lt;/p&gt;
&lt;p&gt;把注意力从代码细节里适当抽出来，更多地放到产品、体验、流程和价值上。&lt;/p&gt;
&lt;p&gt;这可能就是 Vibe Coding 最吸引我的地方。&lt;/p&gt;
&lt;p&gt;它不是让我不再关心技术，而是让我终于有更多精力去关心技术背后真正要完成的事情。&lt;/p&gt;</content:encoded><category>博客</category><category>随笔</category><category>AI</category></item><item><title>梦开始的地方</title><link>https://blog.veyliss.top/blog/dream/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/dream/</guid><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是我创建项目的第一篇博客文章。&lt;/p&gt;
&lt;p&gt;在这个博客里，我将分享我的技术学习历程、项目经验以及一些随笔思考。希望通过这个平台，能够记录下我的成长轨迹，也能与志同道合的朋友们交流和分享。&lt;/p&gt;
&lt;p&gt;我的名字叫做 veyliss，这是我“梦开始的地方”。我希望在这里能够记录下我的梦想和努力的过程，也希望能够激励自己不断前行。无论是技术上的突破，还是生活中的点滴，我都希望能够在这里留下足迹。&lt;/p&gt;
&lt;p&gt;在许多年前我就曾想着自己搭建真正属于自己的博客网站，记录自己的学习和成长。如今这个想法终于实现了，我感到非常兴奋和满足。这个博客不仅是一个记录工具，更是一个激励自己不断前进的动力源泉。&lt;/p&gt;
&lt;p&gt;我相信，通过这个博客，我能够更好地总结和反思自己的学习过程，也能够与更多的人分享我的经验和见解。无论是技术上的问题，还是生活中的思考，我都希望能够在这里找到共鸣和支持。&lt;/p&gt;
&lt;p&gt;最后，感谢每一个来到这个博客的朋友们，希望你们能够在这里找到有价值的内容，也希望我们能够在这里一起成长和进步。让我们一起在这个梦开始的地方，书写属于我们的故事吧！&lt;/p&gt;</content:encoded><category>博客</category><category>随笔</category></item><item><title>回顾 2025</title><link>https://blog.veyliss.top/blog/year_review_2025/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/year_review_2025/</guid><pubDate>Wed, 31 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;回望 2025 年，时间像被按下了快进键。&lt;/p&gt;
&lt;p&gt;这一年走得很快，也走得并不轻松。很多事情在开始时都带着热情，真正走到最后却发现，能完整收尾的并不算多。想做的项目、想沉淀的知识、想持续推进的计划，有些还停留在半成品阶段，有些甚至只是短暂地闪过念头。&lt;/p&gt;
&lt;p&gt;这并不是一个让人完全满意的年份。&lt;/p&gt;
&lt;p&gt;但它也不是毫无意义的一年。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;工作带来的真实成长&quot;&gt;工作带来的真实成长&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这一年里，有一段连续而紧张的工作经历。&lt;/p&gt;
&lt;p&gt;那几个月很充实，也很消耗人。每天都在处理新的任务，面对新的问题，试着把一些看起来并不容易的事情往前推进。工作中，我尽力认真对待每一项安排，也不断尝试挑战自己原本觉得困难的部分。&lt;/p&gt;
&lt;p&gt;这段经历让我收获了不少东西。&lt;/p&gt;
&lt;p&gt;我积累了更真实的工作经验，也接触到了很多优秀的同事和前辈。从他们身上，我看到了更成熟的处理方式、更稳定的职业节奏，以及面对复杂问题时更清晰的判断。&lt;/p&gt;
&lt;p&gt;这些东西不是单靠看教程、写练习就能得到的。它们来自具体的工作现场，来自一次次沟通、交付、修改和复盘。&lt;/p&gt;
&lt;p&gt;不过遗憾也很明显。&lt;/p&gt;
&lt;p&gt;个人能力确实在提升，但还没有达到自己期待中的飞跃。和行业里真正优秀的人相比，差距依然存在，而且并不小。意识到这一点的时候，会有一点失落，但也会更清楚自己接下来应该往哪里用力。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;疲惫也是这一年的关键词&quot;&gt;疲惫也是这一年的关键词&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;下班后的疲惫，是这一年很真实的一部分。&lt;/p&gt;
&lt;p&gt;这种疲惫不只是身体上的，更是精神上的。忙碌一天之后，大脑长时间处在紧绷状态。回到住处，整个人像被抽空了能量，明明知道还有很多东西值得学习，却很难再把自己重新拉回专注状态。&lt;/p&gt;
&lt;p&gt;我曾经计划利用业余时间继续学习新知识，补足能力短板，也想持续推进一些个人项目。可是很多时候，打开资料、看到待办列表，就会先感到一阵无力。&lt;/p&gt;
&lt;p&gt;于是一些计划被推迟，一些想法被搁置，一些原本应该继续打磨的东西，也慢慢停在了半路。&lt;/p&gt;
&lt;p&gt;这并不值得美化。&lt;/p&gt;
&lt;p&gt;它只是提醒我：人的精力是有限的，自我提升也不能只靠一时热情。真正能走得远的，应该是更稳定、更可持续的节奏。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;知识体系仍然需要建立&quot;&gt;知识体系仍然需要建立&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这一年，我也多次想过要构建自己的知识体系。&lt;/p&gt;
&lt;p&gt;我越来越能感受到，零散学习带来的问题很明显。今天看一点后端，明天补一点前端，后天又去了解新的工具和概念，短期内似乎学了很多，但如果没有整理和连接，这些知识很容易散落在各处。&lt;/p&gt;
&lt;p&gt;我希望能把这些零散的知识点串联起来。&lt;/p&gt;
&lt;p&gt;不是为了把自己包装得很厉害，而是希望在未来遇到问题时，能更快找到方向，知道某个知识点在整个技术体系里处于什么位置，也能把过去踩过的坑、做过的项目、解决过的问题留下来。&lt;/p&gt;
&lt;p&gt;这也是我后来越来越想认真维护博客和知识库的原因。&lt;/p&gt;
&lt;p&gt;文字不是为了证明什么，而是为了给自己留下路径。现在的记录，也许会成为未来某个阶段重新出发时的坐标。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;遇见更多不同的人&quot;&gt;遇见更多不同的人&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;2025 年，我结识了很多圈内朋友。&lt;/p&gt;
&lt;p&gt;他们来自不同领域，有交易所、Web3、外卖行业、传统行业、SaaS 领域，也有来自大厂的朋友。还有一些自由职业者，他们的工作方式和生活状态让我产生过很多羡慕与向往。&lt;/p&gt;
&lt;p&gt;和这些人交流，会明显感觉到世界比自己日常接触到的范围更大。&lt;/p&gt;
&lt;p&gt;不同的人在不同赛道里寻找机会，也在用各自的方式处理工作、生活和成长之间的关系。有的人很专注，有的人很灵活，有的人已经找到了相对自由的节奏，也有人还在变化里持续摸索。&lt;/p&gt;
&lt;p&gt;这些交流让我意识到，职业发展不是只有一条路。&lt;/p&gt;
&lt;p&gt;稳定工作是一种选择，持续深耕是一种选择，做项目、做产品、做自由职业，也都是不同的选择。重要的是，自己要逐渐知道想过怎样的生活，并为之积累足够的能力。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;ai-时代正在加速到来&quot;&gt;AI 时代正在加速到来&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;2025 年也是 AI 快速爆发的一年。&lt;/p&gt;
&lt;p&gt;越来越多行业开始投入 AI 相关研发，尝试用 AI 提升效率、优化流程、创造新的业务价值。无论是技术研发、内容生产，还是产品设计、数据分析，AI 都在逐渐进入日常工作。&lt;/p&gt;
&lt;p&gt;这不是一个遥远的趋势，而是正在发生的变化。&lt;/p&gt;
&lt;p&gt;我能明显感觉到，未来互联网生态会和 AI 更紧密地连接在一起。很多岗位的工作方式会被改变，很多工具会被重做，很多原本依赖人工经验的流程，也会被新的方式重新组织。&lt;/p&gt;
&lt;p&gt;这对个人来说既是压力，也是机会。&lt;/p&gt;
&lt;p&gt;压力在于，原本掌握的技能可能很快变得不够用。机会在于，如果能更早理解这些变化，并把 AI 当成能力放大器，就有可能在新的阶段找到更好的位置。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;写在最后&quot;&gt;写在最后&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;2025 年并不是一个完成度很高的年份。&lt;/p&gt;
&lt;p&gt;它有遗憾，有拖延，有没有收尾的项目，也有很多没有真正落实的计划。但它同样留下了工作经验、人际连接、行业观察和对自我节奏的重新认识。&lt;/p&gt;
&lt;p&gt;我不想把这一年写得过于漂亮。&lt;/p&gt;
&lt;p&gt;因为真实的成长并不总是热血的。很多时候，它只是一次次发现自己的不足，然后在疲惫里慢慢调整方向。&lt;/p&gt;
&lt;p&gt;如果要给 2025 年一个总结，我想它更像是一个提醒：&lt;/p&gt;
&lt;p&gt;不要只依赖热情，也不要害怕缓慢。&lt;/p&gt;
&lt;p&gt;真正重要的，是在每一次停顿之后，还能重新开始。&lt;/p&gt;</content:encoded><category>博客</category><category>随笔</category></item><item><title>Java 校招面试题复盘清单</title><link>https://blog.veyliss.top/blog/java_campus_interview_review_01/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/java_campus_interview_review_01/</guid><pubDate>Wed, 29 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是一份 Java 校招面试题复盘清单。&lt;/p&gt;
&lt;p&gt;它不适合当作“背诵稿”逐字记忆，更适合当作复习地图：先知道面试会问哪些方向，再把每个问题整理成可以讲清楚的核心答案。&lt;/p&gt;
&lt;p&gt;Java 校招面试通常会围绕三部分展开：&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;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;约 40%&lt;/td&gt;&lt;td&gt;Java、Spring、MySQL、Redis、JVM、并发&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;项目深挖&lt;/td&gt;&lt;td&gt;约 40%&lt;/td&gt;&lt;td&gt;项目背景、技术选型、难点、优化、问题排查&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;学习能力&lt;/td&gt;&lt;td&gt;约 20%&lt;/td&gt;&lt;td&gt;最近在学什么、为什么做 Java、如何解决问题&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果项目里有 Elasticsearch，那么 ES 往往会成为面试官重点追问的方向。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;java-基础&quot;&gt;Java 基础&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;hashmap-的底层原理是什么&quot;&gt;HashMap 的底层原理是什么&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;HashMap&lt;/code&gt; 底层主要是数组、链表和红黑树。&lt;/p&gt;
&lt;p&gt;当放入一个 key-value 时，会先根据 key 的 &lt;code dir=&quot;auto&quot;&gt;hashCode()&lt;/code&gt; 计算 hash，再定位到数组下标。如果该位置没有元素，就直接放入；如果已经有元素，就会形成链表或红黑树。&lt;/p&gt;
&lt;p&gt;在 Java 8 之后，当链表长度达到一定阈值，并且数组容量足够大时，链表会转换为红黑树，用来提高查询效率。&lt;/p&gt;
&lt;p&gt;可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;HashMap 通过 hash 定位数组下标，数组中每个位置叫 bucket。发生 hash 冲突时，Java 8 以前主要用链表，Java 8 之后链表过长会转为红黑树。扩容时会重新计算元素位置，所以 HashMap 的性能和初始容量、负载因子、hash 分布都有关系。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h3 id=&quot;hashmap-为什么线程不安全&quot;&gt;HashMap 为什么线程不安全&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;HashMap&lt;/code&gt; 没有做同步控制，多线程同时读写时可能出现数据覆盖、状态不一致、扩容异常等问题。&lt;/p&gt;
&lt;p&gt;常见风险：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多个线程同时 &lt;code dir=&quot;auto&quot;&gt;put&lt;/code&gt;，可能覆盖彼此写入。&lt;/li&gt;
&lt;li&gt;扩容时结构变化，其他线程同时访问可能拿到异常结果。&lt;/li&gt;
&lt;li&gt;统计数量 &lt;code dir=&quot;auto&quot;&gt;size&lt;/code&gt; 可能不准确。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以多线程场景通常使用 &lt;code dir=&quot;auto&quot;&gt;ConcurrentHashMap&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;concurrenthashmap-如何实现线程安全&quot;&gt;ConcurrentHashMap 如何实现线程安全&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Java 8 中，&lt;code dir=&quot;auto&quot;&gt;ConcurrentHashMap&lt;/code&gt; 主要通过 CAS 和 &lt;code dir=&quot;auto&quot;&gt;synchronized&lt;/code&gt; 保证线程安全。&lt;/p&gt;
&lt;p&gt;它不是给整张表加一把大锁，而是尽量缩小锁粒度。&lt;/p&gt;
&lt;p&gt;常见回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Java 8 的 ConcurrentHashMap 底层也是数组、链表和红黑树。插入时，如果桶为空，会通过 CAS 放入节点；如果桶不为空，会对桶头节点加 synchronized，只锁当前桶。这样既保证线程安全，又比 Hashtable 整表加锁性能更好。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h3 id=&quot;arraylist-和-linkedlist-的区别&quot;&gt;ArrayList 和 LinkedList 的区别&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ArrayList&lt;/code&gt; 底层是动态数组，&lt;code dir=&quot;auto&quot;&gt;LinkedList&lt;/code&gt; 底层是双向链表。&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;对比项&lt;/th&gt;&lt;th&gt;ArrayList&lt;/th&gt;&lt;th&gt;LinkedList&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&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;/td&gt;&lt;td&gt;快，按下标访问 O(1)&lt;/td&gt;&lt;td&gt;慢，需要遍历&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&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;/td&gt;&lt;td&gt;相对较少&lt;/td&gt;&lt;td&gt;每个节点要存前后指针&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&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;面试里要注意：不要简单说 &lt;code dir=&quot;auto&quot;&gt;LinkedList&lt;/code&gt; 插入删除一定快。因为如果要先按索引找到位置，遍历本身也有成本。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;stringstringbuilderstringbuffer-的区别&quot;&gt;String、StringBuilder、StringBuffer 的区别&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;String&lt;/code&gt; 是不可变对象，每次修改都会产生新字符串。&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;StringBuilder&lt;/code&gt; 是可变字符序列，线程不安全，但性能较好。&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;StringBuffer&lt;/code&gt; 也是可变字符序列，方法加了同步，线程安全，但性能通常低于 &lt;code dir=&quot;auto&quot;&gt;StringBuilder&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;常见选择：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;少量字符串拼接：直接用 &lt;code dir=&quot;auto&quot;&gt;String&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;单线程大量拼接：用 &lt;code dir=&quot;auto&quot;&gt;StringBuilder&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;多线程共享拼接对象：用 &lt;code dir=&quot;auto&quot;&gt;StringBuffer&lt;/code&gt;，但实际业务中较少这样用。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;equals-和--的区别&quot;&gt;equals 和 == 的区别&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;==&lt;/code&gt; 比较的是两边是否相等。&lt;/p&gt;
&lt;p&gt;对于基本类型，比较的是值。&lt;/p&gt;
&lt;p&gt;对于引用类型，比较的是对象地址。&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;equals()&lt;/code&gt; 是对象方法，默认实现也是比较地址，但很多类会重写它，比如 &lt;code dir=&quot;auto&quot;&gt;String&lt;/code&gt; 会比较字符串内容。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;equals-demo.java&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;hello&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;hello&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;System&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;System&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;equals&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;java-中的异常体系是什么&quot;&gt;Java 中的异常体系是什么&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Java 异常体系的顶层是 &lt;code dir=&quot;auto&quot;&gt;Throwable&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它下面主要分为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Error&lt;/code&gt;：严重错误，程序通常不主动处理，比如 &lt;code dir=&quot;auto&quot;&gt;OutOfMemoryError&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Exception&lt;/code&gt;：程序可以捕获和处理的异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Exception&lt;/code&gt; 又分为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;checked exception：编译期异常，必须处理或声明抛出。&lt;/li&gt;
&lt;li&gt;unchecked exception：运行时异常，继承自 &lt;code dir=&quot;auto&quot;&gt;RuntimeException&lt;/code&gt;，例如空指针、数组越界。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;面试里可以补一句：业务开发中不要滥用异常控制正常流程，异常更适合表示非预期情况。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是反射&quot;&gt;什么是反射&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;反射是 Java 在运行时获取类信息、创建对象、调用方法、访问字段的能力。&lt;/p&gt;
&lt;p&gt;常见应用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring 创建 Bean、依赖注入。&lt;/li&gt;
&lt;li&gt;MyBatis 映射对象字段。&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;div&gt;&lt;h2 id=&quot;java-并发&quot;&gt;Java 并发&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是线程什么是进程&quot;&gt;什么是线程，什么是进程&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;进程是操作系统资源分配的基本单位。一个应用程序运行起来通常就是一个进程。&lt;/p&gt;
&lt;p&gt;线程是 CPU 调度的基本单位，一个进程中可以包含多个线程。&lt;/p&gt;
&lt;p&gt;可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;进程拥有独立的内存空间，线程共享同一进程的内存资源。线程切换成本通常低于进程，但共享数据也会带来线程安全问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h3 id=&quot;synchronized-的原理是什么&quot;&gt;synchronized 的原理是什么&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;synchronized&lt;/code&gt; 可以修饰方法或代码块，用来保证同一时间只有一个线程进入临界区。&lt;/p&gt;
&lt;p&gt;它依赖对象监视器锁，也就是 monitor。&lt;/p&gt;
&lt;p&gt;进入同步代码块时，线程尝试获取对象锁；执行完或异常退出时释放锁。&lt;/p&gt;
&lt;p&gt;可以补充：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修饰普通方法，锁的是当前对象 &lt;code dir=&quot;auto&quot;&gt;this&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;修饰静态方法，锁的是当前类的 &lt;code dir=&quot;auto&quot;&gt;Class&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;修饰代码块，可以指定锁对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;volatile-的作用是什么&quot;&gt;volatile 的作用是什么&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;volatile&lt;/code&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;它不能保证复合操作的原子性。&lt;/p&gt;
&lt;p&gt;比如 &lt;code dir=&quot;auto&quot;&gt;count++&lt;/code&gt; 包含读取、加一、写回，不是一个原子操作，所以只加 &lt;code dir=&quot;auto&quot;&gt;volatile&lt;/code&gt; 仍然不安全。&lt;/p&gt;
&lt;p&gt;常见场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;状态标志位。&lt;/li&gt;
&lt;li&gt;单例模式双重检查锁中的实例变量。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是线程池&quot;&gt;什么是线程池&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;线程池是提前创建并管理一组线程，任务来了以后交给线程池执行。&lt;/p&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;div&gt;&lt;h3 id=&quot;threadpoolexecutor-核心参数有哪些&quot;&gt;ThreadPoolExecutor 核心参数有哪些&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ThreadPoolExecutor&lt;/code&gt; 常见核心参数：&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;&lt;code dir=&quot;auto&quot;&gt;corePoolSize&lt;/code&gt;&lt;/td&gt;&lt;td&gt;核心线程数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;maximumPoolSize&lt;/code&gt;&lt;/td&gt;&lt;td&gt;最大线程数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;keepAliveTime&lt;/code&gt;&lt;/td&gt;&lt;td&gt;非核心线程空闲存活时间&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;unit&lt;/code&gt;&lt;/td&gt;&lt;td&gt;时间单位&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;workQueue&lt;/code&gt;&lt;/td&gt;&lt;td&gt;任务队列&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;threadFactory&lt;/code&gt;&lt;/td&gt;&lt;td&gt;线程创建工厂&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;handler&lt;/code&gt;&lt;/td&gt;&lt;td&gt;拒绝策略&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;执行流程可以概括为：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;核心线程未满 -&gt; 创建核心线程&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;核心线程已满 -&gt; 放入任务队列&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;队列满了 -&gt; 创建非核心线程&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;线程数达到最大且队列也满 -&gt; 执行拒绝策略&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是死锁如何避免&quot;&gt;什么是死锁，如何避免&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;死锁是多个线程互相持有对方需要的资源，导致都无法继续执行。&lt;/p&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;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;li&gt;使用并发工具类代替手写锁。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;jvm&quot;&gt;JVM&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;jvm-的内存结构是什么&quot;&gt;JVM 的内存结构是什么&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;JVM 运行时数据区主要包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;程序计数器。&lt;/li&gt;
&lt;li&gt;Java 虚拟机栈。&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;ul&gt;
&lt;li&gt;程序计数器。&lt;/li&gt;
&lt;li&gt;Java 虚拟机栈。&lt;/li&gt;
&lt;li&gt;本地方法栈。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;线程共享：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;堆。&lt;/li&gt;
&lt;li&gt;方法区。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;堆和栈的区别&quot;&gt;堆和栈的区别&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;栈主要存放方法调用相关信息，比如局部变量表、操作数栈、方法出口等。&lt;/p&gt;
&lt;p&gt;堆主要存放对象实例，是垃圾回收重点关注的区域。&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;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;线程私有&lt;/td&gt;&lt;td&gt;线程共享&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&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;/td&gt;&lt;td&gt;随方法调用入栈出栈&lt;/td&gt;&lt;td&gt;由 GC 管理&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;常见错误&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;StackOverflowError&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;OutOfMemoryError&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是垃圾回收&quot;&gt;什么是垃圾回收&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;垃圾回收是 JVM 自动回收不再使用对象的机制。&lt;/p&gt;
&lt;p&gt;判断对象是否可回收，主流方法是可达性分析：从 GC Roots 出发，能到达的对象是存活对象，不能到达的对象可以被回收。&lt;/p&gt;
&lt;p&gt;常见 GC Roots：&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;div&gt;&lt;h3 id=&quot;常见-gc-算法有哪些&quot;&gt;常见 GC 算法有哪些&lt;/h3&gt;&lt;/div&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;div&gt;&lt;h3 id=&quot;什么情况下会发生-oom&quot;&gt;什么情况下会发生 OOM&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;OOM 是内存不足导致的错误。&lt;/p&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;li&gt;元空间加载类过多。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;排查时通常会看日志、堆 dump、GC 情况和对象引用链。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;jvm-调优一般关注哪些参数&quot;&gt;JVM 调优一般关注哪些参数&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;常见关注点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;堆大小：&lt;code dir=&quot;auto&quot;&gt;-Xms&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;-Xmx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;新生代大小：&lt;code dir=&quot;auto&quot;&gt;-Xmn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;元空间大小：&lt;code dir=&quot;auto&quot;&gt;-XX:MetaspaceSize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GC 收集器选择。&lt;/li&gt;
&lt;li&gt;GC 日志。&lt;/li&gt;
&lt;li&gt;停顿时间和吞吐量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;调优不是盲目改参数，而是先看现象：内存是否够、GC 是否频繁、停顿是否过长、对象是否异常增长。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;spring&quot;&gt;Spring&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是-ioc&quot;&gt;什么是 IoC&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;IoC 是控制反转。&lt;/p&gt;
&lt;p&gt;原本对象由程序自己创建和管理，现在交给 Spring 容器创建和管理。&lt;/p&gt;
&lt;p&gt;DI 依赖注入是 IoC 的一种实现方式。比如一个 Service 依赖 Mapper，不需要自己 &lt;code dir=&quot;auto&quot;&gt;new&lt;/code&gt;，而是由 Spring 注入。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是-aop&quot;&gt;什么是 AOP&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;AOP 是面向切面编程，用来把通用逻辑从业务代码中抽离出来。&lt;/p&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;li&gt;接口耗时。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;核心思想是：不修改业务方法本身，在方法执行前后织入增强逻辑。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;bean-的生命周期是什么&quot;&gt;Bean 的生命周期是什么&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;简化流程：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;实例化 -&gt; 属性注入 -&gt; 初始化前后处理 -&gt; 初始化方法 -&gt; 使用 -&gt; 销毁&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&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;code dir=&quot;auto&quot;&gt;BeanPostProcessor&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;InitializingBean&lt;/code&gt; 或 &lt;code dir=&quot;auto&quot;&gt;init-method&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;DisposableBean&lt;/code&gt; 或 &lt;code dir=&quot;auto&quot;&gt;destroy-method&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;为什么使用-spring-boot&quot;&gt;为什么使用 Spring Boot&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Spring Boot 主要解决传统 Spring 项目配置繁琐的问题。&lt;/p&gt;
&lt;p&gt;优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动配置。&lt;/li&gt;
&lt;li&gt;内置 Web 容器。&lt;/li&gt;
&lt;li&gt;starter 依赖简化。&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;blockquote&gt;
&lt;p&gt;Spring Boot 让 Spring 项目更容易启动和维护，它通过自动配置和 starter 机制减少大量 XML 或手动配置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h3 id=&quot;spring-boot-自动配置原理是什么&quot;&gt;Spring Boot 自动配置原理是什么&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;自动配置的核心是根据类路径、配置文件和条件注解，自动创建合适的 Bean。&lt;/p&gt;
&lt;p&gt;常见关键词：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;starter。&lt;/li&gt;
&lt;li&gt;auto configuration。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;@EnableAutoConfiguration&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;条件注解，比如 &lt;code dir=&quot;auto&quot;&gt;@ConditionalOnClass&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;@ConditionalOnMissingBean&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;面试中可以这样说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Spring Boot 会根据引入的依赖和当前环境判断是否满足条件，如果满足，就把对应配置类里的 Bean 注册到容器中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是-restful-api&quot;&gt;什么是 RESTful API&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;RESTful API 是一种接口设计风格。&lt;/p&gt;
&lt;p&gt;核心思想：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 URL 表示资源。&lt;/li&gt;
&lt;li&gt;使用 HTTP 方法表示操作。&lt;/li&gt;
&lt;li&gt;使用状态码表示结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;GET /users/1      查询用户&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;POST /users       创建用户&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;PUT /users/1      更新用户&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DELETE /users/1   删除用户&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;mysql&quot;&gt;MySQL&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;mysql-索引是什么&quot;&gt;MySQL 索引是什么&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;索引是帮助 MySQL 快速查找数据的数据结构。&lt;/p&gt;
&lt;p&gt;可以理解为书的目录。没有索引时，数据库可能要全表扫描；有索引时，可以更快定位数据。&lt;/p&gt;
&lt;p&gt;索引能提高查询效率，但会增加写入成本和存储空间。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mysql-为什么使用-btree&quot;&gt;MySQL 为什么使用 B+Tree&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;B+Tree 适合磁盘存储和范围查询。&lt;/p&gt;
&lt;p&gt;原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;树高度低，减少磁盘 IO。&lt;/li&gt;
&lt;li&gt;非叶子节点只存索引，能放更多 key。&lt;/li&gt;
&lt;li&gt;叶子节点之间有链表，范围查询效率高。&lt;/li&gt;
&lt;li&gt;查询性能稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是覆盖索引&quot;&gt;什么是覆盖索引&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;覆盖索引指查询需要的字段都能从索引中获得，不需要回表查询。&lt;/p&gt;
&lt;p&gt;比如有联合索引 &lt;code dir=&quot;auto&quot;&gt;(name, age)&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, age &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; user &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果查询字段都在索引里，就可能走覆盖索引。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;什么情况下索引会失效&quot;&gt;什么情况下索引会失效&lt;/h3&gt;&lt;/div&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;code dir=&quot;auto&quot;&gt;LIKE &apos;%abc&apos;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;联合索引不符合最左前缀原则。&lt;/li&gt;
&lt;li&gt;隐式类型转换。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;OR&lt;/code&gt; 条件使用不当。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是事务&quot;&gt;什么是事务&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;事务是一组数据库操作的集合，要么全部成功，要么全部失败。&lt;/p&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;/ul&gt;
&lt;p&gt;这些操作应该作为一个整体处理。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mysql-事务的-acid&quot;&gt;MySQL 事务的 ACID&lt;/h3&gt;&lt;/div&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;Atomicity 原子性&lt;/td&gt;&lt;td&gt;事务要么全部成功，要么全部失败&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Consistency 一致性&lt;/td&gt;&lt;td&gt;事务前后数据满足约束&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Isolation 隔离性&lt;/td&gt;&lt;td&gt;并发事务之间互相隔离&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Durability 持久性&lt;/td&gt;&lt;td&gt;事务提交后数据持久保存&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是-mvcc&quot;&gt;什么是 MVCC&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;MVCC 是多版本并发控制。&lt;/p&gt;
&lt;p&gt;它通过保存数据的多个版本，让读写尽量不互相阻塞。&lt;/p&gt;
&lt;p&gt;在 InnoDB 中，MVCC 主要依赖：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;隐藏字段。&lt;/li&gt;
&lt;li&gt;undo log。&lt;/li&gt;
&lt;li&gt;ReadView。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见作用是支持可重复读和快照读。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;redis&quot;&gt;Redis&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;redis-为什么这么快&quot;&gt;Redis 为什么这么快&lt;/h3&gt;&lt;/div&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;IO 多路复用。&lt;/li&gt;
&lt;li&gt;C 语言实现，执行效率高。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;redis-有哪些数据结构&quot;&gt;Redis 有哪些数据结构&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;常见类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;String。&lt;/li&gt;
&lt;li&gt;Hash。&lt;/li&gt;
&lt;li&gt;List。&lt;/li&gt;
&lt;li&gt;Set。&lt;/li&gt;
&lt;li&gt;Sorted Set。&lt;/li&gt;
&lt;li&gt;Bitmap。&lt;/li&gt;
&lt;li&gt;HyperLogLog。&lt;/li&gt;
&lt;li&gt;Geo。&lt;/li&gt;
&lt;li&gt;Stream。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是缓存穿透&quot;&gt;什么是缓存穿透&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;缓存穿透是请求的数据在缓存和数据库中都不存在，导致请求持续打到数据库。&lt;/p&gt;
&lt;p&gt;解决方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存空对象。&lt;/li&gt;
&lt;li&gt;布隆过滤器。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是缓存击穿&quot;&gt;什么是缓存击穿&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;缓存击穿是热点 Key 过期瞬间，大量并发请求同时打到数据库。&lt;/p&gt;
&lt;p&gt;解决方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;互斥锁。&lt;/li&gt;
&lt;li&gt;逻辑过期。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是缓存雪崩&quot;&gt;什么是缓存雪崩&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;缓存雪崩是大量 Key 同时过期，或者 Redis 整体不可用，导致大量请求涌向数据库。&lt;/p&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;Redis 高可用。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;elasticsearch&quot;&gt;Elasticsearch&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是-elasticsearch&quot;&gt;什么是 Elasticsearch&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Elasticsearch 是一个分布式搜索和分析引擎，常用于全文搜索、日志检索、商品搜索等场景。&lt;/p&gt;
&lt;p&gt;它底层基于 Lucene，对外提供 REST API，支持分布式、倒排索引和复杂查询。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是倒排索引&quot;&gt;什么是倒排索引&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;倒排索引是从词到文档的映射。&lt;/p&gt;
&lt;p&gt;普通索引更像：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;文档 -&gt; 包含哪些词&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;倒排索引更像：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;词 -&gt; 出现在哪些文档中&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这就是 ES 做全文搜索快的重要原因。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;es-中-indextypedocument-是什么&quot;&gt;ES 中 index、type、document 是什么&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;index&lt;/code&gt; 类似一类数据的集合。&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;document&lt;/code&gt; 是一条 JSON 数据。&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;type&lt;/code&gt; 在早期版本中用于区分类型，但新版本已经逐步移除，不建议在新项目中依赖 type。&lt;/p&gt;
&lt;p&gt;可以类比：&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;ES&lt;/th&gt;&lt;th&gt;关系型数据库&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;index&lt;/td&gt;&lt;td&gt;table 或 database 的某种集合概念&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;document&lt;/td&gt;&lt;td&gt;row&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;field&lt;/td&gt;&lt;td&gt;column&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是-dsl-查询&quot;&gt;什么是 DSL 查询&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;DSL 是 Elasticsearch 的查询语言，使用 JSON 描述查询条件。&lt;/p&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;es-dsl.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;query&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;match&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;bool-query-有哪些条件&quot;&gt;bool query 有哪些条件&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;常见条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;must&lt;/code&gt;：必须匹配，影响评分。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;should&lt;/code&gt;：可选匹配，可能影响评分。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;filter&lt;/code&gt;：必须匹配，但不参与评分，适合过滤条件。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;must_not&lt;/code&gt;：必须不匹配。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;为什么-elasticsearch-搜索比-mysql-快&quot;&gt;为什么 Elasticsearch 搜索比 MySQL 快&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;ES 在全文搜索场景下更快，主要因为它使用倒排索引。&lt;/p&gt;
&lt;p&gt;MySQL 更擅长结构化数据查询和事务处理，全文检索不是它最核心的场景。&lt;/p&gt;
&lt;p&gt;可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ES 会先对文本分词，再建立词到文档的倒排索引。查询关键词时，可以快速定位包含该词的文档。MySQL B+Tree 索引更适合精确匹配和范围查询，对复杂全文搜索、相关性评分和分词检索不如 ES 合适。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;系统与运维基础&quot;&gt;系统与运维基础&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是-docker&quot;&gt;什么是 Docker&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Docker 是容器化技术，可以把应用和依赖打包成镜像，再以容器方式运行。&lt;/p&gt;
&lt;p&gt;它解决了“本地能跑，服务器不能跑”的环境一致性问题。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;docker-和虚拟机有什么区别&quot;&gt;Docker 和虚拟机有什么区别&lt;/h3&gt;&lt;/div&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;对比项&lt;/th&gt;&lt;th&gt;Docker&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;进程级隔离&lt;/td&gt;&lt;td&gt;硬件级虚拟化&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&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;/td&gt;&lt;td&gt;较少&lt;/td&gt;&lt;td&gt;较多&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&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;/td&gt;&lt;td&gt;应用部署、微服务&lt;/td&gt;&lt;td&gt;强隔离、多系统环境&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;linux-常用命令有哪些&quot;&gt;Linux 常用命令有哪些&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;常见命令：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;linux-commands.sh&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pwd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mv&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ps&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-ef&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;top&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;df&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-h&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;free&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chown&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;面试时如果结合项目部署经历回答，会比单纯背命令更好。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是-rest-api&quot;&gt;什么是 REST API&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;REST API 是基于 REST 风格设计的接口。&lt;/p&gt;
&lt;p&gt;它通常使用 HTTP 协议，通过 URL 表示资源，通过 HTTP 方法表示操作。&lt;/p&gt;
&lt;p&gt;这个问题和 Spring 里的 RESTful API 本质相同。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是微服务架构&quot;&gt;什么是微服务架构&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;微服务是把一个大系统拆成多个小服务，每个服务负责一个相对独立的业务能力。&lt;/p&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;ul&gt;
&lt;li&gt;服务间调用复杂。&lt;/li&gt;
&lt;li&gt;分布式事务困难。&lt;/li&gt;
&lt;li&gt;监控、链路追踪、部署复杂度提高。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;什么是-api-网关&quot;&gt;什么是 API 网关&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;API 网关是系统入口，负责把外部请求转发到内部服务。&lt;/p&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;li&gt;日志。&lt;/li&gt;
&lt;li&gt;跨域处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;项目深挖elasticsearch-项目&quot;&gt;项目深挖：Elasticsearch 项目&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果你的项目重点是 Elasticsearch，面试官很可能会围绕项目继续追问。&lt;/p&gt;
&lt;p&gt;可以提前准备这些问题：&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;为什么项目要用 ES&lt;/td&gt;&lt;td&gt;MySQL 搜索能力不足、全文检索、分词、排序、性能&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;数据怎么同步到 ES&lt;/td&gt;&lt;td&gt;同步写、异步消息、定时补偿&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ES 和 MySQL 数据不一致怎么办&lt;/td&gt;&lt;td&gt;重试、补偿任务、最终一致性&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;索引怎么设计&lt;/td&gt;&lt;td&gt;index、mapping、分词器、字段类型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;搜索结果怎么排序&lt;/td&gt;&lt;td&gt;相关性评分、业务权重、时间、热度&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;查询慢怎么优化&lt;/td&gt;&lt;td&gt;filter、分页限制、字段选择、索引设计&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;面试时不要只说“我用了 ES”。更好的说法是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我在项目中用 Elasticsearch 解决全文检索问题。MySQL 更适合事务和结构化查询，但对分词搜索、相关性排序支持有限。所以把需要搜索的数据同步到 ES，通过倒排索引提升搜索效率。项目里还需要考虑 MySQL 和 ES 的数据一致性，比如通过消息队列异步同步，并配合定时任务补偿。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;学习能力问题&quot;&gt;学习能力问题&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;学习能力问题通常不会太难，但很考验真实感。&lt;/p&gt;
&lt;p&gt;常见问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最近在学什么技术？&lt;/li&gt;
&lt;li&gt;为什么选择 Java？&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;ul&gt;
&lt;li&gt;不要只说“我在学 Java”。&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;blockquote&gt;
&lt;p&gt;最近我在补 Redis 和 Elasticsearch。Redis 主要关注缓存穿透、击穿、雪崩以及数据结构的使用场景；Elasticsearch 主要学习倒排索引、DSL 查询和数据同步。因为我的项目里有搜索场景，所以我想把搜索链路和缓存链路都理解得更完整。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;复习优先级&quot;&gt;复习优先级&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果时间有限，可以按这个顺序准备：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Java 基础：集合、字符串、异常、反射。&lt;/li&gt;
&lt;li&gt;MySQL：索引、事务、MVCC、索引失效。&lt;/li&gt;
&lt;li&gt;Redis：数据结构、缓存问题、常用命令。&lt;/li&gt;
&lt;li&gt;Spring Boot：IoC、AOP、自动配置、REST API。&lt;/li&gt;
&lt;li&gt;JVM：内存结构、GC、OOM。&lt;/li&gt;
&lt;li&gt;并发：线程池、锁、volatile、死锁。&lt;/li&gt;
&lt;li&gt;Elasticsearch 项目：倒排索引、DSL、数据同步、搜索优化。&lt;/li&gt;
&lt;li&gt;Docker 和 Linux：能讲清楚部署和常用命令即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Java 校招面试不是只背八股。&lt;/p&gt;
&lt;p&gt;基础题要能答清楚概念，项目题要能讲清楚自己做了什么、为什么这样做、遇到了什么问题、怎么解决。&lt;/p&gt;
&lt;p&gt;这份清单的重点不是一次性背完，而是把每个问题都整理成三层：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;一句话结论&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;核心原理&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;项目或使用场景&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这样回答时会更稳，也更像真的理解过。&lt;/p&gt;</content:encoded><category>Java</category><category>后端</category><category>面试</category></item><item><title>爬虫与 JS 逆向面试题复盘</title><link>https://blog.veyliss.top/blog/crawler_reverse_interview_01/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/crawler_reverse_interview_01/</guid><pubDate>Tue, 16 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是一组爬虫和 JS 逆向相关的面试题。&lt;/p&gt;
&lt;p&gt;这类面试不会只问“会不会用 &lt;code dir=&quot;auto&quot;&gt;requests&lt;/code&gt;”，更常见的是从一个具体业务场景开始追问：怎么登录、怎么抓动态接口、怎么处理反爬、百万级数据怎么调度、数据怎么进入后续处理链路。&lt;/p&gt;
&lt;p&gt;这篇文章按面试题复盘的方式整理，重点是把回答讲得更清楚、更工程化。&lt;/p&gt;
&lt;p&gt;同时要先明确一点：爬虫和逆向要遵守法律、站点协议和数据合规要求。面试中可以讲技术思路，但不应该表达绕过风控、攻击站点或采集敏感数据的意图。更稳的说法是：&lt;strong&gt;在授权范围内做数据采集和接口分析。&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;自动登录怎么选&quot;&gt;自动登录怎么选&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;题目：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;登录有两种方式，一种是账号密码登录，并且需要输入动态 token；另一种是二维码登录。如果要自动登录，你会选择哪种方式，为什么？说说实现方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我的回答倾向是：优先选择账号密码加 token 的方式。&lt;/p&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;登录成功后可以拿到 token、cookie 或 session。&lt;/li&gt;
&lt;li&gt;二维码登录通常依赖人工扫码，不适合长期自动化任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我会优先选择账号密码加 token 的登录方式。因为它更容易通过请求和响应模拟，流程上可以先请求登录页或初始化接口，拿到登录所需的 token、cookie，再携带账号密码和动态 token 请求登录接口。登录成功后保存 cookie 或 access token，后续请求统一带上认证信息。二维码登录更适合人工确认，自动化成本更高，而且很多二维码登录会绑定设备、时效和扫码确认，不适合爬虫任务长期稳定运行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一个简化流程：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;请求登录页或初始化接口&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;获取 token / csrf / cookie&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;提交账号、密码、动态 token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;登录成功&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;保存 cookie 或 access token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;后续请求携带认证信息&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;需要注意：如果动态 token 是验证码、短信码、二次验证，不能假设可以无成本自动化。面试里可以强调“在授权账号和合规场景下处理登录态”。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;前端动态渲染的网站怎么爬&quot;&gt;前端动态渲染的网站怎么爬&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;题目：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这种前端返回数据的网站，如何爬取数据？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在很多网站是前端框架渲染，HTML 源码里没有完整数据。此时不要急着解析页面，而是先看网络请求。&lt;/p&gt;
&lt;p&gt;常规步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打开 Chrome DevTools。&lt;/li&gt;
&lt;li&gt;进入 Network 面板。&lt;/li&gt;
&lt;li&gt;过滤 XHR / Fetch 请求。&lt;/li&gt;
&lt;li&gt;找到真正返回 JSON 数据的接口。&lt;/li&gt;
&lt;li&gt;分析 URL、请求方法、参数、Headers、Cookie。&lt;/li&gt;
&lt;li&gt;用 Python 模拟请求。&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;fetch-api-data.py&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://example.com/api/list&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;headers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;User-Agent&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Mozilla/5.0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Referer&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://example.com/list&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;params &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果接口参数是动态生成的，就继续分析 JS。&lt;/p&gt;
&lt;p&gt;如果页面确实没有接口，或者数据必须通过浏览器运行后才出现，可以考虑 Selenium 或 Playwright。但大规模采集时，优先分析接口，因为浏览器自动化成本更高。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;百万级数据怎么爬&quot;&gt;百万级数据怎么爬&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;题目：&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;/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;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;任务调度 -&gt; 爬虫采集 -&gt; Kafka -&gt; Flink 清洗 -&gt; 数据存储&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果只是普通项目，可以存 MySQL 或 CSV；如果是百万、千万规模，就要考虑分批写入、去重、失败重试、数据清洗和存储扩展。&lt;/p&gt;
&lt;p&gt;可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;面对百万级数据，我不会用一个单机脚本顺序爬。一般会先把任务拆成分页任务、关键词任务或 ID 区间任务，放到任务队列里，由多个爬虫节点并发消费。采集时会限制请求频率，设置超时重试和代理池，避免单点 IP 或账号压力过大。采集到的数据先进入 Kafka，后续由 Flink 做实时清洗，再写入 MySQL、ES 或数据仓库。对于失败任务会记录状态，后续补偿重试。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;平时用什么工具分析网站接口&quot;&gt;平时用什么工具分析网站接口&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;常用 Chrome DevTools 的 Network 面板。&lt;/p&gt;
&lt;p&gt;主要看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XHR / Fetch 请求。&lt;/li&gt;
&lt;li&gt;请求 URL。&lt;/li&gt;
&lt;li&gt;请求方法。&lt;/li&gt;
&lt;li&gt;Query 参数和 Request Payload。&lt;/li&gt;
&lt;li&gt;Headers。&lt;/li&gt;
&lt;li&gt;Cookie。&lt;/li&gt;
&lt;li&gt;Response。&lt;/li&gt;
&lt;li&gt;Initiator 调用来源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果参数是动态生成的，会继续去 Sources 面板断点调试，或在 JS 文件中搜索参数名。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;如何判断网站是否有反爬&quot;&gt;如何判断网站是否有反爬&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;可以从几个现象判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;请求参数里存在动态加密参数。&lt;/li&gt;
&lt;li&gt;接口依赖 token、cookie、签名或时间戳。&lt;/li&gt;
&lt;li&gt;请求频率过高会被封 IP。&lt;/li&gt;
&lt;li&gt;返回内容出现验证码、空数据或风控页面。&lt;/li&gt;
&lt;li&gt;同一个接口在浏览器能访问，程序请求失败。&lt;/li&gt;
&lt;li&gt;Headers 缺失时返回异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我会先比较浏览器正常请求和程序模拟请求的差异。如果同样的 URL 在浏览器里返回正常，但程序里返回空数据、验证码、403 或风控响应，就说明可能存在反爬。再继续分析是否有动态参数、token 校验、cookie 校验、频率限制或行为检测。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;xpath-和-css-selector-的区别&quot;&gt;XPath 和 CSS Selector 的区别&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;XPath 和 CSS Selector 都能定位 HTML 节点。&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;对比项&lt;/th&gt;&lt;th&gt;XPath&lt;/th&gt;&lt;th&gt;CSS Selector&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;语法&lt;/td&gt;&lt;td&gt;类似路径表达式&lt;/td&gt;&lt;td&gt;类似 CSS 选择器&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&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;/td&gt;&lt;td&gt;复杂表达式可读性一般&lt;/td&gt;&lt;td&gt;简单场景更清晰&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&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;面试可以说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;简单页面我会用 CSS Selector，因为语法简洁；复杂定位，比如按文本、层级、相邻节点查找时，我更倾向 XPath。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;做过-js-逆向吗&quot;&gt;做过 JS 逆向吗&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;可以按流程回答：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 Network 抓包找到目标接口。&lt;/li&gt;
&lt;li&gt;确认哪个参数是动态生成的。&lt;/li&gt;
&lt;li&gt;全局搜索参数名。&lt;/li&gt;
&lt;li&gt;在 Sources 面板下断点。&lt;/li&gt;
&lt;li&gt;观察 Call Stack 调用链。&lt;/li&gt;
&lt;li&gt;找到最终生成参数的函数。&lt;/li&gt;
&lt;li&gt;用 Python 或 Node 复现算法。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;更完整的回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我在项目中遇到过接口参数由 JS 加密生成的情况。处理时先通过 Network 找到接口和异常参数，然后在 JS 文件中搜索参数名。如果搜索不到，就从请求发起位置或 XHR 断点入手，在 Sources 里下断点，结合 Call Stack 分析调用链，找到参数生成函数。确定算法后，再用 Python 或 Node 复现，最后和浏览器生成结果对比，确保请求参数一致。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这类回答要强调“分析和复现授权接口参数”，不要说成攻击或绕过安全系统。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;js-混淆怎么分析&quot;&gt;JS 混淆怎么分析&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;JS 混淆后，变量名和函数名可能没有意义，所以不要期待完全看懂所有代码。&lt;/p&gt;
&lt;p&gt;常见思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不全量还原，只找关键链路。&lt;/li&gt;
&lt;li&gt;通过 XHR/fetch 断点定位请求发起位置。&lt;/li&gt;
&lt;li&gt;使用 Call Stack 看调用链。&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;blockquote&gt;
&lt;p&gt;遇到混淆 JS 时，我不会从头读完整文件，而是围绕目标接口定位关键参数。通过断点、调用栈、关键变量打印和函数输入输出分析，逐步缩小范围，最终定位生成参数的函数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;为什么不用-selenium&quot;&gt;为什么不用 Selenium&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Selenium 是浏览器自动化工具，适合复杂页面、需要真实浏览器环境的场景。&lt;/p&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;li&gt;大规模采集不划算。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以一般优先分析接口直接请求。只有接口很难复现、页面强依赖浏览器环境、或需要真实交互时，才考虑 Selenium 或 Playwright。&lt;/p&gt;
&lt;p&gt;面试回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Selenium 可以用，但我不会作为首选。因为大规模采集更关注吞吐和稳定性，直接请求接口效率更高。Selenium 更适合登录、复杂交互或无法绕开浏览器渲染的页面。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;如何保证爬虫长期稳定运行&quot;&gt;如何保证爬虫长期稳定运行&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;长期稳定运行靠的不是一个脚本，而是容错和监控。&lt;/p&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;li&gt;失败任务记录。&lt;/li&gt;
&lt;li&gt;账号状态检测。&lt;/li&gt;
&lt;li&gt;IP 或代理状态检测。&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;blockquote&gt;
&lt;p&gt;我会为爬虫设计超时重试、异常捕获、失败任务记录和任务监控机制。如果请求失败，会根据错误类型决定重试、切换账号、切换代理或标记任务失败。系统层面会有健康监测和失败上报，保证爬虫可以长期稳定运行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;爬虫规模是多少&quot;&gt;爬虫规模是多少&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果项目里支持 &lt;code dir=&quot;auto&quot;&gt;500万+ / 日&lt;/code&gt; 的采集规模，可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;系统支持 500 万以上日采集量。采集任务不是由单个脚本完成，而是通过任务调度系统统一拆分和分发，多节点并发执行。采集结果进入 Kafka，再由 Flink 进行实时清洗和处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;面试时不要只报数字，最好补上支撑数字的架构。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;系统架构是什么&quot;&gt;系统架构是什么&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;整体链路可以这样描述：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;调度系统&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;爬虫节点&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Kafka&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Flink&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;MySQL / Elasticsearch / 数据仓库&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;各模块职责：&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;调度系统&lt;/td&gt;&lt;td&gt;生成任务、分配任务、协调账号&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;爬虫节点&lt;/td&gt;&lt;td&gt;执行采集、解析数据、处理重试&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Kafka&lt;/td&gt;&lt;td&gt;解耦采集和处理，缓冲流量&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Flink&lt;/td&gt;&lt;td&gt;实时清洗、过滤、转换&lt;/td&gt;&lt;/tr&gt;&lt;tr&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;这类回答会比“我用 Scrapy 分布式”更有工程感。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;调度系统怎么工作&quot;&gt;调度系统怎么工作&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;调度系统主要负责任务生成和账号协调。&lt;/p&gt;
&lt;p&gt;你笔记中的规模是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1400+ 爬虫任务。&lt;/li&gt;
&lt;li&gt;400+ 账号 Cookie。&lt;/li&gt;
&lt;li&gt;任务信息存储在 Redis。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;调度系统会把采集目标拆成具体任务，任务状态存储在 Redis 中。爬虫节点从 Redis 获取任务，执行后回写任务状态。账号 Cookie 也由调度系统统一管理，分配任务时会根据账号状态选择可用账号，避免单个账号压力过大。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;为什么任务放-redis&quot;&gt;为什么任务放 Redis&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Redis 适合做任务队列和状态缓存。&lt;/p&gt;
&lt;p&gt;原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读写性能高。&lt;/li&gt;
&lt;li&gt;支持 List、Set、Hash、Sorted Set 等结构。&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;blockquote&gt;
&lt;p&gt;如果任务需要更强的可靠性、确认机制和重试语义，也可以引入消息队列；Redis 更适合轻量级任务调度和状态管理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;如何处理账号失效&quot;&gt;如何处理账号失效&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;账号失效的表现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;登录失败。&lt;/li&gt;
&lt;li&gt;Cookie 失效。&lt;/li&gt;
&lt;li&gt;返回 401、403。&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;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;li&gt;触发重新登录或人工处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;系统会根据响应状态和页面内容判断账号是否异常。一旦发现 Cookie 失效或登录状态异常，就标记账号状态，避免继续分配任务，同时把未完成任务重新放回队列，交给其他可用账号处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;python-爬虫常用库&quot;&gt;Python 爬虫常用库&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;常用库：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;requests&lt;/code&gt;：发送 HTTP 请求。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;httpx&lt;/code&gt;：支持同步和异步请求。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;scrapy&lt;/code&gt;：爬虫框架。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;lxml&lt;/code&gt;：解析 HTML，支持 XPath。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;beautifulsoup4&lt;/code&gt;：HTML 解析。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;selenium&lt;/code&gt;：浏览器自动化。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;playwright&lt;/code&gt;：现代浏览器自动化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;项目里如果主要使用 &lt;code dir=&quot;auto&quot;&gt;requests + XPath&lt;/code&gt;，可以这样说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;普通接口采集我主要使用 requests，请求接口后用 XPath 或 JSON 解析数据。如果是复杂任务调度和大规模采集，会考虑 Scrapy 或自研调度系统。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;如何处理请求超时&quot;&gt;如何处理请求超时&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;基本做法：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;request-timeout.py&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://example.com/api&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; requests.Timeout:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 记录超时并重试&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; requests.RequestException:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 记录其他请求异常&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&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;div&gt;&lt;h2 id=&quot;如何控制爬虫速度&quot;&gt;如何控制爬虫速度&lt;/h2&gt;&lt;/div&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;li&gt;对单账号限速。&lt;/li&gt;
&lt;li&gt;对异常响应动态降速。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;面试里可以说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;控制速度不只是 sleep，而是结合并发数、任务队列、账号维度和站点响应来动态调整，避免触发反爬，也保护目标站点和自身系统。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;为什么使用-docker&quot;&gt;为什么使用 Docker&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Docker 的价值：&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;li&gt;适合配合 CI/CD。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;爬虫系统里尤其适合把爬虫节点容器化。需要扩容时，可以快速启动多个容器实例。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;kafka-在系统中作用&quot;&gt;Kafka 在系统中作用&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Kafka 主要承担数据通道和缓冲层。&lt;/p&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;blockquote&gt;
&lt;p&gt;爬虫采集速度和后续清洗入库速度不一定一致，所以中间用 Kafka 解耦。爬虫只负责把原始数据写入 Kafka，Flink 再从 Kafka 消费并清洗处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;redis-在项目中用来做什么&quot;&gt;Redis 在项目中用来做什么&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Redis 在项目中可以承担：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务队列。&lt;/li&gt;
&lt;li&gt;任务状态缓存。&lt;/li&gt;
&lt;li&gt;账号 Cookie 管理。&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;blockquote&gt;
&lt;p&gt;Redis 主要用于调度层，保存任务队列、任务状态和账号 Cookie。因为它读写快，并且数据结构丰富，适合管理这种高频变化的临时状态。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;项目中最难的部分是什么&quot;&gt;项目中最难的部分是什么&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;可以回答 JS 加密参数逆向。&lt;/p&gt;
&lt;p&gt;更完整的说法：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;最难的是 JS 加密参数逆向。因为网站 JS 做了混淆，不能直接通过阅读代码看懂逻辑。我通过 Network 定位接口和动态参数，再用 Sources 下断点，结合调用栈分析参数生成流程，最后把关键算法用 Python 或 Node 复现出来。这个过程比较考验调试能力和耐心。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;如果网站增加新的反爬怎么办&quot;&gt;如果网站增加新的反爬怎么办&lt;/h2&gt;&lt;/div&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;判断新增机制：token、cookie、签名、频率、验证码、行为检测。&lt;/li&gt;
&lt;li&gt;如果是参数变化，重新调试 JS。&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;blockquote&gt;
&lt;p&gt;我会先分析新增反爬属于哪一类，再决定策略。如果是参数签名变化，就重新定位 JS 生成逻辑；如果是频率限制，就降低并发、调整账号和代理策略；如果是登录态或 Cookie 变化，就更新账号状态检测和重新登录流程。对于验证码或强风控场景，需要评估合规性，不能盲目绕过。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;面试回答模板&quot;&gt;面试回答模板&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果面试官让你整体介绍这个爬虫项目，可以这样组织：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这个项目主要做大规模数据采集。整体链路是爬虫采集、Kafka 缓冲、Flink 清洗、最终写入存储。爬虫侧通过 Chrome DevTools 分析接口，优先直接请求接口而不是 Selenium。调度系统负责管理 1400 多个任务和 400 多个账号 Cookie，任务状态存储在 Redis。系统支持超时重试、失败任务记录、账号失效检测和健康监控。项目中比较难的是 JS 加密参数逆向，我通过断点调试、调用栈分析和算法复现解决过接口动态参数问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;爬虫和逆向面试题，重点不是只会某个库，而是能把采集链路讲完整：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;接口分析 -&gt; 登录态处理 -&gt; 参数逆向 -&gt; 任务调度 -&gt; 并发控制 -&gt; 数据通道 -&gt; 清洗入库 -&gt; 监控补偿&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果能把这条链路讲清楚，再结合自己实际做过的规模、账号调度、Kafka/Flink、Redis 和 Docker，回答就会更像真实项目经验，而不是零散知识点。&lt;/p&gt;</content:encoded><category>Python</category><category>爬虫</category><category>面试</category></item><item><title>一道 gRPC 简易计算器面试题复盘</title><link>https://blog.veyliss.top/blog/grpc_calculator_interview_01/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/grpc_calculator_interview_01/</guid><pubDate>Fri, 11 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是我曾经做过的一道面试题中的一个小项目：用 &lt;strong&gt;Go + gRPC + gRPC-Web + Next.js&lt;/strong&gt; 做一个简易计算器。&lt;/p&gt;
&lt;p&gt;题目本身不复杂，只有加、减、乘、除四种运算。但它真正考察的不是“会不会写计算器”，而是能不能把前后端通信、接口契约、错误处理和跨端调用讲清楚。&lt;/p&gt;
&lt;p&gt;项目地址：&lt;a href=&quot;https://github.com/veyliss/GolangNextGrpcSimpleCalculator&quot;&gt;GolangNextGrpcSimpleCalculator&lt;/a&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;题目要做什么&quot;&gt;题目要做什么&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这道题可以理解为：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;前端输入两个数字和一个运算符，通过 gRPC-Web 调用 Go 后端，后端完成计算并返回结果。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最小功能闭环包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前端页面可以输入两个操作数。&lt;/li&gt;
&lt;li&gt;前端可以选择 &lt;code dir=&quot;auto&quot;&gt;+&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;-&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;*&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;/&lt;/code&gt; 四种运算符。&lt;/li&gt;
&lt;li&gt;前端通过 gRPC-Web 调用后端。&lt;/li&gt;
&lt;li&gt;后端使用 Go 实现 gRPC 服务。&lt;/li&gt;
&lt;li&gt;后端根据请求参数返回计算结果。&lt;/li&gt;
&lt;li&gt;除数为 &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt; 或未知运算符时，需要返回错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果只是做一个普通 HTTP 接口，这题可能很快就结束了。但这里要求使用 gRPC，就会多出一层“接口契约”的设计：前后端都要围绕 &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; 文件生成代码。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;项目结构&quot;&gt;项目结构&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这个项目大致可以拆成两部分：&lt;/p&gt;
&lt;starlight-file-tree data-pagefind-ignore=&quot;true&quot;&gt;&lt;ul&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;calculator-backend/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;calculator/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M1.082 16.876L1.082 14.014L22.918 14.014L22.918 16.876L1.082 16.876ZM1.082 9.986L1.082 7.071L13.272 7.071L13.272 9.986L1.082 9.986ZM1.082 3.096L1.082 0.181L22.918 0.181L22.918 3.096L1.082 3.096ZM1.082 23.819L1.082 20.904L17.300 20.904L17.300 23.819L1.082 23.819Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;calculator.proto&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M6.119 10.273L1.933 10.247Q1.855 10.247 1.881 10.195L1.881 10.195L2.141 9.883Q2.167 9.831 2.271 9.831L2.271 9.831L6.379 9.831Q6.457 9.831 6.431 9.883L6.431 9.883L6.223 10.195Q6.171 10.273 6.119 10.273L6.119 10.273ZM5.651 11.313L0.191 11.313Q0.113 11.313 0.139 11.261L0.139 11.261L0.399 10.949Q0.425 10.897 0.529 10.897L0.529 10.897L5.781 10.897Q5.859 10.897 5.833 10.949L5.833 10.949L5.755 11.235Q5.729 11.313 5.651 11.313L5.651 11.313ZM5.495 12.379L2.973 12.379Q2.895 12.379 2.947 12.301L2.947 12.301L3.103 12.015Q3.155 11.963 3.233 11.963L3.233 11.963L5.521 11.963Q5.599 11.963 5.599 12.041L5.599 12.041L5.573 12.301Q5.573 12.379 5.495 12.379L5.495 12.379ZM14.985 10.039L14.985 10.039Q14.569 10.143 13.893 10.325L13.893 10.325L13.009 10.559Q12.905 10.585 12.853 10.572Q12.801 10.559 12.697 10.429L12.697 10.429Q12.411 10.117 12.151 9.987L12.151 9.987Q11.111 9.493 10.071 10.169L10.071 10.169Q8.875 10.949 8.875 12.379L8.875 12.379Q8.901 13.055 9.330 13.575Q9.759 14.095 10.435 14.173L10.435 14.173Q11.605 14.329 12.411 13.419L12.411 13.419L12.723 13.003L10.487 13.003Q10.305 13.003 10.253 12.899Q10.201 12.795 10.279 12.639L10.279 12.639Q10.591 11.911 10.851 11.391L10.851 11.391Q10.955 11.209 11.137 11.209L11.137 11.209L15.349 11.209L15.349 11.521Q15.323 11.937 15.297 12.145L15.297 12.145Q15.089 13.419 14.335 14.407L14.335 14.407Q13.061 16.097 11.033 16.357L11.033 16.357Q9.265 16.591 7.939 15.603L7.939 15.603Q6.665 14.641 6.457 13.029L6.457 13.029Q6.249 11.209 7.445 9.649L7.445 9.649Q8.693 8.011 10.695 7.647L10.695 7.647Q12.437 7.335 13.737 8.219L13.737 8.219Q14.647 8.817 15.089 9.857L15.089 9.857Q15.193 9.987 14.985 10.039ZM19.119 16.409L18.807 16.435Q17.117 16.409 15.973 15.421L15.973 15.421Q14.933 14.511 14.725 13.185L14.725 13.185Q14.439 11.313 15.661 9.701L15.661 9.701Q16.285 8.869 17.065 8.414Q17.845 7.959 18.911 7.777L18.911 7.777Q20.835 7.439 22.226 8.375Q23.617 9.311 23.825 10.923L23.825 10.923Q24.111 13.211 22.499 14.849L22.499 14.849Q21.381 15.993 19.717 16.331L19.717 16.331Q19.509 16.357 19.119 16.409L19.119 16.409ZM21.537 11.755L21.537 11.755Q21.537 11.729 21.537 11.651L21.537 11.651Q21.537 11.469 21.511 11.391L21.511 11.391Q21.355 10.533 20.666 10.091Q19.977 9.649 19.145 9.857L19.145 9.857Q17.507 10.221 17.143 11.859L17.143 11.859Q16.987 12.535 17.286 13.146Q17.585 13.757 18.209 14.043L18.209 14.043Q19.145 14.459 20.081 13.965L20.081 13.965Q21.459 13.263 21.537 11.755Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;main.go&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M6.119 10.273L1.933 10.247Q1.855 10.247 1.881 10.195L1.881 10.195L2.141 9.883Q2.167 9.831 2.271 9.831L2.271 9.831L6.379 9.831Q6.457 9.831 6.431 9.883L6.431 9.883L6.223 10.195Q6.171 10.273 6.119 10.273L6.119 10.273ZM5.651 11.313L0.191 11.313Q0.113 11.313 0.139 11.261L0.139 11.261L0.399 10.949Q0.425 10.897 0.529 10.897L0.529 10.897L5.781 10.897Q5.859 10.897 5.833 10.949L5.833 10.949L5.755 11.235Q5.729 11.313 5.651 11.313L5.651 11.313ZM5.495 12.379L2.973 12.379Q2.895 12.379 2.947 12.301L2.947 12.301L3.103 12.015Q3.155 11.963 3.233 11.963L3.233 11.963L5.521 11.963Q5.599 11.963 5.599 12.041L5.599 12.041L5.573 12.301Q5.573 12.379 5.495 12.379L5.495 12.379ZM14.985 10.039L14.985 10.039Q14.569 10.143 13.893 10.325L13.893 10.325L13.009 10.559Q12.905 10.585 12.853 10.572Q12.801 10.559 12.697 10.429L12.697 10.429Q12.411 10.117 12.151 9.987L12.151 9.987Q11.111 9.493 10.071 10.169L10.071 10.169Q8.875 10.949 8.875 12.379L8.875 12.379Q8.901 13.055 9.330 13.575Q9.759 14.095 10.435 14.173L10.435 14.173Q11.605 14.329 12.411 13.419L12.411 13.419L12.723 13.003L10.487 13.003Q10.305 13.003 10.253 12.899Q10.201 12.795 10.279 12.639L10.279 12.639Q10.591 11.911 10.851 11.391L10.851 11.391Q10.955 11.209 11.137 11.209L11.137 11.209L15.349 11.209L15.349 11.521Q15.323 11.937 15.297 12.145L15.297 12.145Q15.089 13.419 14.335 14.407L14.335 14.407Q13.061 16.097 11.033 16.357L11.033 16.357Q9.265 16.591 7.939 15.603L7.939 15.603Q6.665 14.641 6.457 13.029L6.457 13.029Q6.249 11.209 7.445 9.649L7.445 9.649Q8.693 8.011 10.695 7.647L10.695 7.647Q12.437 7.335 13.737 8.219L13.737 8.219Q14.647 8.817 15.089 9.857L15.089 9.857Q15.193 9.987 14.985 10.039ZM19.119 16.409L18.807 16.435Q17.117 16.409 15.973 15.421L15.973 15.421Q14.933 14.511 14.725 13.185L14.725 13.185Q14.439 11.313 15.661 9.701L15.661 9.701Q16.285 8.869 17.065 8.414Q17.845 7.959 18.911 7.777L18.911 7.777Q20.835 7.439 22.226 8.375Q23.617 9.311 23.825 10.923L23.825 10.923Q24.111 13.211 22.499 14.849L22.499 14.849Q21.381 15.993 19.717 16.331L19.717 16.331Q19.509 16.357 19.119 16.409L19.119 16.409ZM21.537 11.755L21.537 11.755Q21.537 11.729 21.537 11.651L21.537 11.651Q21.537 11.469 21.511 11.391L21.511 11.391Q21.355 10.533 20.666 10.091Q19.977 9.649 19.145 9.857L19.145 9.857Q17.507 10.221 17.143 11.859L17.143 11.859Q16.987 12.535 17.286 13.146Q17.585 13.757 18.209 14.043L18.209 14.043Q19.145 14.459 20.081 13.965L20.081 13.965Q21.459 13.263 21.537 11.755Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;calculator_test.go&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M1.082 16.876L1.082 14.014L22.918 14.014L22.918 16.876L1.082 16.876ZM1.082 9.986L1.082 7.071L13.272 7.071L13.272 9.986L1.082 9.986ZM1.082 3.096L1.082 0.181L22.918 0.181L22.918 3.096L1.082 3.096ZM1.082 23.819L1.082 20.904L17.300 20.904L17.300 23.819L1.082 23.819Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;go.mod&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;calculator-frontend/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;app/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;generated/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;details&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;calculator/&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;…&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M18.955 15.993L18.993 15.993Q18.993 16.107 18.993 16.297L18.993 16.297Q19.221 18.615 19.221 19.774Q19.221 20.933 18.651 21.693Q18.081 22.453 17.207 22.529L17.207 22.529Q16.333 22.719 15.307 22.301L15.307 22.301Q14.547 21.921 13.179 21.009L13.179 21.009Q12.305 20.439 11.849 20.173L11.849 20.173Q10.671 21.123 9.949 21.579L9.949 21.579Q9.341 21.997 8.771 22.187L8.771 22.187Q7.707 22.643 6.795 22.472Q5.883 22.301 5.332 21.579Q4.781 20.857 4.762 19.679Q4.743 18.501 4.971 15.879L4.971 15.879Q3.527 15.423 2.805 14.929L2.805 14.929Q1.855 14.473 0.905 13.523L0.905 13.523Q0.145 12.725 0.183 11.794Q0.221 10.863 1.057 10.065L1.057 10.065Q1.665 9.343 2.691 8.849L2.691 8.849Q3.299 8.507 4.591 8.051L4.591 8.051Q4.667 8.051 4.838 7.994Q5.009 7.937 5.085 7.937L5.085 7.937L4.743 5.315Q4.705 4.479 4.971 3.073L4.971 3.073Q5.237 2.085 6.054 1.667Q6.871 1.249 7.935 1.515L7.935 1.515Q8.885 1.743 9.835 2.351L9.835 2.351Q10.519 2.769 11.393 3.529L11.393 3.529Q11.545 3.605 11.735 3.871L11.735 3.871L11.849 4.023Q13.749 2.579 14.813 1.971L14.813 1.971Q16.029 1.173 17.207 1.515L17.207 1.515Q18.081 1.705 18.594 2.446Q19.107 3.187 19.221 4.365L19.221 4.365L19.221 6.265Q19.221 6.645 19.031 7.405L19.031 7.405Q18.917 7.899 18.841 8.165L18.841 8.165Q19.905 8.507 20.874 8.982Q21.843 9.457 22.261 9.761L22.261 9.761Q22.983 10.255 23.401 10.882Q23.819 11.509 23.819 12.193Q23.819 12.877 23.401 13.504Q22.983 14.131 22.261 14.625L22.261 14.625Q21.805 14.929 20.855 15.423L20.855 15.423Q20.171 15.537 18.955 15.993L18.955 15.993ZM10.861 15.841L12.077 15.879Q12.267 15.879 12.685 15.841L12.685 15.841Q13.255 15.765 13.521 15.765L13.521 15.765Q14.015 15.765 14.357 15.271L14.357 15.271Q15.535 13.371 15.991 12.307L15.991 12.307Q16.105 12.155 16.105 11.927Q16.105 11.699 15.991 11.623L15.991 11.623Q15.421 10.521 14.205 8.659L14.205 8.659Q14.053 8.279 13.635 8.279L13.635 8.279Q11.621 8.279 10.557 8.165L10.557 8.165Q9.607 8.165 9.113 9.001L9.113 9.001Q8.657 9.723 8.391 10.179L8.391 10.179L7.859 11.167Q7.517 11.661 7.460 11.870Q7.403 12.079 7.460 12.288Q7.517 12.497 7.859 13.029L7.859 13.029L8.391 13.979L8.847 14.739Q9.227 15.347 9.379 15.499L9.379 15.499Q9.607 15.765 9.911 15.803L9.911 15.803Q10.139 15.841 10.861 15.841L10.861 15.841ZM5.199 14.929L5.199 14.929Q5.921 13.029 6.263 12.193L6.263 12.193L6.263 11.737L5.199 9.001Q3.071 9.647 1.893 10.521L1.893 10.521Q1.057 11.167 1.057 11.908Q1.057 12.649 1.893 13.257L1.893 13.257Q2.501 13.903 3.451 14.321L3.451 14.321Q4.021 14.587 5.199 14.929ZM18.613 8.887L18.613 8.887Q18.157 9.951 17.435 11.851L17.435 11.851L17.397 11.965Q17.359 12.079 17.435 12.079L17.435 12.079Q18.157 13.979 18.613 15.157L18.613 15.157Q19.069 14.929 20.019 14.435L20.019 14.435Q21.273 13.865 21.805 13.523L21.805 13.523Q22.793 12.877 22.793 12.079Q22.793 11.281 21.843 10.673L21.843 10.673Q21.197 10.103 19.753 9.419L19.753 9.419Q18.955 9.077 18.613 8.887ZM5.883 7.481L5.921 7.671Q7.821 7.481 8.885 7.215L8.885 7.215Q8.961 7.215 9.037 7.139L9.037 7.139L9.113 7.101Q9.797 6.151 11.013 4.707L11.013 4.707Q10.063 3.909 9.569 3.529L9.569 3.529Q8.733 2.883 7.935 2.579L7.935 2.579Q6.947 2.237 6.339 2.598Q5.731 2.959 5.541 4.023L5.541 4.023Q5.465 4.745 5.579 5.695L5.579 5.695Q5.655 6.265 5.883 7.481L5.883 7.481ZM17.777 7.709L17.777 7.671Q17.777 7.633 17.834 7.462Q17.891 7.291 17.891 7.215L17.891 7.215Q18.157 6.037 18.157 5.391L18.157 5.391Q18.233 4.327 17.891 3.529L17.891 3.529Q17.739 2.921 17.321 2.655Q16.903 2.389 16.371 2.465L16.371 2.465Q15.421 2.617 14.509 3.225L14.509 3.225Q13.939 3.567 12.951 4.479L12.951 4.479L12.685 4.707Q13.293 5.543 14.471 6.987L14.471 6.987L14.813 7.329L17.777 7.709ZM5.921 16.221L5.921 16.221Q5.883 16.449 5.807 16.867L5.807 16.867Q5.579 17.931 5.541 18.425L5.541 18.425Q5.465 19.299 5.693 20.059L5.693 20.059Q5.845 20.933 6.434 21.275Q7.023 21.617 7.935 21.351L7.935 21.351Q8.695 21.123 9.493 20.591L9.493 20.591Q9.949 20.249 10.823 19.489L10.823 19.489L11.127 19.223Q10.443 18.273 9.227 16.829L9.227 16.829Q8.999 16.601 8.885 16.601L8.885 16.601Q7.517 16.601 5.921 16.221ZM12.571 19.223L12.571 19.223Q13.065 19.869 14.015 20.553L14.015 20.553Q14.889 21.199 15.649 21.465L15.649 21.465Q17.549 22.035 17.891 20.173L17.891 20.173Q18.043 19.337 17.967 18.349L17.967 18.349Q17.929 17.741 17.701 16.525L17.701 16.525L17.663 16.373L16.903 16.449Q15.307 16.563 14.585 16.829L14.585 16.829Q14.243 17.019 13.939 17.361L13.939 17.361Q13.749 17.589 13.445 18.045L13.445 18.045Q13.179 18.425 13.046 18.634Q12.913 18.843 12.571 19.223ZM10.443 7.101L13.293 7.101Q13.027 6.797 12.552 6.265Q12.077 5.733 11.849 5.429L11.849 5.429L11.659 5.695Q10.937 6.607 10.443 7.101L10.443 7.101ZM12.837 17.361L13.293 16.829L10.557 16.829Q10.785 17.133 11.260 17.665Q11.735 18.197 11.963 18.501L11.963 18.501Q12.229 18.083 12.837 17.361L12.837 17.361ZM8.391 15.651L8.391 15.651Q8.239 15.385 7.935 14.853L7.935 14.853Q7.289 13.789 6.985 13.143L6.985 13.143L6.263 15.157Q6.909 15.423 8.391 15.651ZM16.713 13.143L16.713 13.143L15.307 15.651L17.435 15.271Q17.283 14.511 16.713 13.143ZM6.985 10.787L6.985 10.787L8.391 8.279L6.263 8.621L6.415 9.115Q6.681 10.217 6.985 10.787ZM15.307 8.279L15.307 8.279Q15.535 8.773 16.010 9.476Q16.485 10.179 16.713 10.673L16.713 10.673Q17.207 9.229 17.435 8.659L17.435 8.659Q16.523 8.621 15.307 8.279Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;page.tsx&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;calculator/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M1.082 16.876L1.082 14.014L22.918 14.014L22.918 16.876L1.082 16.876ZM1.082 9.986L1.082 7.071L13.272 7.071L13.272 9.986L1.082 9.986ZM1.082 3.096L1.082 0.181L22.918 0.181L22.918 3.096L1.082 3.096ZM1.082 23.819L1.082 20.904L17.300 20.904L17.300 23.819L1.082 23.819Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;calculator.proto&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M0.734 13.269L0.562 10.732Q1.938 10.732 2.497 10.087L2.497 10.087Q2.884 9.614 2.884 8.711L2.884 8.711Q2.884 8.324 2.798 7.571Q2.712 6.819 2.712 6.410Q2.712 6.002 2.669 5.185L2.669 5.185Q2.583 4.454 2.583 4.153L2.583 4.153Q2.583 2.089 3.787 1.099Q4.991 0.111 7.184 0.111L7.184 0.111L8.259 0.111L8.259 2.648L7.700 2.648Q6.754 2.648 6.345 3.185Q5.937 3.723 5.937 4.798L5.937 4.798Q5.937 5.056 6.023 5.572L6.023 5.572Q6.109 6.217 6.109 6.561L6.109 6.561Q6.109 6.819 6.152 7.378L6.152 7.378Q6.238 8.152 6.238 8.582L6.238 8.582Q6.238 10.216 5.550 11.033L5.550 11.033Q4.948 11.764 3.658 12.065L3.658 12.065Q4.948 12.409 5.550 13.097L5.550 13.097Q6.238 13.957 6.238 15.548L6.238 15.548Q6.238 16.021 6.152 16.795L6.152 16.795Q6.066 17.354 6.088 17.612Q6.109 17.870 6.023 18.515L6.023 18.515Q5.937 18.988 5.937 19.203L5.937 19.203Q5.937 20.278 6.345 20.815Q6.754 21.353 7.700 21.353L7.700 21.353L8.259 21.353L8.259 23.890L7.184 23.890Q2.712 23.890 2.712 19.848L2.712 19.848Q2.712 18.386 2.862 17.590Q3.013 16.795 3.013 15.290L3.013 15.290Q3.013 13.269 0.734 13.269L0.734 13.269ZM23.438 10.732L23.438 13.011Q21.159 13.011 21.159 15.032L21.159 15.032Q21.159 15.419 21.224 16.171Q21.288 16.924 21.288 17.311L21.288 17.311Q21.417 18.128 21.417 19.590L21.417 19.590Q21.417 23.632 16.859 23.632L16.859 23.632L15.784 23.632L15.784 21.353L16.300 21.353Q17.246 21.353 17.654 20.815Q18.063 20.278 18.063 19.203Q18.063 18.128 17.934 17.569L17.934 17.569Q17.934 17.225 17.848 16.558Q17.762 15.892 17.762 15.548L17.762 15.548Q17.762 13.957 18.450 13.097L18.450 13.097Q19.052 12.409 20.342 12.065L20.342 12.065Q19.052 11.764 18.450 11.033L18.450 11.033Q17.762 10.216 17.762 8.582L17.762 8.582Q17.762 8.152 17.848 7.378L17.848 7.378Q17.934 6.819 17.934 6.561L17.934 6.561Q18.063 5.873 18.063 4.841Q18.063 3.809 17.633 3.293Q17.203 2.777 16.300 2.648L16.300 2.648L15.784 2.648L15.784 0.111L16.859 0.111Q19.009 0.111 20.213 1.099Q21.417 2.089 21.417 4.153L21.417 4.153Q21.417 4.540 21.352 5.292Q21.288 6.045 21.288 6.432L21.288 6.432Q21.159 7.249 21.116 8.711L21.116 8.711Q21.159 9.614 21.503 10.087L21.503 10.087Q22.062 10.732 23.438 10.732L23.438 10.732Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;package.json&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/starlight-file-tree&gt;
&lt;p&gt;后端负责定义和实现计算服务，前端负责生成 gRPC-Web 客户端代码并发起调用。&lt;/p&gt;
&lt;p&gt;这里最核心的文件有三个：&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;&lt;code dir=&quot;auto&quot;&gt;calculator.proto&lt;/code&gt;&lt;/td&gt;&lt;td&gt;定义服务、请求结构、响应结构&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;main.go&lt;/code&gt;&lt;/td&gt;&lt;td&gt;实现 Go gRPC 服务，并包装成 gRPC-Web HTTP 服务&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;page.tsx&lt;/code&gt;&lt;/td&gt;&lt;td&gt;在 Next.js 页面中创建请求并调用后端&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;先定义接口契约&quot;&gt;先定义接口契约&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;gRPC 的入口通常不是先写控制器，而是先写 &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;calculator.proto&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;syntax&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;proto3&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;calculator&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;option&lt;/span&gt;&lt;span&gt; go_package &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;github.com/2760439882/calculator-backend/calculator;calculator&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;service&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Calculator&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;rpc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Calculate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CalculationRequest&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;returns&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;CalculationResponse&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CalculationRequest&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operand1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operand2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// &quot;+&quot;, &quot;-&quot;, &quot;*&quot;, &quot;/&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CalculationResponse&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这份协议里有三个关键信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Calculator&lt;/code&gt; 是服务名。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Calculate&lt;/code&gt; 是远程调用方法。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;CalculationRequest&lt;/code&gt; 和 &lt;code dir=&quot;auto&quot;&gt;CalculationResponse&lt;/code&gt; 分别是请求和响应结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是 gRPC 和普通 REST 接口很不一样的地方。REST 接口经常先约定 URL 和 JSON 字段，而 gRPC 会先约定服务方法和强类型消息结构。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;后端实现计算服务&quot;&gt;后端实现计算服务&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;后端使用 Go 实现 &lt;code dir=&quot;auto&quot;&gt;Calculator&lt;/code&gt; 服务。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;main.go&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; server &lt;/span&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pb.UnimplementedCalculatorServer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;server) &lt;/span&gt;&lt;span&gt;Calculate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context.Context,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;pb.CalculationRequest,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;pb.CalculationResponse, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;float64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operator&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operand1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operand2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operand1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operand2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operand1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operand2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operand2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Errorf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;division by zero&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operand1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Operand2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Errorf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;unknown operator&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;pb.CalculationResponse{&lt;/span&gt;&lt;span&gt;Result&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这段实现很适合面试时讲，因为它虽然简单，但覆盖了服务端接口实现的几个基本点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方法签名来自 &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; 生成代码。&lt;/li&gt;
&lt;li&gt;请求参数来自 &lt;code dir=&quot;auto&quot;&gt;CalculationRequest&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;返回值必须符合 &lt;code dir=&quot;auto&quot;&gt;CalculationResponse&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;错误可以通过 &lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt; 返回给调用方。&lt;/li&gt;
&lt;li&gt;除法需要额外处理除数为 &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt; 的情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;真正的业务逻辑只有一个 &lt;code dir=&quot;auto&quot;&gt;switch&lt;/code&gt;，但重点是：这个 &lt;code dir=&quot;auto&quot;&gt;switch&lt;/code&gt; 被放在了 gRPC 服务方法里，前端不会直接知道后端怎么计算，只知道自己要调用 &lt;code dir=&quot;auto&quot;&gt;Calculate&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;为什么要用-grpc-web&quot;&gt;为什么要用 gRPC-Web&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;浏览器不能像 Go、Java、Node 服务端那样直接发起原生 gRPC 请求。原生 gRPC 基于 HTTP/2，而浏览器端直接使用 gRPC 会受到限制。&lt;/p&gt;
&lt;p&gt;所以前端调用 gRPC 服务时，通常需要一层 gRPC-Web。&lt;/p&gt;
&lt;p&gt;这个项目里，Go 后端把原始 gRPC 服务包装成了 gRPC-Web 服务：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;main.go&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grpcServer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grpc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NewServer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RegisterCalculatorServer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;grpcServer&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;server{})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;wrappedGrpc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grpcweb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WrapServer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;grpcServer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;httpServer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; http.Server{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Addr&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;:8080&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Handler&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;cors&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;(cors.Options{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;AllowedOrigins&lt;/span&gt;&lt;span&gt;: []&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;http://localhost:3000&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;AllowedMethods&lt;/span&gt;&lt;span&gt;: []&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;OPTIONS&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;AllowedHeaders&lt;/span&gt;&lt;span&gt;: []&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Content-Type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;X-Grpc-Web&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;X-User-Agent&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;grpc-timeout&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;AllowCredentials&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}).&lt;/span&gt;&lt;span&gt;Handler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;http&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;HandlerFunc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;w&lt;/span&gt;&lt;span&gt; http.ResponseWriter, &lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;http.Request) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;wrappedGrpc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IsGrpcWebRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;wrappedGrpc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IsAcceptableGrpcCorsRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;wrappedGrpc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IsGrpcWebSocketRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;wrappedGrpc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ServeHTTP&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;w&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;http&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NotFound&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;w&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这里有两个面试时值得说清楚的点。&lt;/p&gt;
&lt;p&gt;第一，后端并不是直接写一个普通 HTTP JSON 接口，而是先创建 &lt;code dir=&quot;auto&quot;&gt;grpc.NewServer()&lt;/code&gt;，再通过 &lt;code dir=&quot;auto&quot;&gt;grpcweb.WrapServer()&lt;/code&gt; 包装。&lt;/p&gt;
&lt;p&gt;第二，因为前端运行在 &lt;code dir=&quot;auto&quot;&gt;http://localhost:3000&lt;/code&gt;，后端运行在 &lt;code dir=&quot;auto&quot;&gt;http://localhost:8080&lt;/code&gt;，所以需要配置 CORS。否则浏览器会先把请求挡掉，根本到不了 gRPC-Web 服务。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;前端如何调用&quot;&gt;前端如何调用&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;前端是 Next.js 页面。因为要在浏览器里响应用户输入和发起请求，所以页面文件使用了 &lt;code dir=&quot;auto&quot;&gt;&apos;use client&apos;&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;page.tsx&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;use client&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { CalculatorClient } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./generated/calculator/calculator_grpc_web_pb&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { CalculateRequest } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./generated/calculator/calculator_pb&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CalculatorClient&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;http://localhost:8080&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Home&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;operand1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setOperand1&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;operand2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setOperand2&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;operator&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setOperator&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setResult&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setError&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setLoading&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;handleCalculate&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;setResult&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;setError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CalculateRequest&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setOperand1&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;parseFloat&lt;/span&gt;&lt;span&gt;(operand1))&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setOperand2&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;parseFloat&lt;/span&gt;&lt;span&gt;(operand2))&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setOperator&lt;/span&gt;&lt;span&gt;(operator)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;calculate&lt;/span&gt;&lt;span&gt;(req&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;if &lt;/span&gt;&lt;span&gt;(err)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;setError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;请求失败: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; + &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;setResult&lt;/span&gt;&lt;span&gt;(response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getResult&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这里的关键不是 &lt;code dir=&quot;auto&quot;&gt;useState&lt;/code&gt;，而是这三步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用生成的 &lt;code dir=&quot;auto&quot;&gt;CalculatorClient&lt;/code&gt; 创建客户端。&lt;/li&gt;
&lt;li&gt;使用生成的 &lt;code dir=&quot;auto&quot;&gt;CalculateRequest&lt;/code&gt; 创建请求对象。&lt;/li&gt;
&lt;li&gt;调用 &lt;code dir=&quot;auto&quot;&gt;client.calculate()&lt;/code&gt;，在回调里处理结果或错误。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这说明前端并没有手写请求路径、请求体字段和响应解析逻辑。它依赖 &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; 生成的代码来保证调用结构一致。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;这道题真正考什么&quot;&gt;这道题真正考什么&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果把这道题当成面试题看，它主要考察下面几类能力。&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;gRPC 基础&lt;/td&gt;&lt;td&gt;是否知道 &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt;、service、message、生成代码&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;前后端通信&lt;/td&gt;&lt;td&gt;是否知道浏览器不能直接使用原生 gRPC，需要 gRPC-Web&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Go 服务端&lt;/td&gt;&lt;td&gt;是否能注册服务、实现接口、启动服务&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;错误处理&lt;/td&gt;&lt;td&gt;是否处理除数为 &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt;、未知运算符、请求失败&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;跨域问题&lt;/td&gt;&lt;td&gt;是否知道前后端端口不同会触发 CORS&lt;/td&gt;&lt;/tr&gt;&lt;tr&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;所以面试时不要只说“我实现了一个计算器”。更好的说法是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我用 &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; 定义了计算服务和消息结构，再用 Go 实现 gRPC 服务。因为前端运行在浏览器里，不能直接调用原生 gRPC，所以后端用 gRPC-Web 包装了一层 HTTP 服务，并配置 CORS。前端通过生成的 gRPC-Web Client 创建请求对象，调用后端的 &lt;code dir=&quot;auto&quot;&gt;Calculate&lt;/code&gt; 方法，最后处理成功结果和错误信息。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这段回答会比“我写了加减乘除”更能说明你理解了题目。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;可以继续优化的地方&quot;&gt;可以继续优化的地方&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这个项目作为面试题已经能跑通主流程，但如果继续完善，可以从下面几个方向补强。&lt;/p&gt;
&lt;p&gt;第一，输入校验可以提前放在前端。&lt;/p&gt;
&lt;p&gt;现在前端直接对输入值 &lt;code dir=&quot;auto&quot;&gt;parseFloat()&lt;/code&gt;。如果用户没有输入数字，可能得到 &lt;code dir=&quot;auto&quot;&gt;NaN&lt;/code&gt;。可以在发请求前判断两个操作数是否合法。&lt;/p&gt;
&lt;p&gt;第二，后端错误可以使用 gRPC status。&lt;/p&gt;
&lt;p&gt;当前代码使用 &lt;code dir=&quot;auto&quot;&gt;fmt.Errorf()&lt;/code&gt; 返回错误。实际项目里可以使用 &lt;code dir=&quot;auto&quot;&gt;status.Error()&lt;/code&gt; 搭配 &lt;code dir=&quot;auto&quot;&gt;codes.InvalidArgument&lt;/code&gt;，这样调用方可以更准确地区分错误类型。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;codes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;InvalidArgument&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;division by zero&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;第三，运算符可以使用枚举。&lt;/p&gt;
&lt;p&gt;当前 &lt;code dir=&quot;auto&quot;&gt;operator&lt;/code&gt; 是字符串，优点是直观，缺点是容易传入非法值。如果要更严谨，可以在 &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; 中定义 enum。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;enum&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Operator&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;OPERATOR_UNSPECIFIED&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;ADD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;SUBTRACT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;MULTIPLY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;DIVIDE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;第四，配置可以抽出来。&lt;/p&gt;
&lt;p&gt;前端里的 &lt;code dir=&quot;auto&quot;&gt;http://localhost:8080&lt;/code&gt; 和后端 CORS 里的 &lt;code dir=&quot;auto&quot;&gt;http://localhost:3000&lt;/code&gt; 都是开发环境地址。后续如果部署，最好放进环境变量里。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;面试时怎么讲&quot;&gt;面试时怎么讲&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这道题可以按四步讲：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先说题目目标：前端输入表达式，后端通过 gRPC 完成计算。&lt;/li&gt;
&lt;li&gt;再说接口契约：用 &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; 定义 &lt;code dir=&quot;auto&quot;&gt;Calculate&lt;/code&gt; 方法、请求和响应。&lt;/li&gt;
&lt;li&gt;接着说后端实现：Go 实现服务，处理四种运算和异常情况。&lt;/li&gt;
&lt;li&gt;最后说浏览器调用：使用 gRPC-Web 生成客户端，前端发起请求并处理结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果面试官继续追问，可以展开这几个点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gRPC 和 REST 的区别是什么？&lt;/li&gt;
&lt;li&gt;为什么浏览器需要 gRPC-Web？&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; 文件改了以后要做什么？&lt;/li&gt;
&lt;li&gt;如果除数为 &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt;，应该怎么返回错误？&lt;/li&gt;
&lt;li&gt;如果以后要支持更多运算，应该怎么扩展？&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这个项目的价值不在于计算器本身，而在于它用一个很小的功能，把 gRPC 项目里最关键的链路串起来了：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;proto 定义 -&gt; 生成代码 -&gt; Go 实现服务 -&gt; gRPC-Web 包装 -&gt; 前端调用 -&gt; 展示结果&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;对于面试题来说，这种项目很合适。它足够小，能在有限时间内完成；同时又能覆盖接口设计、后端实现、前端调用、跨域和错误处理这些真实工程里会遇到的问题。&lt;/p&gt;</content:encoded><category>Go</category><category>gRPC</category><category>后端</category><category>面试</category></item><item><title>React 父子组件通信面试题复盘</title><link>https://blog.veyliss.top/blog/react_parent_child_communication_interview_01/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/react_parent_child_communication_interview_01/</guid><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是我曾经遇到过的一道 React 面试题，问题很常见：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;React 父子组件之间如何通信？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个问题看起来很基础，但其实很适合继续追问。因为它背后不只是 &lt;code dir=&quot;auto&quot;&gt;props&lt;/code&gt; 怎么传，而是 React 的数据流、状态放在哪里、组件边界怎么设计、什么时候用 Context、什么时候才需要状态管理库。&lt;/p&gt;
&lt;p&gt;如果只是回答“父传子用 props，子传父用回调函数”，当然没错，但面试里还不够。更好的回答应该能顺着这个问题，把 React 的单向数据流讲清楚。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;最直接的回答&quot;&gt;最直接的回答&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;React 默认是单向数据流。&lt;/p&gt;
&lt;p&gt;父组件可以通过 &lt;code dir=&quot;auto&quot;&gt;props&lt;/code&gt; 把数据传给子组件；子组件不能直接修改父组件的数据，如果子组件需要影响父组件，就由父组件传一个函数给子组件，子组件调用这个函数，把变化通知给父组件。&lt;/p&gt;
&lt;p&gt;可以先用一句话概括：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;父传子用 &lt;code dir=&quot;auto&quot;&gt;props&lt;/code&gt;，子传父用回调函数；如果兄弟组件之间要通信，就把状态提升到它们共同的父组件。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是这道题的核心答案。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;父组件向子组件传值&quot;&gt;父组件向子组件传值&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;父传子最简单，就是把数据作为 &lt;code dir=&quot;auto&quot;&gt;props&lt;/code&gt; 传给子组件。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Parent.tsx&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; User &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Parent&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;UserCard&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; UserCardProps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserCard&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;UserCardProps&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;section&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;ID: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;section&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这里的数据方向很清楚：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Parent state/data -&gt; props -&gt; Child render&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;子组件只负责接收和展示，不负责决定这个数据从哪里来。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;子组件通知父组件&quot;&gt;子组件通知父组件&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;子组件不能直接修改父组件内部的 &lt;code dir=&quot;auto&quot;&gt;state&lt;/code&gt;。如果子组件需要触发父组件更新，就由父组件把更新函数传下去。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Counter.tsx&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Parent&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;handleAdd&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; + &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;当前数量：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CounterButton&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onAdd&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;handleAdd&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; CounterButtonProps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;onAdd&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CounterButton&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;onAdd&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;CounterButtonProps&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;onAdd&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;加一&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这个过程可以理解为：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Child click -&gt; call props function -&gt; Parent setState -&gt; Child receives new props&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;也就是说，子组件并没有“改父组件”，它只是触发了父组件提供的回调。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;子组件把数据传给父组件&quot;&gt;子组件把数据传给父组件&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;有时候子组件不只是触发动作，还需要把自己的数据交给父组件。&lt;/p&gt;
&lt;p&gt;比如搜索框输入内容，父组件需要拿到关键词：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;SearchBox.tsx&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Parent&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;keyword&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setKeyword&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SearchBox&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onSearch&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;setKeyword&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;当前搜索词：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;keyword&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; SearchBoxProps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;onSearch&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;keyword&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SearchBox&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;onSearch&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;SearchBoxProps&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;handleSubmit&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;onSearch&lt;/span&gt;&lt;span&gt;(value)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;handleSubmit&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;搜索&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这里的 &lt;code dir=&quot;auto&quot;&gt;onSearch(value)&lt;/code&gt; 就是典型的子传父。&lt;/p&gt;
&lt;p&gt;面试时可以强调：&lt;strong&gt;React 里子传父不是反向修改数据，而是调用父组件传入的回调。&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;状态提升&quot;&gt;状态提升&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果两个兄弟组件需要共享数据，通常不是让它们互相调用，而是把状态提升到共同父组件。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;StateLifting.tsx&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Parent&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;selectedId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setSelectedId&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ProductList&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onSelect&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;setSelectedId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ProductDetail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;selectedId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; ProductListProps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;onSelect&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProductList&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;onSelect&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;ProductListProps&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;onSelect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;商品 1&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;onSelect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;商品 2&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; ProductDetailProps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProductDetail&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;ProductDetailProps&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (productId &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;请选择一个商品&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;当前商品 ID：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这就是 React 组件设计里很重要的一点：&lt;strong&gt;状态应该放在需要它的最小公共父组件中。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果状态只属于一个组件，就放在这个组件内部；如果多个子组件都要用，就提升到公共父组件；如果很多层都要用，再考虑 Context 或状态管理库。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;受控组件也是父子通信&quot;&gt;受控组件也是父子通信&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;表单场景里，受控组件其实也是父子通信的一种体现。&lt;/p&gt;
&lt;p&gt;父组件控制输入框的值，子组件只负责展示和触发修改。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;ControlledInput.tsx&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Parent&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;email&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setEmail&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;EmailInput&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;email&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;setEmail&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; EmailInputProps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EmailInput&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;EmailInputProps&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt;(event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;placeholder&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;请输入邮箱&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这里的核心是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;value&lt;/code&gt; 从父组件传给子组件。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;onChange&lt;/code&gt; 从父组件传给子组件。&lt;/li&gt;
&lt;li&gt;子组件触发 &lt;code dir=&quot;auto&quot;&gt;onChange&lt;/code&gt;，父组件更新状态。&lt;/li&gt;
&lt;li&gt;新状态再通过 &lt;code dir=&quot;auto&quot;&gt;value&lt;/code&gt; 传回来。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以受控组件不是“输入框自己管理值”，而是父组件管理值。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;跨层级通信context&quot;&gt;跨层级通信：Context&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果只是父子组件通信，不需要上来就用 Context。&lt;/p&gt;
&lt;p&gt;但如果数据需要跨很多层传递，比如主题、登录用户、语言配置，就可以考虑 Context。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;ThemeContext.tsx&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { createContext, useContext } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Theme &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;light&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;dark&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;ThemeContext&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;createContext&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Theme&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;light&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ThemeContext.Provider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;dark&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Layout&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;ThemeContext.Provider&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Layout&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Toolbar&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Toolbar&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;theme&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;useContext&lt;/span&gt;&lt;span&gt;(ThemeContext);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;theme&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;保存&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Context 解决的是 &lt;code dir=&quot;auto&quot;&gt;props drilling&lt;/code&gt;，也就是一层层传 props 的问题。&lt;/p&gt;
&lt;p&gt;但是它不应该被滥用。不是所有父子通信都需要 Context。对于很近的父子组件，直接用 props 更清楚。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;ref-和命令式调用&quot;&gt;ref 和命令式调用&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;有些场景不是传数据，而是父组件需要调用子组件暴露出来的方法。&lt;/p&gt;
&lt;p&gt;比如父组件点击按钮，让子组件内部的输入框聚焦。&lt;/p&gt;
&lt;p&gt;这时可以使用 &lt;code dir=&quot;auto&quot;&gt;forwardRef&lt;/code&gt; 和 &lt;code dir=&quot;auto&quot;&gt;useImperativeHandle&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;FocusInput.tsx&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { forwardRef, useImperativeHandle, useRef } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; FocusInputRef &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;FocusInput&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;forwardRef&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;FocusInputRef&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function &lt;/span&gt;&lt;span&gt;FocusInput&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;inputRef&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;useRef&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;HTMLInputElement&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;useImperativeHandle&lt;/span&gt;&lt;span&gt;(ref&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; =&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;inputRef&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;current&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputRef&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;placeholder&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;请输入内容&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Parent&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;inputRef&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;useRef&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;FocusInputRef&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;FocusInput&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputRef&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;inputRef&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;current&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;聚焦输入框&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;不过这种方式要谨慎使用。&lt;/p&gt;
&lt;p&gt;React 更推荐声明式的数据流。&lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; 更适合处理 DOM、聚焦、滚动、播放控制这类命令式场景，不适合作为普通业务数据通信的首选方案。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;性能相关回调函数会不会导致重复渲染&quot;&gt;性能相关：回调函数会不会导致重复渲染&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;面试官可能继续问：父组件每次渲染都会创建新的回调函数，会不会导致子组件重复渲染？&lt;/p&gt;
&lt;p&gt;答案是：可能会，但要结合场景看。&lt;/p&gt;
&lt;p&gt;如果子组件使用了 &lt;code dir=&quot;auto&quot;&gt;memo&lt;/code&gt;，并且传入的回调函数每次都是新引用，子组件可能仍然会重新渲染。这时可以使用 &lt;code dir=&quot;auto&quot;&gt;useCallback&lt;/code&gt; 稳定函数引用。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;MemoCallback.tsx&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { memo, useCallback, useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;Child&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;memo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function &lt;/span&gt;&lt;span&gt;Child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;onAdd&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;onAdd&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; =&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;onAdd&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;加一&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Parent&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;] = &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;handleAdd&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;useCallback&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; + &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; []);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Child&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onAdd&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;handleAdd&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;但不要为了“看起来专业”到处写 &lt;code dir=&quot;auto&quot;&gt;useCallback&lt;/code&gt;。如果子组件没有性能问题，或者没有使用 &lt;code dir=&quot;auto&quot;&gt;memo&lt;/code&gt;，盲目使用 &lt;code dir=&quot;auto&quot;&gt;useCallback&lt;/code&gt; 反而会增加理解成本。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;什么时候用状态管理库&quot;&gt;什么时候用状态管理库&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;父子通信、兄弟通信、跨层级通信，本质上都是状态在哪里的问题。&lt;/p&gt;
&lt;p&gt;可以按下面的顺序判断：&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;父组件给子组件数据&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;props&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;子组件通知父组件&lt;/td&gt;&lt;td&gt;回调函数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;兄弟组件共享状态&lt;/td&gt;&lt;td&gt;状态提升&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;多层组件共享稳定数据&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Context&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;大量页面共享复杂业务状态&lt;/td&gt;&lt;td&gt;Zustand、Redux、Jotai 等状态管理库&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;状态管理库不是为了替代 props，而是为了解决更大范围、更复杂的数据共享和更新问题。&lt;/p&gt;
&lt;p&gt;比如用户信息、购物车、权限、全局弹窗、复杂筛选条件，这类状态可能会跨多个页面或模块使用，就可以考虑状态管理库。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;面试时怎么回答&quot;&gt;面试时怎么回答&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这道题可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;React 是单向数据流。父组件向子组件传值用 &lt;code dir=&quot;auto&quot;&gt;props&lt;/code&gt;；子组件想影响父组件时，父组件传回调函数给子组件，子组件调用回调并把数据传回去。如果兄弟组件需要共享状态，就把状态提升到共同父组件。如果跨层级传递太深，可以用 Context；如果是跨页面、跨模块的复杂共享状态，再考虑 Redux、Zustand 这类状态管理库。特殊情况下，父组件需要调用子组件内部方法，可以用 &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;forwardRef&lt;/code&gt; 和 &lt;code dir=&quot;auto&quot;&gt;useImperativeHandle&lt;/code&gt;，但这更适合聚焦、滚动这类命令式场景，不是普通数据通信的首选。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果面试官继续追问，可以展开这些点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React 为什么强调单向数据流？&lt;/li&gt;
&lt;li&gt;状态提升解决什么问题？&lt;/li&gt;
&lt;li&gt;Context 和状态管理库有什么区别？&lt;/li&gt;
&lt;li&gt;受控组件为什么也是父子通信？&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; 能不能替代 props？&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;useCallback&lt;/code&gt; 是否一定能优化性能？&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;React 父子通信这道题看似基础，但它可以引申到很多 React 核心思想：&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;跨层级共享再考虑 Context。&lt;/li&gt;
&lt;li&gt;复杂全局状态再考虑状态管理库。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; 是命令式能力，不是普通数据流的替代品。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以这道题最重要的不是记住几个 API，而是理解 React 组件之间的数据边界：&lt;strong&gt;谁拥有状态，谁负责修改状态，谁只是接收状态并渲染。&lt;/strong&gt;&lt;/p&gt;</content:encoded><category>React</category><category>前端</category><category>面试</category></item><item><title>Redis 缓存穿透、击穿、雪崩面试题复盘</title><link>https://blog.veyliss.top/blog/redis_cache_problems_interview_01/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/redis_cache_problems_interview_01/</guid><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是 Redis 面试里非常高频的一类问题：&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;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;穿透：查的数据根本不存在。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;击穿：一个热点 Key 过期。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;雪崩：大量 Key 同时过期，或者 Redis 整体不可用。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;缓存穿透&quot;&gt;缓存穿透&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;缓存穿透指的是：请求的数据在缓存中不存在，在数据库中也不存在。&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;请求一个不存在的用户 ID：&lt;code dir=&quot;auto&quot;&gt;id = -1&lt;/code&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;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;请求 -&gt; 查 Redis -&gt; Redis 没有 -&gt; 查数据库 -&gt; 写入 Redis -&gt; 返回数据&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;但如果数据根本不存在，就会变成：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;请求 -&gt; 查 Redis -&gt; Redis 没有 -&gt; 查数据库 -&gt; 数据库也没有 -&gt; 返回空&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;问题在于：下一次同样的非法请求过来，Redis 里还是没有，于是又会打到数据库。&lt;/p&gt;
&lt;p&gt;如果有人构造大量不存在的 ID，请求就会绕过缓存，持续打到数据库。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;穿透的解决方案&quot;&gt;穿透的解决方案&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;缓存穿透常见有两种解决方案：缓存空对象和布隆过滤器。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;缓存空对象&quot;&gt;缓存空对象&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;当数据库查不到数据时，也往 Redis 里写一个空值或特殊值，并设置较短过期时间。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;cache-null.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;cacheKey&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;user:&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;cached&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cacheKey&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;cached&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cached&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cached&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;queryUserById&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cacheKey&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;EX&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cacheKey&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;EX&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3600&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这样下一次请求同一个不存在的 ID，就会被 Redis 拦住，不会继续打数据库。&lt;/p&gt;
&lt;p&gt;它的优点是简单，缺点是如果恶意请求的非法 ID 极多，Redis 里会出现很多空值缓存。所以空值缓存的过期时间一般要短一些。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;布隆过滤器&quot;&gt;布隆过滤器&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;布隆过滤器适合在请求进入缓存和数据库之前，先判断这个数据是否可能存在。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;请求 userId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;布隆过滤器判断&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;| 不存在 -&gt; 直接拒绝&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;| 可能存在&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;查 Redis / DB&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&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;所以它适合挡掉大量明显非法的请求。&lt;/p&gt;
&lt;p&gt;面试里可以这样说：&lt;strong&gt;缓存空对象适合兜住少量不存在数据，布隆过滤器适合在入口处拦截大量非法 Key。&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;缓存击穿&quot;&gt;缓存击穿&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;缓存击穿指的是：某一个热点 Key 在过期瞬间，大量并发请求同时打到数据库。&lt;/p&gt;
&lt;p&gt;注意，击穿通常是单个 Key。&lt;/p&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;这个 Key 平时在 Redis 里，所以数据库压力不大。&lt;/p&gt;
&lt;p&gt;但它刚好过期时，大量请求同时进来，发现 Redis 没有，于是一起查数据库。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;热点 Key 过期&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;大量请求同时进入&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Redis 都没查到&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;全部打到数据库&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这就是缓存击穿。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;击穿的解决方案&quot;&gt;击穿的解决方案&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;缓存击穿常见有两种方案：互斥锁和逻辑过期。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;互斥锁&quot;&gt;互斥锁&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;互斥锁的思路是：同一时间只允许一个线程去查数据库并重建缓存，其他线程等待或重试。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;mutex-lock.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getProduct&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;cacheKey&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;product:&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;lockKey&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;lock:product:&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;cached&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cacheKey&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;cached&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cached&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;locked&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lockKey&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;NX&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;EX&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;locked&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getProduct&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;queryProductById&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cacheKey&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;EX&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3600&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;del&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lockKey&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这个方案可以保护数据库，但会让部分请求等待，接口耗时可能变长。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;逻辑过期&quot;&gt;逻辑过期&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;逻辑过期的思路是：Redis 里的数据不设置物理过期，或者设置很长的物理过期；真正的过期时间写在 value 里。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;logical-expire-value.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;热门商品&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;expireAt&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;2026-05-20T20:00:00+08:00&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&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;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;查到缓存&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;判断逻辑过期&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;已过期 -&gt; 尝试加锁 -&gt; 异步重建缓存&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;直接返回旧数据&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;它的优点是响应速度更稳，不会让大量请求等待数据库。&lt;/p&gt;
&lt;p&gt;缺点是用户可能短时间看到旧数据，所以它更适合对实时性要求不那么高的热点数据。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;缓存雪崩&quot;&gt;缓存雪崩&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;缓存雪崩指的是：大量 Key 在同一时间过期，或者 Redis 服务不可用，导致大量请求同时打到数据库。&lt;/p&gt;
&lt;p&gt;它和击穿的区别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;击穿是单个热点 Key。&lt;/li&gt;
&lt;li&gt;雪崩是大量 Key，或者缓存层整体出问题。&lt;/li&gt;
&lt;/ul&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;Redis 宕机或网络故障。&lt;/li&gt;
&lt;li&gt;Redis 集群大面积不可用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;雪崩的危害比击穿更大，因为它不是一个点，而是一片。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;雪崩的解决方案&quot;&gt;雪崩的解决方案&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;缓存雪崩通常要从多个层面解决。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;过期时间加随机值&quot;&gt;过期时间加随机值&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;不要让大量 Key 设置完全一样的过期时间。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;random-expire.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;baseTtl&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;3600&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;randomTtl&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;random&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; * &lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cacheKey&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;EX&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;baseTtl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;randomTtl&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这样可以把过期时间打散，避免某一秒大量 Key 同时失效。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;多级缓存&quot;&gt;多级缓存&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;可以在 Redis 前面加本地缓存，比如 Caffeine、Guava Cache 或进程内缓存。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;请求 -&gt; 本地缓存 -&gt; Redis -&gt; 数据库&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;即使 Redis 短时间抖动，本地缓存也能顶住一部分热点请求。&lt;/p&gt;
&lt;p&gt;不过本地缓存也会带来一致性问题，所以一般只适合热点数据或允许短暂不一致的数据。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;服务降级和限流&quot;&gt;服务降级和限流&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;当数据库压力过大时，不能让所有请求继续堆积。&lt;/p&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;div&gt;&lt;h3 id=&quot;redis-高可用&quot;&gt;Redis 高可用&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;如果雪崩原因是 Redis 宕机，就需要高可用架构。&lt;/p&gt;
&lt;p&gt;常见方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Redis 主从复制。&lt;/li&gt;
&lt;li&gt;哨兵模式。&lt;/li&gt;
&lt;li&gt;Redis Cluster。&lt;/li&gt;
&lt;li&gt;多机房或多可用区部署。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;高可用解决的是缓存服务不可用的问题，过期时间随机解决的是大量 Key 同时失效的问题。两者关注点不同。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;三者对比&quot;&gt;三者对比&lt;/h2&gt;&lt;/div&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;缓存穿透&lt;/th&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;核心原因&lt;/td&gt;&lt;td&gt;数据根本不存在&lt;/td&gt;&lt;td&gt;热点 Key 过期&lt;/td&gt;&lt;td&gt;大量 Key 同时过期或 Redis 宕机&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Key 数量&lt;/td&gt;&lt;td&gt;大量不存在 Key&lt;/td&gt;&lt;td&gt;单个热点 Key&lt;/td&gt;&lt;td&gt;多个或大部分 Key&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;数据库状态&lt;/td&gt;&lt;td&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;/td&gt;&lt;td&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;/td&gt;&lt;td&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;记忆时可以抓住关键词：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;穿透：不存在&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;击穿：热点&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;雪崩：大量&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;面试时怎么回答&quot;&gt;面试时怎么回答&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这道题可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;缓存穿透是请求的数据在缓存和数据库中都不存在，请求会绕过缓存持续打到数据库，常用缓存空对象和布隆过滤器解决。缓存击穿是某个热点 Key 在过期瞬间被大量并发请求访问，导致请求同时打到数据库，常用互斥锁或逻辑过期解决。缓存雪崩是大量 Key 同时过期，或者 Redis 服务不可用，导致大量请求同时涌向数据库，常用过期时间加随机值、多级缓存、限流降级和 Redis 高可用解决。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果面试官继续追问，可以补充方案取舍：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存空对象简单，但要设置较短 TTL，避免缓存污染。&lt;/li&gt;
&lt;li&gt;布隆过滤器适合拦截大量非法请求，但存在误判为可能存在。&lt;/li&gt;
&lt;li&gt;互斥锁能保护数据库，但会增加等待时间。&lt;/li&gt;
&lt;li&gt;逻辑过期响应快，但可能返回旧数据。&lt;/li&gt;
&lt;li&gt;过期随机能打散失效时间，但不能解决 Redis 宕机。&lt;/li&gt;
&lt;li&gt;Redis 高可用能提升可用性，但不能替代限流和降级。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这类 Redis 面试题的关键，是把三个概念的根因讲清楚。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;缓存穿透：缓存没有，数据库也没有。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;缓存击穿：缓存没有，但数据库有，而且是热点 Key。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;缓存雪崩：大量缓存同时没有，或者 Redis 整体不可用。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;只要先分清根因，再说解决方案，就不会混乱。&lt;/p&gt;
&lt;p&gt;最后再补一句工程思维：缓存是为了保护数据库，但缓存系统本身也会失效。所以真正可靠的设计，通常不是只靠一个方案，而是缓存策略、限流降级、异步重建和高可用架构一起配合。&lt;/p&gt;</content:encoded><category>Redis</category><category>后端</category><category>面试</category></item><item><title>深拷贝、浅拷贝与堆栈面试题复盘</title><link>https://blog.veyliss.top/blog/deep_shallow_copy_heap_stack_interview_01/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/deep_shallow_copy_heap_stack_interview_01/</guid><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是前端面试里非常常见的一类问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;什么是深拷贝和浅拷贝？它们和堆、栈有什么关系？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这道题看起来像是在问 API，实际上是在考察你是否理解 JavaScript 的数据类型、内存模型和引用关系。&lt;/p&gt;
&lt;p&gt;如果只回答“浅拷贝只拷贝一层，深拷贝会递归拷贝”，只能算答到表面。更完整的回答应该从基本类型和引用类型讲起，再解释为什么对象拷贝容易互相影响。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;先说结论&quot;&gt;先说结论&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;在 JavaScript 中，数据大致可以分成两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基本类型：&lt;code dir=&quot;auto&quot;&gt;string&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;number&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;boolean&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;undefined&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;symbol&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;bigint&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;引用类型：&lt;code dir=&quot;auto&quot;&gt;object&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;array&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;function&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;Date&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;Map&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;Set&lt;/code&gt; 等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基本类型保存的是值本身，赋值时通常是值的复制。&lt;/p&gt;
&lt;p&gt;引用类型保存的是对象的引用，赋值时复制的是引用地址，所以多个变量可能指向同一个对象。&lt;/p&gt;
&lt;p&gt;浅拷贝只拷贝对象第一层。如果第一层里还有对象，里面的对象仍然共享引用。&lt;/p&gt;
&lt;p&gt;深拷贝会把嵌套对象也复制出来，让新对象和旧对象尽量互不影响。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;栈和堆怎么理解&quot;&gt;栈和堆怎么理解&lt;/h2&gt;&lt;/div&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;/ul&gt;
&lt;p&gt;这是一种帮助理解的简化模型，不需要把它讲得像浏览器引擎源码一样复杂。&lt;/p&gt;
&lt;p&gt;可以这样理解：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let name = &apos;xiaoxi&apos;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;栈：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name -&gt; &apos;xiaoxi&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;基本类型比较直接，变量和值之间的关系很简单。&lt;/p&gt;
&lt;p&gt;再看对象：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;profile: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;age: &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;可以理解为：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;栈：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user -&gt; 引用地址 0x001&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;堆：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;0x001 -&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &apos;xiaoxi&apos;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;profile: 引用地址 0x002&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;0x002 -&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;age: 18&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;对象本身放在堆里，变量 &lt;code dir=&quot;auto&quot;&gt;user&lt;/code&gt; 保存的是引用。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;赋值不是拷贝对象&quot;&gt;赋值不是拷贝对象&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;很多拷贝问题都从赋值开始。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;reference-assignment.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;profile: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;age: &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;user2&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;veyliss&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// veyliss&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这里 &lt;code dir=&quot;auto&quot;&gt;user2 = user1&lt;/code&gt; 并没有创建一个新对象，只是让 &lt;code dir=&quot;auto&quot;&gt;user2&lt;/code&gt; 和 &lt;code dir=&quot;auto&quot;&gt;user1&lt;/code&gt; 指向同一个对象。&lt;/p&gt;
&lt;p&gt;所以修改 &lt;code dir=&quot;auto&quot;&gt;user2.name&lt;/code&gt;，&lt;code dir=&quot;auto&quot;&gt;user1.name&lt;/code&gt; 也会变。&lt;/p&gt;
&lt;p&gt;可以理解为：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user1 -&gt; 0x001&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user2 -&gt; 0x001&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;两个变量指向同一个堆对象。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;什么是浅拷贝&quot;&gt;什么是浅拷贝&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;浅拷贝会创建一个新的外层对象，但里面的引用类型属性仍然和原对象共享。&lt;/p&gt;
&lt;p&gt;常见浅拷贝方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;展开运算符：&lt;code dir=&quot;auto&quot;&gt;{ ...obj }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Object.assign()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;数组的 &lt;code dir=&quot;auto&quot;&gt;slice()&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;concat()&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;Array.from()&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;[...arr]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;看一个例子：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;shallow-copy.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;profile: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;age: &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;user2&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;veyliss&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;profile&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;age&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// xiaoxi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;profile&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;age&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 20&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;为什么 &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt; 没有互相影响，但 &lt;code dir=&quot;auto&quot;&gt;profile.age&lt;/code&gt; 互相影响了？&lt;/p&gt;
&lt;p&gt;因为 &lt;code dir=&quot;auto&quot;&gt;user2&lt;/code&gt; 是一个新的外层对象，但 &lt;code dir=&quot;auto&quot;&gt;profile&lt;/code&gt; 仍然指向同一个内部对象。&lt;/p&gt;
&lt;p&gt;可以理解为：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user1 -&gt; 0x001 -&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &apos;xiaoxi&apos;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;profile: 0x002&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user2 -&gt; 0x003 -&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &apos;xiaoxi&apos;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;profile: 0x002&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;外层对象不同，内层 &lt;code dir=&quot;auto&quot;&gt;profile&lt;/code&gt; 相同。&lt;/p&gt;
&lt;p&gt;这就是浅拷贝。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;什么是深拷贝&quot;&gt;什么是深拷贝&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;深拷贝会递归复制对象中的嵌套对象，让新对象和旧对象不再共享内部引用。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;deep-copy-result.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;profile: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;age: &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;user2&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;structuredClone&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;profile&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;age&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;user1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;profile&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;age&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 18&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;user2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;profile&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;age&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 20&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这时可以理解为：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user1 -&gt; 0x001 -&gt; profile -&gt; 0x002&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;user2 -&gt; 0x003 -&gt; profile -&gt; 0x004&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;外层对象不同，内层对象也不同。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;json-方法的深拷贝&quot;&gt;JSON 方法的深拷贝&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;以前很常见的一种写法是：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;json-clone.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这种方式简单，但有明显限制。&lt;/p&gt;
&lt;p&gt;它适合普通 JSON 数据：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tags:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;前端&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;JavaScript&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;但它处理不了很多特殊值：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;json-clone-limit.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;createdAt: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;sayHello&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;hello&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value: &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createdAt&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 字符串，不再是 Date&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sayHello&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// undefined&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// undefined&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;它的主要问题包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Date&lt;/code&gt; 会变成字符串。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;undefined&lt;/code&gt;、函数、&lt;code dir=&quot;auto&quot;&gt;symbol&lt;/code&gt; 会丢失。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Map&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;Set&lt;/code&gt; 不能按原结构保留。&lt;/li&gt;
&lt;li&gt;遇到循环引用会直接报错。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以面试时不要把 &lt;code dir=&quot;auto&quot;&gt;JSON.parse(JSON.stringify())&lt;/code&gt; 说成万能深拷贝。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;structuredclone&quot;&gt;structuredClone&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;现代浏览器和 Node.js 中可以使用 &lt;code dir=&quot;auto&quot;&gt;structuredClone()&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;structured-clone.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;createdAt: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;items: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Map&lt;/span&gt;&lt;span&gt;([[&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]])&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;structuredClone&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createdAt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;items&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Map&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;相比 JSON 方法，&lt;code dir=&quot;auto&quot;&gt;structuredClone()&lt;/code&gt; 能保留更多内置类型，也能处理循环引用。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;structured-clone-cycle.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;structuredClone&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;不过它也不是没有限制。函数、DOM 节点这类内容不能直接被结构化克隆。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;手写深拷贝&quot;&gt;手写深拷贝&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;面试里有时会要求手写一个简单版本。&lt;/p&gt;
&lt;p&gt;最基础的写法是递归：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;simple-deep-clone.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deepClone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isArray&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span&gt; : {}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (Object&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;hasOwnProperty&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deepClone&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这个版本可以处理普通对象和数组，但不能处理循环引用，也不能完整处理 &lt;code dir=&quot;auto&quot;&gt;Date&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;Map&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;Set&lt;/code&gt; 等类型。&lt;/p&gt;
&lt;p&gt;如果要支持循环引用，可以用 &lt;code dir=&quot;auto&quot;&gt;WeakMap&lt;/code&gt; 记录已经拷贝过的对象。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;deep-clone-with-weakmap.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deepClone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WeakMap&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getTime&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Map&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Map&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;deepClone&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deepClone&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Set&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Set&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;deepClone&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isArray&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span&gt; : {}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;Reflect&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ownKeys&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deepClone&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&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;code dir=&quot;auto&quot;&gt;Date&lt;/code&gt; 单独处理。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Map&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;Set&lt;/code&gt; 单独处理。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;WeakMap&lt;/code&gt; 解决循环引用。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Reflect.ownKeys()&lt;/code&gt; 可以拿到 &lt;code dir=&quot;auto&quot;&gt;symbol&lt;/code&gt; key。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但它仍然不是完整工业级实现。比如属性描述符、原型链、不可枚举属性、函数、DOM 节点等，还需要更多额外处理。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;浅拷贝和深拷贝怎么选&quot;&gt;浅拷贝和深拷贝怎么选&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;不是所有场景都需要深拷贝。&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;只改第一层字段&lt;/td&gt;&lt;td&gt;浅拷贝即可&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;React 更新一层状态&lt;/td&gt;&lt;td&gt;展开运算符或 &lt;code dir=&quot;auto&quot;&gt;Object.assign()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;嵌套对象也要完全隔离&lt;/td&gt;&lt;td&gt;深拷贝&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;普通 JSON 数据复制&lt;/td&gt;&lt;td&gt;JSON 方法可以考虑&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;复杂对象、循环引用&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;structuredClone()&lt;/code&gt; 或专门工具&lt;/td&gt;&lt;/tr&gt;&lt;tr&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;在 React 里也经常会遇到这个问题。&lt;/p&gt;
&lt;p&gt;比如更新用户名称：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;setUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;veyliss&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这是浅拷贝，足够更新第一层。&lt;/p&gt;
&lt;p&gt;如果要更新嵌套字段：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;setUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;profile: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;...user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;profile&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;age: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这不是对整个对象做深拷贝，而是只拷贝发生变化的路径。这个方式在 React 里更常见，也更可控。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;面试时怎么回答&quot;&gt;面试时怎么回答&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这道题可以这样回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;JavaScript 里基本类型通常保存值本身，引用类型保存的是对象引用。对象内容可以理解为存在堆中，变量里保存的是引用地址。普通赋值只是复制引用，不会创建新对象。浅拷贝会创建一个新的外层对象，但嵌套对象仍然共享引用；深拷贝会递归复制嵌套对象，让新旧对象尽量不共享引用。常见浅拷贝方式有展开运算符、&lt;code dir=&quot;auto&quot;&gt;Object.assign()&lt;/code&gt;、数组 &lt;code dir=&quot;auto&quot;&gt;slice()&lt;/code&gt; 等；常见深拷贝方式有 &lt;code dir=&quot;auto&quot;&gt;structuredClone()&lt;/code&gt;、JSON 序列化和手写递归。JSON 方法有局限，会丢失函数、&lt;code dir=&quot;auto&quot;&gt;undefined&lt;/code&gt;，也不能处理循环引用。手写深拷贝时要考虑递归、特殊类型和循环引用，可以用 &lt;code dir=&quot;auto&quot;&gt;WeakMap&lt;/code&gt; 做缓存。&lt;/p&gt;
&lt;/blockquote&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;code dir=&quot;auto&quot;&gt;JSON.parse(JSON.stringify())&lt;/code&gt; 有哪些问题。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;structuredClone()&lt;/code&gt; 能解决什么，不能解决什么。&lt;/li&gt;
&lt;li&gt;手写深拷贝如何处理循环引用。&lt;/li&gt;
&lt;li&gt;React 中为什么经常只拷贝变化路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;深拷贝、浅拷贝和堆栈这道题，本质是在问你是否理解引用。&lt;/p&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;li&gt;深拷贝递归复制嵌套层。&lt;/li&gt;
&lt;li&gt;深拷贝不是越多越好，要看业务是否真的需要完全隔离。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;面试里把这些关系讲清楚，比单纯背一个手写深拷贝函数更重要。&lt;/p&gt;</content:encoded><category>JavaScript</category><category>后端</category><category>前端</category><category>面试</category></item><item><title>电商订单过期处理面试题复盘</title><link>https://blog.veyliss.top/blog/ecommerce_order_expiry_interview_01/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/ecommerce_order_expiry_interview_01/</guid><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是我 2024 年遇到的一道电商面试题，题目大概是：&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;strong&gt;不要让数据库自己去扛所有过期判断，也不要用定时任务在同一秒扫大量订单。订单创建时写入延时队列，到期后由消费者接收消息，再做批量更新和后续事件分发。&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;问题本质&quot;&gt;问题本质&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;订单过期一般不是一个难逻辑。比如订单超过 30 分钟未支付，就把状态从 &lt;code dir=&quot;auto&quot;&gt;WAIT_PAY&lt;/code&gt; 改成 &lt;code dir=&quot;auto&quot;&gt;CLOSED&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;真正的问题在于：如果某个秒级时间点有大量订单同时过期，系统会出现几个压力点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据库短时间内出现大量 &lt;code dir=&quot;auto&quot;&gt;update&lt;/code&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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以这道题不能只回答“定时任务扫一下订单表”。那样在低并发下能跑，但在电商场景里不够稳。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;整体方案&quot;&gt;整体方案&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;我当时记录的主思路是四步：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;订单创建 -&gt; 写入延时队列 -&gt; 到期消费 -&gt; 批量关闭订单 -&gt; 发布后续业务事件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;可以画成这样：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;创建订单&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;| 写订单表&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;发送延时消息：orderId + expireTime&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;| 到期后投递&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;订单过期消费者&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;| 聚合一小批订单&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;批量更新订单状态&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;| 更新成功后发布事件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;释放库存 / 退款 / 通知用户 / 同步 ES 或缓存&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;核心目标是：&lt;strong&gt;把同一秒的大量过期订单，拆成可控的批量写入和异步事件处理。&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;订单创建时写入延时队列&quot;&gt;订单创建时写入延时队列&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;订单创建成功后，系统先写入订单表，然后把订单 ID 和过期时间写入延时队列。&lt;/p&gt;
&lt;p&gt;消息里通常包含：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;order-expire-message.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;orderId&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;202401010001&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;orderType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;NORMAL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;expireTime&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;2024-01-01T10:30:00+08:00&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;延时时间可以这样计算：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TTL = expireTime - now()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;也就是说，如果订单 30 分钟后过期，就发送一条 30 分钟后投递的延时消息。&lt;/p&gt;
&lt;p&gt;这里可以选不同的实现：&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;RabbitMQ TTL + 死信队列&lt;/td&gt;&lt;td&gt;到期后消息进入死信队列，由消费者处理&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RocketMQ 延时消息&lt;/td&gt;&lt;td&gt;原生支持延时消息，适合常见延时等级&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Redis ZSet&lt;/td&gt;&lt;td&gt;用过期时间作为 score，消费者按时间拉取&lt;/td&gt;&lt;/tr&gt;&lt;tr&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;面试时不用把某个 MQ 的细节讲太深，重点是说明：订单创建时就把“未来要关闭订单”这件事交给延时机制，而不是等数据库定时扫描。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;到期后进入消费者&quot;&gt;到期后进入消费者&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;当延时时间到达，消息会被投递到订单过期消费者。&lt;/p&gt;
&lt;p&gt;消费者拿到消息后，不能直接无脑关闭订单，而是要先做状态校验：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;收到 orderId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;查询订单当前状态&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;如果已支付：忽略&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;如果已关闭：忽略&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;如果待支付且已超时：关闭&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;原因很简单：延时消息到达时，订单可能已经被用户支付了。&lt;/p&gt;
&lt;p&gt;所以关闭订单必须是有条件的。不能只根据“消息到了”就关闭。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;消费端做批量处理&quot;&gt;消费端做批量处理&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果每条消息都单独更新一次数据库，几千条过期订单就会变成几千次 &lt;code dir=&quot;auto&quot;&gt;update&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;更好的做法是：消费者先把消息暂存到一个批量处理队列里，然后按数量或时间窗口触发批量更新。&lt;/p&gt;
&lt;p&gt;常见触发条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;累积到 &lt;code dir=&quot;auto&quot;&gt;100-500&lt;/code&gt; 条订单。&lt;/li&gt;
&lt;li&gt;或者等待 &lt;code dir=&quot;auto&quot;&gt;100ms&lt;/code&gt; 左右。&lt;/li&gt;
&lt;li&gt;两个条件满足任意一个就执行批量更新。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以理解为一个小型缓冲区：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;消息 1 -&gt; buffer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;消息 2 -&gt; buffer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;消息 3 -&gt; buffer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;达到 300 条，批量 update&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&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;div&gt;&lt;h2 id=&quot;批量更新要保证幂等&quot;&gt;批量更新要保证幂等&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;批量更新订单状态时，一定要带上状态条件。&lt;/p&gt;
&lt;p&gt;比如只关闭仍然处于待支付状态的订单：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;batch-close-orders.sql&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;UPDATE&lt;/span&gt;&lt;span&gt; orders&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;SET&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;CLOSED&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;close_reason &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;TIMEOUT&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;closed_at &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;NOW&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span&gt;IN&lt;/span&gt;&lt;span&gt; (...)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;AND&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;WAIT_PAY&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;AND&lt;/span&gt;&lt;span&gt; expire_time &lt;/span&gt;&lt;span&gt;&amp;#x3C;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;NOW&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这里的 &lt;code dir=&quot;auto&quot;&gt;status = &apos;WAIT_PAY&apos;&lt;/code&gt; 很关键。&lt;/p&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;/ul&gt;
&lt;p&gt;这就是幂等。&lt;/p&gt;
&lt;p&gt;面试里可以直接说：&lt;strong&gt;消息队列天然可能重复投递，所以订单关闭逻辑必须是幂等的。&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;更新后发布业务事件&quot;&gt;更新后发布业务事件&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;订单状态批量更新完成后，不要在同一个流程里把所有后续业务都做完。&lt;/p&gt;
&lt;p&gt;应该发布一个订单关闭事件：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;order-closed-event.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;eventType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ORDER_CLOSED&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;reason&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TIMEOUT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;orderIds&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;202401010001&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;202401010002&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&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;同步订单状态到 ES。&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;p&gt;关闭订单是核心链路，必须尽快完成；释放库存、通知用户、同步缓存这些动作可以异步扩展。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;为什么不用定时任务直接扫表&quot;&gt;为什么不用定时任务直接扫表&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;定时任务不是不能用，但它不适合直接承担高并发订单过期主链路。&lt;/p&gt;
&lt;p&gt;如果用定时任务每秒扫一次订单表，大概会遇到几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查询条件依赖 &lt;code dir=&quot;auto&quot;&gt;expire_time&lt;/code&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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;定时任务更适合做兜底补偿，比如每隔一段时间扫描仍然处于 &lt;code dir=&quot;auto&quot;&gt;WAIT_PAY&lt;/code&gt; 且已经超时的订单，补偿可能丢失的延时消息。&lt;/p&gt;
&lt;p&gt;所以更完整的方案是：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;延时队列处理主流程&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;定时任务做兜底补偿&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;还要注意削峰&quot;&gt;还要注意削峰&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;题目里说某一秒有数以千计订单过期，这其实就是一个瞬时流量峰值。&lt;/p&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;对数据库连接池和 MQ 消费速度设置上限。&lt;/li&gt;
&lt;li&gt;监控积压量，必要时临时扩容消费者。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关键思想是：&lt;strong&gt;MQ 可以积压，消费者可以慢慢处理，但数据库不能被突然打爆。&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;面试时可以这样回答&quot;&gt;面试时可以这样回答&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这道题可以按下面这段话回答：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我不会让数据库在某一秒直接承受大量订单过期更新。订单创建成功后，会把订单 ID 和过期时间写入延时队列，TTL 设置为 &lt;code dir=&quot;auto&quot;&gt;expireTime - now()&lt;/code&gt;。消息到期后投递给订单过期消费者，消费者先校验订单是否仍是待支付状态，再把消息放入批量缓冲区，按固定数量或固定时间窗口统一批量更新订单状态。更新时 SQL 会带上 &lt;code dir=&quot;auto&quot;&gt;status = WAIT_PAY&lt;/code&gt; 和过期时间条件，保证幂等，避免误关已支付订单。订单关闭成功后再发布订单关闭事件，由库存、退款、通知、缓存同步等消费者异步处理。定时任务只作为兜底补偿，不作为主流程。&lt;/p&gt;
&lt;/blockquote&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;li&gt;事件驱动。&lt;/li&gt;
&lt;li&gt;兜底补偿。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;这道题的重点不是“怎么把订单改成过期”，而是怎么在大量订单同时过期时保护数据库。&lt;/p&gt;
&lt;p&gt;比较合理的设计是：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;订单创建时写延时队列&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;到期后消费者接收消息&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;消费者聚合订单并批量更新&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;更新时保证幂等&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;关闭后发布业务事件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;定时任务做兜底补偿&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;电商系统里很多问题都不是单点逻辑难，而是峰值、幂等、补偿、解耦这些工程问题难。这个题目正好把这些点串到了一起。&lt;/p&gt;</content:encoded><category>后端</category><category>面试</category><category>电商</category><category>消息队列</category></item><item><title>Vue 请求后端数据与跨域问题</title><link>https://blog.veyliss.top/blog/vue_axios_cors_01/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/vue_axios_cors_01/</guid><pubDate>Tue, 16 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这篇文章迁移自我早年写在博客园的一篇记录。当时遇到的问题很直接：&lt;strong&gt;后端接口已经写好了，Vue 前端应该怎么请求数据？请求时报跨域又应该怎么处理？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现在回头看，这其实是前后端分离开发里非常典型的一步：前端项目跑在一个端口，后端服务跑在另一个端口，浏览器因为同源策略限制，默认不允许它们随便互相请求。&lt;/p&gt;
&lt;p&gt;这篇文章按现在的写法重新整理一遍：先用 axios 发起请求，再抽出请求模块，最后处理跨域问题。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;场景说明&quot;&gt;场景说明&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;假设后端提供了一个用户列表接口：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;GET http://localhost:8088/user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;前端 Vue 项目运行在另一个地址，例如：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;http://localhost:8080/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这两个地址的协议、域名或端口只要有一个不同，就不是同源。这里端口不同，所以浏览器会把它们视为跨域请求。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;安装-axios&quot;&gt;安装 axios&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;前端请求接口可以使用 &lt;code dir=&quot;auto&quot;&gt;axios&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;安装后，在需要请求数据的 Vue 页面中引入：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; axios &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果项目使用的是 Vue 2 或 Options API，可以先在 &lt;code dir=&quot;auto&quot;&gt;methods&lt;/code&gt; 中写一个简单请求方法。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;methods: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;fetchUsers&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// 这里填写后端接口地址&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;http://localhost:8088/user&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;// 请求成功后，后端返回的数据在 response.data 中&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;// 请求失败时会进入 catch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;// 不管成功还是失败，finally 都会执行&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;请求结束&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;页面上可以绑定一个按钮测试请求：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;fetchUsers&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;请求用户数据&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;点击按钮后，打开浏览器控制台。如果后端接口正常，并且跨域已经允许，就能在控制台看到返回数据。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;async-写法&quot;&gt;async 写法&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果你更喜欢同步风格，也可以用 &lt;code dir=&quot;auto&quot;&gt;async / await&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; axios &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;methods: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetchUsers&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// await 会等待请求完成&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;http://localhost:8088/user&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// 后端真正返回的数据通常放在 response.data&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// 网络错误、接口错误、跨域错误都会进入这里&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这种写法在业务代码里更容易读，尤其是请求成功后还有多步处理时。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;为什么要封装请求&quot;&gt;为什么要封装请求&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;刚开始写一个请求时，直接在页面里写完整地址没有问题。&lt;/p&gt;
&lt;p&gt;但接口一多，就会出现几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个页面都要写 &lt;code dir=&quot;auto&quot;&gt;http://localhost:8088&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;请求地址散落在各个组件里。&lt;/li&gt;
&lt;li&gt;后续换服务器地址时，需要到处改。&lt;/li&gt;
&lt;li&gt;请求头、错误处理、登录 token 等逻辑难以统一。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以更常见的做法是：把 axios 实例单独封装起来，再把具体接口拆到 &lt;code dir=&quot;auto&quot;&gt;api&lt;/code&gt; 目录里。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;推荐目录结构&quot;&gt;推荐目录结构&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;可以按下面的方式组织前端请求代码：&lt;/p&gt;
&lt;starlight-file-tree data-pagefind-ignore=&quot;true&quot;&gt;&lt;ul&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;src/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;api/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M5.376 14.300L5.376 3.352L9.286 3.352L9.286 14.300Q9.286 17.842 7.630 19.452L7.630 19.452Q6.112 20.924 3.076 20.924L3.076 20.924Q2.386 20.924 1.558 20.809Q0.730 20.694 0.224 20.464L0.224 20.464L0.638 17.336Q1.512 17.750 2.662 17.750L2.662 17.750Q3.950 17.750 4.594 17.060L4.594 17.060Q5.376 16.232 5.376 14.300L5.376 14.300ZM11.862 19.912L12.736 16.600Q13.564 17.060 14.668 17.382L14.668 17.382Q15.910 17.750 17.014 17.750L17.014 17.750Q18.394 17.750 19.084 17.267Q19.774 16.784 19.774 15.910Q19.774 15.036 19.130 14.507Q18.486 13.978 16.876 13.426L16.876 13.426Q12.138 11.770 12.138 8.274L12.138 8.274Q12.138 5.974 13.909 4.525Q15.680 3.076 18.762 3.076L18.762 3.076Q21.200 3.076 23.224 3.950L23.224 3.950L22.350 7.124L22.074 6.986Q21.246 6.664 20.740 6.526L20.740 6.526Q19.774 6.250 18.762 6.250L18.762 6.250Q17.520 6.250 16.853 6.733Q16.186 7.216 16.186 7.998Q16.186 8.780 16.922 9.286L16.922 9.286Q17.474 9.700 19.314 10.436L19.314 10.436Q21.614 11.310 22.695 12.552Q23.776 13.794 23.776 15.588L23.776 15.588Q23.776 17.888 22.028 19.314L22.028 19.314Q20.142 20.924 16.738 20.924L16.738 20.924Q15.404 20.924 13.932 20.556L13.932 20.556Q12.782 20.326 11.862 19.912L11.862 19.912Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;user.js&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;utils/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M5.376 14.300L5.376 3.352L9.286 3.352L9.286 14.300Q9.286 17.842 7.630 19.452L7.630 19.452Q6.112 20.924 3.076 20.924L3.076 20.924Q2.386 20.924 1.558 20.809Q0.730 20.694 0.224 20.464L0.224 20.464L0.638 17.336Q1.512 17.750 2.662 17.750L2.662 17.750Q3.950 17.750 4.594 17.060L4.594 17.060Q5.376 16.232 5.376 14.300L5.376 14.300ZM11.862 19.912L12.736 16.600Q13.564 17.060 14.668 17.382L14.668 17.382Q15.910 17.750 17.014 17.750L17.014 17.750Q18.394 17.750 19.084 17.267Q19.774 16.784 19.774 15.910Q19.774 15.036 19.130 14.507Q18.486 13.978 16.876 13.426L16.876 13.426Q12.138 11.770 12.138 8.274L12.138 8.274Q12.138 5.974 13.909 4.525Q15.680 3.076 18.762 3.076L18.762 3.076Q21.200 3.076 23.224 3.950L23.224 3.950L22.350 7.124L22.074 6.986Q21.246 6.664 20.740 6.526L20.740 6.526Q19.774 6.250 18.762 6.250L18.762 6.250Q17.520 6.250 16.853 6.733Q16.186 7.216 16.186 7.998Q16.186 8.780 16.922 9.286L16.922 9.286Q17.474 9.700 19.314 10.436L19.314 10.436Q21.614 11.310 22.695 12.552Q23.776 13.794 23.776 15.588L23.776 15.588Q23.776 17.888 22.028 19.314L22.028 19.314Q20.142 20.924 16.738 20.924L16.738 20.924Q15.404 20.924 13.932 20.556L13.932 20.556Q12.782 20.326 11.862 19.912L11.862 19.912Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;request.js&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;li&gt;&lt;details open&gt;&lt;summary&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M22.073 4.900L22.073 4.900L12.148 4.900L12.148 3.950Q12.148 3.125 11.585 2.563Q11.023 2 10.198 2L10.198 2L0.048 2L0.048 22L23.948 22L23.948 6.850Q23.998 6.025 23.448 5.462Q22.898 4.900 22.073 4.900Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;views/
&lt;/span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M6.117 1.659L12.000 11.870L17.883 1.659L14.775 1.659L12.000 6.469L9.225 1.659L6.117 1.659ZM23.951 1.659L19.178 1.659L12.000 14.090L4.822 1.659L0.049 1.659L12.000 22.341L23.951 1.659Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;UserList.vue&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/details&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/starlight-file-tree&gt;
&lt;p&gt;这里的职责可以这样理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;utils/request.js&lt;/code&gt;：创建 axios 实例，统一配置基础地址、超时时间、拦截器。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;api/user.js&lt;/code&gt;：按业务模块封装用户相关接口。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;views/UserList.vue&lt;/code&gt;：页面组件，只关心调用哪个接口，不关心底层请求细节。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;封装-axios-实例&quot;&gt;封装 axios 实例&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;在 &lt;code dir=&quot;auto&quot;&gt;src/utils/request.js&lt;/code&gt; 中写入：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; axios &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 接口服务器地址&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 后续接口只需要写 /user、/login 这类路径&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;baseURL: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;http://localhost:8088&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 超时时间，单位是毫秒&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timeout: &lt;/span&gt;&lt;span&gt;10000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 请求拦截器：请求发出前会先经过这里&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;interceptors&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// 如果后续有 token，可以在这里统一添加&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// config.headers.Authorization = `Bearer ${token}`;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 响应拦截器：后端响应后会先经过这里&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;interceptors&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// 这里先直接返回 response&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// 如果后端有统一结构，也可以只返回 response.data&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// 可以在这里统一处理错误提示、登录过期等问题&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这样封装后，页面里就不需要每次都写完整后端地址了。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;封装接口方法&quot;&gt;封装接口方法&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;在 &lt;code dir=&quot;auto&quot;&gt;src/api/user.js&lt;/code&gt; 中写：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; request &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;../utils/request&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getUserList&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// axios 里字段名是 method，不是 methods&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;method: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// 会和 baseURL 拼接成 http://localhost:8088/user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;url: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/user&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// GET 请求参数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这里要注意一个细节：axios 配置里的请求方式字段是 &lt;code dir=&quot;auto&quot;&gt;method&lt;/code&gt;，不是 &lt;code dir=&quot;auto&quot;&gt;methods&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;在-vue-页面中调用&quot;&gt;在 Vue 页面中调用&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;在页面组件中引入接口方法：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { getUserList } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;../api/user&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tableData: [],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;methods: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;loadUsers&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;getUserList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// 如果响应拦截器返回的是完整 response，就从 response.data 取数据&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tableData&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果希望页面创建时自动请求，可以在 &lt;code dir=&quot;auto&quot;&gt;created&lt;/code&gt; 生命周期中调用：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { getUserList } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;../api/user&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tableData: [],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;created&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// 页面创建时加载数据&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;loadUsers&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;methods: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;loadUsers&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;getUserList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tableData&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果要在模板中展示：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v-for&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;user in tableData&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;user.id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{{ user.name }}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;实际字段名要根据后端返回数据决定。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;跨域问题是什么&quot;&gt;跨域问题是什么&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;前端请求后端时，如果浏览器控制台出现类似错误：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Access to XMLHttpRequest at &apos;http://localhost:8088/user&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from origin &apos;http://localhost:8080&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;has been blocked by CORS policy&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这就是跨域问题。&lt;/p&gt;
&lt;p&gt;它不是 axios 的问题，也不是 Vue 的问题，而是浏览器的同源策略在生效。&lt;/p&gt;
&lt;p&gt;同源要求三者完全一致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;协议相同，例如都是 &lt;code dir=&quot;auto&quot;&gt;http&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;域名相同，例如都是 &lt;code dir=&quot;auto&quot;&gt;localhost&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;端口相同，例如都是 &lt;code dir=&quot;auto&quot;&gt;8080&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面两个地址端口不同，所以不是同源：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;前端：http://localhost:8080&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;后端：http://localhost:8088&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;浏览器会拦截前端 JavaScript 对后端的请求，除非后端明确允许这个来源访问。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;后端使用-crossorigin&quot;&gt;后端使用 CrossOrigin&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果后端是 Spring Boot，可以在 Controller 上添加 &lt;code dir=&quot;auto&quot;&gt;@CrossOrigin&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;org.springframework.web.bind.annotation.CrossOrigin&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;org.springframework.web.bind.annotation.GetMapping&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;org.springframework.web.bind.annotation.RestController&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;RestController&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;CrossOrigin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserController&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;GetMapping&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/user&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;listUsers&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;userService&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;listUsers&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这样写表示这个 Controller 允许跨域访问。&lt;/p&gt;
&lt;p&gt;如果想限制来源，可以写得更明确：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;CrossOrigin&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;origins&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;http://localhost:8080&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;RestController&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserController&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这比完全放开更安全。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;全局跨域配置&quot;&gt;全局跨域配置&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果接口很多，不想每个 Controller 都写 &lt;code dir=&quot;auto&quot;&gt;@CrossOrigin&lt;/code&gt;，也可以做全局配置。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;org.springframework.web.servlet.config.annotation.CorsRegistry&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;org.springframework.web.servlet.config.annotation.WebMvcConfigurer&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Configuration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WebConfig&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WebMvcConfigurer&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;addCorsMappings&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CorsRegistry&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;registry&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;// 哪些接口路径允许跨域&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addMapping&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;// 允许哪个前端地址访问&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allowedOrigins&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;http://localhost:8080&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;// 允许哪些请求方法&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allowedMethods&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;PUT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DELETE&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;OPTIONS&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;// 允许携带哪些请求头&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allowedHeaders&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;开发阶段可以先这样配置。正式环境中，&lt;code dir=&quot;auto&quot;&gt;allowedOrigins&lt;/code&gt; 不建议随便写成 &lt;code dir=&quot;auto&quot;&gt;*&lt;/code&gt;，最好明确填写真实前端域名。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;前端代理方案&quot;&gt;前端代理方案&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;开发环境也可以通过前端代理解决跨域。&lt;/p&gt;
&lt;p&gt;如果使用 Vue CLI，可以在 &lt;code dir=&quot;auto&quot;&gt;vue.config.js&lt;/code&gt; 中配置：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exports&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;devServer: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;proxy: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/api&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// 后端服务地址&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;target: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;http://localhost:8088&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// 是否改变请求来源&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;changeOrigin: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// 把 /api/user 重写成 /user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pathRewrite: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;^/api&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这时 axios 的基础地址可以改成：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;baseURL: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/api&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;请求：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;method: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;url: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/user&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;浏览器实际请求的是前端开发服务器：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;http://localhost:8080/api/user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;再由前端开发服务器转发给后端：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;http://localhost:8088/user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这样浏览器看到的是同源请求，就不会触发跨域限制。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;常见问题&quot;&gt;常见问题&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;后端能访问前端却报跨域&quot;&gt;后端能访问，前端却报跨域&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;浏览器跨域是浏览器限制。你用 Postman、Apifox、curl 能请求成功，不代表浏览器里也能请求成功。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;axios-报错是不是后端没返回数据&quot;&gt;axios 报错是不是后端没返回数据&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;不一定。跨域错误时，请求可能已经被浏览器拦截，前端拿不到正常响应。要先看浏览器控制台的 Network 和 Console。&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;请求写对了但-this-取不到&quot;&gt;请求写对了，但 this 取不到&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;如果使用普通函数，&lt;code dir=&quot;auto&quot;&gt;this&lt;/code&gt; 可能发生变化。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;getUserList&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 这里的 this 不一定是 Vue 组件实例&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tableData&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;可以改成箭头函数：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;getUserList&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 箭头函数不会重新绑定 this&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tableData&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;或者直接使用 &lt;code dir=&quot;auto&quot;&gt;async / await&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Vue 请求后端接口时，可以按这个顺序处理：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 axios 先完成一次最简单的请求。&lt;/li&gt;
&lt;li&gt;确认后端接口能正常返回数据。&lt;/li&gt;
&lt;li&gt;如果浏览器报跨域，让后端配置 CORS，或者在开发环境使用代理。&lt;/li&gt;
&lt;li&gt;当接口变多后，把 axios 封装成统一的 request 模块。&lt;/li&gt;
&lt;li&gt;按业务拆分 API 文件，页面只调用方法，不直接拼接口地址。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这篇旧文当年只是为了解决“前端怎么拿到后端数据”这个问题。现在重新整理后，它更像是一条前后端分离开发的入门路径：先跑通，再封装，最后处理跨域和工程结构。&lt;/p&gt;
&lt;p&gt;原文记录：&lt;a href=&quot;https://www.cnblogs.com/xiaoxiblog/p/17405514.html&quot;&gt;vue请求后端数据和跨域问题&lt;/a&gt;&lt;/p&gt;</content:encoded><category>Vue</category><category>前端</category><category>axios</category></item><item><title>ServletConfig 接口介绍</title><link>https://blog.veyliss.top/blog/servlet_config_01/</link><guid isPermaLink="true">https://blog.veyliss.top/blog/servlet_config_01/</guid><pubDate>Wed, 08 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这篇文章迁移自我早年写在博客园的一篇记录。当时主要是在学习 Servlet 初始化参数：Servlet 容器初始化 Servlet 时，会创建一个 &lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 对象，并把它交给当前 Servlet 使用。&lt;/p&gt;
&lt;p&gt;简单说，&lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 适合保存“只属于某一个 Servlet 的配置”。比如某个 Servlet 自己需要的用户名、开关、路径、默认参数等。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;servletconfig-是什么&quot;&gt;ServletConfig 是什么&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;当 Web 容器创建并初始化 Servlet 时，会为这个 Servlet 准备一个 &lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 对象。这个对象里包含当前 Servlet 的初始化信息，也可以通过它拿到整个 Web 应用的 &lt;code dir=&quot;auto&quot;&gt;ServletContext&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;需要记住两点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个 Web 应用里可以有多个 Servlet，也就可以有多个 &lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;一个 Servlet 只对应一个 &lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 对象，所以 Servlet 初始化参数默认只对当前 Servlet 有效。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果把 Web 应用理解成一个项目，那么：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ServletContext&lt;/code&gt; 更像“整个项目的全局上下文”。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 更像“某一个 Servlet 自己的配置说明”。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;常用方法&quot;&gt;常用方法&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 常用方法不多，重点是下面这几个：&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;&lt;code dir=&quot;auto&quot;&gt;String getInitParameter(String name)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;根据参数名获取当前 Servlet 的初始化参数值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Enumeration&amp;#x3C;String&gt; getInitParameterNames()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;获取当前 Servlet 所有初始化参数名&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ServletContext getServletContext()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;获取当前 Web 应用的 &lt;code dir=&quot;auto&quot;&gt;ServletContext&lt;/code&gt; 对象&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;String getServletName()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;获取当前 Servlet 名称，也就是 &lt;code dir=&quot;auto&quot;&gt;web.xml&lt;/code&gt; 中 &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;servlet-name&gt;&lt;/code&gt; 的值&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这里最常用的是 &lt;code dir=&quot;auto&quot;&gt;getInitParameter()&lt;/code&gt; 和 &lt;code dir=&quot;auto&quot;&gt;getInitParameterNames()&lt;/code&gt;。前者适合读取单个配置，后者适合遍历所有配置。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;先分清两类参数&quot;&gt;先分清两类参数&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Servlet 里有两类参数很容易混在一起：&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;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;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;context-param&gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ServletContext&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;getServletContext().getInitParameter()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;整个 Web 应用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;servlet&gt;&lt;/code&gt; 里的 &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;init-param&gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;getServletConfig().getInitParameter()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前 Servlet&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;也就是说，&lt;code dir=&quot;auto&quot;&gt;context-param&lt;/code&gt; 不是当前 Servlet 的私有配置，它属于整个 Web 应用。&lt;code dir=&quot;auto&quot;&gt;ServletConfig#getServletContext()&lt;/code&gt; 只是让你可以从当前 Servlet 拿到全局上下文，并不代表这些全局参数属于 &lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这个区别非常重要。很多初学 Servlet 的时候，会把“通过 &lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 拿到 &lt;code dir=&quot;auto&quot;&gt;ServletContext&lt;/code&gt;，再读取全局参数”误认为是在读取 Servlet 自己的初始化参数。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;使用-webxml-配置全局参数&quot;&gt;使用 web.xml 配置全局参数&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;先看全局参数的写法。下面的 &lt;code dir=&quot;auto&quot;&gt;admin-email&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;admin-name&lt;/code&gt;、&lt;code dir=&quot;auto&quot;&gt;admin-password&lt;/code&gt; 都配置在 &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;context-param&gt;&lt;/code&gt; 中，因此它们属于整个 Web 应用。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;web.xml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;?&lt;/span&gt;&lt;span&gt;xml&lt;/span&gt;&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;UTF-8&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;?&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;web-app&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;xmlns&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;http://xmlns.jcp.org/xml/ns/javaee&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;xmlns:xsi&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;http://www.w3.org/2001/XMLSchema-instance&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;xsi:schemaLocation&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;version&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;4.0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;servlet&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;servlet-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Servlet&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;servlet-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;servlet-class&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;main.java.com.Servlet&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;servlet-class&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;servlet&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;context-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;admin-email&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;123456@qq.com&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;context-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;context-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;admin-name&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;context-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;context-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;admin-password&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;123456&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;context-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;servlet-mapping&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;servlet-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Servlet&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;servlet-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;url-pattern&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;/Servlet&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;url-pattern&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;servlet-mapping&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;web-app&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;读取这些全局参数时，需要先拿到 &lt;code dir=&quot;auto&quot;&gt;ServletContext&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Servlet.java&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main.java.com&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.ServletConfig&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.ServletContext&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.ServletException&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.annotation.WebServlet&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.http.HttpServlet&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.http.HttpServletRequest&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.http.HttpServletResponse&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;java.io.IOException&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;WebServlet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Servlet&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/Servlet&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Servlet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HttpServlet&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;protected&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;doGet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;HttpServletRequest&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;HttpServletResponse&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;throws&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ServletException&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;IOException&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setContentType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;text/plain;charset=UTF-8&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;ServletConfig&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getServletConfig&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;ServletContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;servletContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getServletContext&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;adminEmail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;servletContext&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;admin-email&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;adminName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;servletContext&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;admin-name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;password&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;servletContext&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;admin-password&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getWriter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;admin-email: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; adminEmail&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getWriter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;admin-name: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; adminName&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getWriter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;admin-password: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; password&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;protected&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;doPost&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;HttpServletRequest&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;HttpServletResponse&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;throws&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ServletException&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;IOException&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;doGet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request, response&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这段代码里虽然先调用了 &lt;code dir=&quot;auto&quot;&gt;getServletConfig()&lt;/code&gt;，但真正读取参数的是 &lt;code dir=&quot;auto&quot;&gt;servletContext.getInitParameter()&lt;/code&gt;。所以它读取的是全局初始化参数。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;使用-webxml-配置当前-servlet-参数&quot;&gt;使用 web.xml 配置当前 Servlet 参数&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果希望参数只属于当前 Servlet，就应该把参数写到 &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;servlet&gt;&lt;/code&gt; 里的 &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;init-param&gt;&lt;/code&gt; 中。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;web.xml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;servlet&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;servlet-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;MyServlet&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;servlet-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;servlet-class&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;java.com.MyServlet&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;servlet-class&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;init-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;init-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;init-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-name&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;xiaoxi&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;param-value&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;init-param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;servlet&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这种参数才是 &lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 最典型的使用场景。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;MyServlet.java&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ServletConfig&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getServletConfig&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果另一个 Servlet 也想使用同名参数，需要在另一个 Servlet 的配置里重新声明。Servlet 私有参数不会自动共享。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;使用注解配置初始化参数&quot;&gt;使用注解配置初始化参数&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;除了 &lt;code dir=&quot;auto&quot;&gt;web.xml&lt;/code&gt;，也可以直接使用 &lt;code dir=&quot;auto&quot;&gt;@WebServlet&lt;/code&gt; 和 &lt;code dir=&quot;auto&quot;&gt;@WebInitParam&lt;/code&gt; 配置当前 Servlet 的初始化参数。&lt;/p&gt;
&lt;p&gt;这种方式更适合简单项目或示例代码，因为配置和 Servlet 类写在一起，阅读起来更直观。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;HelloServlet.java&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main.java.com&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.ServletConfig&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.ServletException&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.annotation.WebInitParam&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.annotation.WebServlet&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.http.HttpServlet&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.http.HttpServletRequest&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.http.HttpServletResponse&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;java.io.IOException&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;java.io.PrintWriter&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;java.util.Enumeration&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;WebServlet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;helloServlet&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/helloServlet&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;initParams&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;WebInitParam&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;测试&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;WebInitParam&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;123456&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HelloServlet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HttpServlet&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;protected&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;doGet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;HttpServletRequest&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;HttpServletResponse&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;throws&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ServletException&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;IOException&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setContentType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;text/html;charset=UTF-8&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;ServletConfig&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getServletConfig&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;servletName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getServletName&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;Enumeration&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;initParameterNames&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitParameterNames&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;PrintWriter&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;writer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getWriter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;writer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;servletName: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; servletName &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;#x3C;br/&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;initParameterNames&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;hasMoreElements&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initParamName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initParameterNames&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;nextElement&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initParamValue&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;initParamName&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;writer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;initParamName &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; initParamValue &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;#x3C;br/&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;writer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;close&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;protected&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;doPost&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;HttpServletRequest&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;HttpServletResponse&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;throws&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ServletException&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;IOException&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;doGet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request, response&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这里的 &lt;code dir=&quot;auto&quot;&gt;initParams&lt;/code&gt; 配置只对 &lt;code dir=&quot;auto&quot;&gt;HelloServlet&lt;/code&gt; 生效。别的 Servlet 不能直接通过自己的 &lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 读取这些参数。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;webxml-和注解怎么选&quot;&gt;web.xml 和注解怎么选&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;如果项目很小，或者只是练习 Servlet，使用注解会比较方便。&lt;/p&gt;
&lt;p&gt;如果项目配置比较多，或者需要把代码和配置分离，使用 &lt;code dir=&quot;auto&quot;&gt;web.xml&lt;/code&gt; 会更清晰。尤其是早期 Java Web 项目里，很多 Servlet、Filter、Listener 都会统一写在 &lt;code dir=&quot;auto&quot;&gt;web.xml&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;可以简单按下面的方式判断：&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;示例代码、学习项目&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;@WebServlet&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;配置较少的小项目&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;@WebServlet&lt;/code&gt; 或 &lt;code dir=&quot;auto&quot;&gt;web.xml&lt;/code&gt; 都可以&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;多个 Servlet 需要统一管理&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;web.xml&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;希望参数不写死在 Java 类里&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;web.xml&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;两种方式本质上都是告诉 Servlet 容器：这个 Servlet 叫什么、映射到哪个路径、初始化时带哪些参数。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;常见注意点&quot;&gt;常见注意点&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;第一，&lt;code dir=&quot;auto&quot;&gt;context-param&lt;/code&gt; 和 &lt;code dir=&quot;auto&quot;&gt;init-param&lt;/code&gt; 不要混用。&lt;/p&gt;
&lt;p&gt;如果一个参数是全站通用的，比如站点名称、管理员邮箱、上传目录，可以放在 &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;context-param&gt;&lt;/code&gt; 中。如果一个参数只服务于某个 Servlet，就放在对应 Servlet 的 &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;init-param&gt;&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;第二，读取参数时要找对对象。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 读取当前 Servlet 的初始化参数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;getServletConfig&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 读取整个 Web 应用的全局参数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;getServletContext&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;admin-email&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;第三，输出中文时记得设置响应编码。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setContentType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;text/html;charset=UTF-8&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果不设置编码，浏览器里可能会出现中文乱码。&lt;/p&gt;
&lt;p&gt;第四，注意包名差异。&lt;/p&gt;
&lt;p&gt;早期 Servlet 项目常见包名是 &lt;code dir=&quot;auto&quot;&gt;javax.servlet&lt;/code&gt;。如果使用的是 Tomcat 10、Spring Boot 3 或 Jakarta EE 新版本，包名会变成 &lt;code dir=&quot;auto&quot;&gt;jakarta.servlet&lt;/code&gt;。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 旧版本常见写法&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;javax.servlet.ServletConfig&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 新版本常见写法&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;jakarta.servlet.ServletConfig&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;学习旧项目或迁移项目时，这个差异很常见。&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 的核心作用，是保存并读取当前 Servlet 的初始化参数。&lt;/p&gt;
&lt;p&gt;需要特别分清楚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ServletConfig&lt;/code&gt; 面向当前 Servlet。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ServletContext&lt;/code&gt; 面向整个 Web 应用。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;init-param&gt;&lt;/code&gt; 是 Servlet 私有配置。&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;context-param&gt;&lt;/code&gt; 是 Web 应用全局配置。&lt;/li&gt;
&lt;li&gt;注解里的 &lt;code dir=&quot;auto&quot;&gt;initParams&lt;/code&gt; 只对当前 Servlet 生效。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把这几个概念理顺之后，Servlet 初始化参数就不难了。真正容易出错的地方，往往不是 API 本身，而是没有分清“这个配置到底属于谁”。&lt;/p&gt;
&lt;p&gt;原文记录：&lt;a href=&quot;https://www.cnblogs.com/xiaoxiblog/p/17195823.html&quot;&gt;ServletConfig接口介绍&lt;/a&gt;&lt;/p&gt;</content:encoded><category>Java</category><category>Servlet</category><category>后端</category></item></channel></rss>