<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>Resmic's | BLOG</title><link>https://blog.resmic.cn/</link><description>个人博客</description><item><title>PHP 8.3 新特性实战：从 Readonly 类到 Fibers 协程的完整升级指南</title><link>https://blog.resmic.cn/post/230.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260406080519177543391914371.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;为什么要迁移到 PHP 8.3？&lt;/h2&gt;&lt;p&gt;PHP 8.3 于 2023 年 11 月正式发布，距今已成为生产环境中的主流版本。相比 PHP 7.x，整个 PHP 8.x 系列带来了质的飞跃：JIT 编译器、枚举类型、Fibers 协程、readonly 属性等一系列重磅特性，让 PHP 的开发体验和运行性能都上了一个台阶。&lt;/p&gt;&lt;p&gt;如果你的项目还运行在 PHP 7.4 或 PHP 8.0/8.1，这篇文章将带你系统了解 PHP 8.3 的核心新特性，并通过实际代码演示如何在项目中落地应用，让你看完就能动手升级。&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;PHP 7.4 已于 2022 年 11 月停止安全支持&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;PHP 8.0 已于 2023 年 11 月停止安全支持&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;PHP 8.3 支持到 2026 年 12 月，是当前最推荐的版本&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;特性一：Readonly 类（Readonly Classes）&lt;/h2&gt;&lt;p&gt;PHP 8.1 引入了 readonly 属性，8.2 将其升级为 Readonly 类。在 PHP 8.2+ 中，你可以直接用 &lt;code&gt;readonly&lt;/code&gt; 修饰整个类，使所有属性自动成为只读的，非常适合用于值对象（Value Object）和 DTO 场景。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

//&amp;nbsp;PHP&amp;nbsp;8.1&amp;nbsp;的方式：每个属性都要加&amp;nbsp;readonly
class&amp;nbsp;UserDTO_Old&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;int&amp;nbsp;$id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$email,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{}
}

//&amp;nbsp;PHP&amp;nbsp;8.2+&amp;nbsp;的方式：直接修饰类
readonly&amp;nbsp;class&amp;nbsp;UserDTO&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;$id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;string&amp;nbsp;$name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;string&amp;nbsp;$email,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{}
}

$user&amp;nbsp;=&amp;nbsp;new&amp;nbsp;UserDTO(id:&amp;nbsp;1,&amp;nbsp;name:&amp;nbsp;&amp;#39;张三&amp;#39;,&amp;nbsp;email:&amp;nbsp;&amp;#39;zhangsan@example.com&amp;#39;);
echo&amp;nbsp;$user-&amp;gt;name;&amp;nbsp;//&amp;nbsp;张三

//&amp;nbsp;尝试修改会抛出错误
$user-&amp;gt;name&amp;nbsp;=&amp;nbsp;&amp;#39;李四&amp;#39;;&amp;nbsp;//&amp;nbsp;Error:&amp;nbsp;Cannot&amp;nbsp;modify&amp;nbsp;readonly&amp;nbsp;property&lt;/pre&gt;&lt;p&gt;⚠️ &lt;strong&gt;踩坑记录：&lt;/strong&gt; readonly 类不支持非类型化属性，所有属性都必须有类型声明。另外，readonly 类不能被继承为非 readonly 类，如果你的 DTO 需要继承扩展，要提前规划好类层级结构。&lt;/p&gt;&lt;h2&gt;特性二：枚举类型（Enums）的最佳实践&lt;/h2&gt;&lt;p&gt;PHP 8.1 引入了原生枚举，解决了长期以来用类常量模拟枚举的痛点。PHP 8.3 对枚举做了进一步优化，下面来看几个实战场景。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

//&amp;nbsp;纯枚举（Pure&amp;nbsp;Enum）
enum&amp;nbsp;Status&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Active;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Inactive;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Pending;
}

//&amp;nbsp;支持值的枚举（Backed&amp;nbsp;Enum）-&amp;nbsp;推荐用于数据库存储
enum&amp;nbsp;OrderStatus:&amp;nbsp;string&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Created&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;created&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Paid&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;paid&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Shipped&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;shipped&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Done&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;done&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Canceled&amp;nbsp;=&amp;nbsp;&amp;#39;canceled&amp;#39;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;枚举可以有方法
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;label():&amp;nbsp;string&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;match($this)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self::Created&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;待支付&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self::Paid&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;已支付&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self::Shipped&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;已发货&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self::Done&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;已完成&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self::Canceled&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;已取消&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;枚举可以实现接口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;isTerminal():&amp;nbsp;bool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;in_array($this,&amp;nbsp;[self::Done,&amp;nbsp;self::Canceled]);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;使用示例
$status&amp;nbsp;=&amp;nbsp;OrderStatus::Paid;
echo&amp;nbsp;$status-&amp;gt;value;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;paid
echo&amp;nbsp;$status-&amp;gt;label();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;已支付
echo&amp;nbsp;$status-&amp;gt;isTerminal()&amp;nbsp;?&amp;nbsp;&amp;#39;终态&amp;#39;&amp;nbsp;:&amp;nbsp;&amp;#39;非终态&amp;#39;;&amp;nbsp;//&amp;nbsp;非终态

//&amp;nbsp;从数据库值转换
$fromDb&amp;nbsp;=&amp;nbsp;OrderStatus::from(&amp;#39;shipped&amp;#39;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;严格转换，找不到抛异常
$fromDb2&amp;nbsp;=&amp;nbsp;OrderStatus::tryFrom(&amp;#39;unknown&amp;#39;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;安全转换，找不到返回&amp;nbsp;null

//&amp;nbsp;枚举用于类型约束
function&amp;nbsp;updateOrderStatus(int&amp;nbsp;$orderId,&amp;nbsp;OrderStatus&amp;nbsp;$newStatus):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;编译时就能检查类型，再也不怕传入不合法的状态值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;...
}&lt;/pre&gt;&lt;p&gt;在 Laravel/Symfony 等框架中，枚举可以直接用于路由参数类型绑定和 Eloquent 属性转换（cast），极大简化了状态机的实现。&lt;/p&gt;&lt;h2&gt;特性三：Fibers 协程实战（PHP 8.1+）&lt;/h2&gt;&lt;p&gt;Fibers 是 PHP 8.1 引入的轻量级并发原语，可以理解为&amp;quot;可中断的函数&amp;quot;。它不是多线程，而是在单线程内实现协作式调度，是构建异步框架（如 ReactPHP、Amp v3）的底层基础。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

//&amp;nbsp;基础示例：理解&amp;nbsp;Fiber&amp;nbsp;的暂停和恢复
$fiber&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Fiber(function():&amp;nbsp;string&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;Fiber&amp;nbsp;开始执行\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$valueFromMain&amp;nbsp;=&amp;nbsp;Fiber::suspend(&amp;#39;第一次暂停，传给主程序的值&amp;#39;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;Fiber&amp;nbsp;恢复，主程序传来：{$valueFromMain}\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Fiber::suspend(&amp;#39;第二次暂停&amp;#39;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;Fiber&amp;nbsp;再次恢复\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;#39;最终返回值&amp;#39;;
});

//&amp;nbsp;启动&amp;nbsp;Fiber
$val1&amp;nbsp;=&amp;nbsp;$fiber-&amp;gt;start();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;输出&amp;nbsp;&amp;quot;Fiber&amp;nbsp;开始执行&amp;quot;，返回&amp;nbsp;&amp;quot;第一次暂停，传给主程序的值&amp;quot;
echo&amp;nbsp;&amp;quot;主程序收到：{$val1}\n&amp;quot;;

$val2&amp;nbsp;=&amp;nbsp;$fiber-&amp;gt;resume(&amp;#39;hello&amp;#39;);&amp;nbsp;//&amp;nbsp;Fiber&amp;nbsp;恢复执行，传入&amp;nbsp;&amp;quot;hello&amp;quot;
echo&amp;nbsp;&amp;quot;主程序收到：{$val2}\n&amp;quot;;

$fiber-&amp;gt;resume();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;最终恢复
echo&amp;nbsp;&amp;quot;Fiber&amp;nbsp;返回：&amp;quot;&amp;nbsp;.&amp;nbsp;$fiber-&amp;gt;getReturn()&amp;nbsp;.&amp;nbsp;&amp;quot;\n&amp;quot;;&lt;/pre&gt;&lt;p&gt;实战场景——用 Fiber 实现简单的并发 HTTP 请求（配合 curl_multi）：&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

function&amp;nbsp;fetchAsync(array&amp;nbsp;$urls):&amp;nbsp;array&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$multiHandle&amp;nbsp;=&amp;nbsp;curl_multi_init();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$fibers&amp;nbsp;=&amp;nbsp;[];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$results&amp;nbsp;=&amp;nbsp;[];

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foreach&amp;nbsp;($urls&amp;nbsp;as&amp;nbsp;$key&amp;nbsp;=&amp;gt;&amp;nbsp;$url)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$ch&amp;nbsp;=&amp;nbsp;curl_init($url);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;curl_setopt($ch,&amp;nbsp;CURLOPT_RETURNTRANSFER,&amp;nbsp;true);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;curl_setopt($ch,&amp;nbsp;CURLOPT_TIMEOUT,&amp;nbsp;10);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;curl_multi_add_handle($multiHandle,&amp;nbsp;$ch);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;每个&amp;nbsp;URL&amp;nbsp;对应一个&amp;nbsp;Fiber
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$fibers[$key]&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;fiber&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;new&amp;nbsp;Fiber(function()&amp;nbsp;use&amp;nbsp;($ch,&amp;nbsp;$multiHandle)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;等待请求完成
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;(curl_multi_exec($multiHandle,&amp;nbsp;$active)&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;$active)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Fiber::suspend();&amp;nbsp;//&amp;nbsp;让出控制权
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;curl_multi_getcontent($ch);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;ch&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;$ch,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;启动所有&amp;nbsp;Fiber
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foreach&amp;nbsp;($fibers&amp;nbsp;as&amp;nbsp;$key&amp;nbsp;=&amp;gt;&amp;nbsp;$item)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$item[&amp;#39;fiber&amp;#39;]-&amp;gt;start();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;调度循环
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;do&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$running&amp;nbsp;=&amp;nbsp;false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foreach&amp;nbsp;($fibers&amp;nbsp;as&amp;nbsp;$key&amp;nbsp;=&amp;gt;&amp;nbsp;$item)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!$item[&amp;#39;fiber&amp;#39;]-&amp;gt;isTerminated())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$item[&amp;#39;fiber&amp;#39;]-&amp;gt;resume();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$running&amp;nbsp;=&amp;nbsp;true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$results[$key]&amp;nbsp;=&amp;nbsp;$item[&amp;#39;fiber&amp;#39;]-&amp;gt;getReturn();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;while&amp;nbsp;($running);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;curl_multi_close($multiHandle);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;$results;
}

$responses&amp;nbsp;=&amp;nbsp;fetchAsync([
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;baidu&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;https://www.baidu.com&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;github&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;https://api.github.com&amp;#39;,
]);&lt;/pre&gt;&lt;p&gt;⚠️ &lt;strong&gt;踩坑记录：&lt;/strong&gt; Fiber 不是真正的并行，不能跨 Fiber 共享状态，不要在 Fiber 内捕获外部引用后修改，否则容易出现竞态问题。推荐使用 Amp v3 或 ReactPHP 这类成熟框架，它们已经帮你处理好了事件循环和调度逻辑。&lt;/p&gt;&lt;h2&gt;特性四：PHP 8.3 新增 — 类常量类型声明&lt;/h2&gt;&lt;p&gt;PHP 8.3 终于支持了类常量的类型声明！这解决了长期以来常量类型不明确的问题，让 IDE 的静态分析和代码补全更加精准。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

class&amp;nbsp;Config&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;新特性：类常量可以声明类型了
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;APP_NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;MyApp&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;APP_VERSION&amp;nbsp;=&amp;nbsp;&amp;#39;1.0.0&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;int&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MAX_RETRIES&amp;nbsp;=&amp;nbsp;3;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;float&amp;nbsp;&amp;nbsp;TAX_RATE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;0.08;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;array&amp;nbsp;&amp;nbsp;ALLOWED_IPS&amp;nbsp;=&amp;nbsp;[&amp;#39;127.0.0.1&amp;#39;,&amp;nbsp;&amp;#39;::1&amp;#39;];

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;接口中同样支持
}

interface&amp;nbsp;Configurable&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;DEFAULT_LOCALE&amp;nbsp;=&amp;nbsp;&amp;#39;zh_CN&amp;#39;;
}

//&amp;nbsp;类型不匹配会直接报错
class&amp;nbsp;BadConfig&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;int&amp;nbsp;WRONG_TYPE&amp;nbsp;=&amp;nbsp;&amp;#39;not_an_int&amp;#39;;&amp;nbsp;//&amp;nbsp;Fatal&amp;nbsp;error:&amp;nbsp;Cannot&amp;nbsp;use&amp;nbsp;string&amp;nbsp;as&amp;nbsp;int&amp;nbsp;constant&amp;nbsp;value
}&lt;/pre&gt;&lt;h2&gt;特性五：PHP 8.3 新增 — json_validate() 函数&lt;/h2&gt;&lt;p&gt;以前验证 JSON 字符串是否合法，需要先 &lt;code&gt;json_decode&lt;/code&gt; 再检查 &lt;code&gt;json_last_error()&lt;/code&gt;，既低效又啰嗦。PHP 8.3 直接内置了 &lt;code&gt;json_validate()&lt;/code&gt; 函数。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

//&amp;nbsp;旧方式（PHP&amp;nbsp;8.2&amp;nbsp;及以前）
function&amp;nbsp;isValidJson_Old(string&amp;nbsp;$json):&amp;nbsp;bool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;json_decode($json);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;json_last_error()&amp;nbsp;===&amp;nbsp;JSON_ERROR_NONE;
}

//&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;新方式&amp;nbsp;-&amp;nbsp;不解码，只验证，更高效
$validJson&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;{&amp;quot;name&amp;quot;:&amp;nbsp;&amp;quot;张三&amp;quot;,&amp;nbsp;&amp;quot;age&amp;quot;:&amp;nbsp;30}&amp;#39;;
$invalidJson&amp;nbsp;=&amp;nbsp;&amp;#39;{name:&amp;nbsp;&amp;quot;张三&amp;quot;}&amp;#39;;&amp;nbsp;//&amp;nbsp;键名没加引号

var_dump(json_validate($validJson));&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;bool(true)
var_dump(json_validate($invalidJson));&amp;nbsp;//&amp;nbsp;bool(false)

//&amp;nbsp;支持深度限制参数
var_dump(json_validate(&amp;#39;{&amp;quot;a&amp;quot;:{&amp;quot;b&amp;quot;:{&amp;quot;c&amp;quot;:1}}}&amp;#39;,&amp;nbsp;depth:&amp;nbsp;2));&amp;nbsp;//&amp;nbsp;bool(false)&amp;nbsp;超过深度限制
var_dump(json_validate(&amp;#39;{&amp;quot;a&amp;quot;:{&amp;quot;b&amp;quot;:{&amp;quot;c&amp;quot;:1}}}&amp;#39;,&amp;nbsp;depth:&amp;nbsp;5));&amp;nbsp;//&amp;nbsp;bool(true)

//&amp;nbsp;实际应用：API&amp;nbsp;请求体校验
function&amp;nbsp;handleWebhook(string&amp;nbsp;$rawBody):&amp;nbsp;array&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!json_validate($rawBody))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;\InvalidArgumentException(&amp;#39;Invalid&amp;nbsp;JSON&amp;nbsp;payload&amp;#39;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;json_decode($rawBody,&amp;nbsp;associative:&amp;nbsp;true);
}&lt;/pre&gt;&lt;h2&gt;实战：从 PHP 8.0 升级到 PHP 8.3 的完整步骤&lt;/h2&gt;&lt;p&gt;了解了新特性后，我们来看如何安全地进行版本迁移。以下是一个经过实战验证的升级流程：&lt;/p&gt;&lt;h3&gt;第一步：检测兼容性&lt;/h3&gt;&lt;pre&gt;#&amp;nbsp;安装&amp;nbsp;PHP&amp;nbsp;Compatibility&amp;nbsp;检查工具
composer&amp;nbsp;require&amp;nbsp;--dev&amp;nbsp;phpcompatibility/php-compatibility

#&amp;nbsp;配置&amp;nbsp;phpcs.xml
vendor/bin/phpcs&amp;nbsp;--config-set&amp;nbsp;installed_paths&amp;nbsp;vendor/phpcompatibility/php-compatibility

#&amp;nbsp;扫描整个项目，检测&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;不兼容的写法
vendor/bin/phpcs&amp;nbsp;--standard=PHPCompatibility&amp;nbsp;--runtime-set&amp;nbsp;testVersion&amp;nbsp;8.3&amp;nbsp;src/&lt;/pre&gt;&lt;h3&gt;第二步：常见破坏性变更处理&lt;/h3&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;动态属性废弃（PHP 8.2）：&lt;/strong&gt; 未在类中声明的属性赋值会触发废弃警告，PHP 9 将报错。需要将动态属性显式声明，或加 &lt;code&gt;#[AllowDynamicProperties]&lt;/code&gt; 属性注解。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;字符串与数字比较（PHP 8.0）：&lt;/strong&gt; &lt;code&gt;0 == &amp;quot;foo&amp;quot;&lt;/code&gt; 从 true 变成 false，检查所有类型转换逻辑。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;match 表达式无默认：&lt;/strong&gt; PHP 8 的 match 不匹配时抛 &lt;code&gt;UnhandledMatchError&lt;/code&gt;，注意加默认分支。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&amp;lt;?php

//&amp;nbsp;PHP&amp;nbsp;8.0&amp;nbsp;之前（会触发警告）
class&amp;nbsp;OldModel&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;setData(array&amp;nbsp;$data):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foreach&amp;nbsp;($data&amp;nbsp;as&amp;nbsp;$key&amp;nbsp;=&amp;gt;&amp;nbsp;$val)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;$key&amp;nbsp;=&amp;nbsp;$val;&amp;nbsp;//&amp;nbsp;动态赋值，PHP&amp;nbsp;8.2&amp;nbsp;警告，PHP&amp;nbsp;9&amp;nbsp;报错
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;PHP&amp;nbsp;8.2+&amp;nbsp;修复方式
#[\AllowDynamicProperties]
class&amp;nbsp;NewModel&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;或者改用数组存储动态数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;array&amp;nbsp;$attributes&amp;nbsp;=&amp;nbsp;[];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;setAttribute(string&amp;nbsp;$key,&amp;nbsp;mixed&amp;nbsp;$value):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;attributes[$key]&amp;nbsp;=&amp;nbsp;$value;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__get(string&amp;nbsp;$name):&amp;nbsp;mixed&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;$this-&amp;gt;attributes[$name]&amp;nbsp;??&amp;nbsp;null;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;第三步：Laravel/Symfony 框架升级注意&lt;/h3&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;Laravel 10.x 和 11.x 已完全支持 PHP 8.3，直接升级 PHP 版本即可&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Symfony 6.4 和 7.x 支持 PHP 8.3，检查各 Bundle 的兼容性&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;运行完整测试套件，重点关注类型相关的 deprecation 警告&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Docker 镜像从 &lt;code&gt;php:8.0-fpm&lt;/code&gt; 更换为 &lt;code&gt;php:8.3-fpm-alpine&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;第四步：性能测试对比&lt;/h3&gt;&lt;pre&gt;#&amp;nbsp;用&amp;nbsp;ab&amp;nbsp;做简单压测对比（升级前后各跑一次）
ab&amp;nbsp;-n&amp;nbsp;1000&amp;nbsp;-c&amp;nbsp;10&amp;nbsp;https://yoursite.com/api/heavy-endpoint

#&amp;nbsp;或用&amp;nbsp;wrk（更精准）
wrk&amp;nbsp;-t4&amp;nbsp;-c100&amp;nbsp;-d30s&amp;nbsp;https://yoursite.com/api/heavy-endpoint&lt;/pre&gt;&lt;p&gt;根据实测，同等配置下 PHP 8.3 比 PHP 7.4 平均性能提升 30-50%，比 PHP 8.0 也有 5-15% 的提升，JIT 对计算密集型任务效果最为显著。&lt;/p&gt;&lt;h2&gt;总结&lt;/h2&gt;&lt;p&gt;PHP 8.3 带来的改进是全方位的：从 Readonly 类到枚举类型，从 Fibers 协程到类常量类型声明，再到 &lt;code&gt;json_validate()&lt;/code&gt; 这样细心的 API 填补，都体现了 PHP 语言在工程化和现代化道路上的持续进步。&lt;/p&gt;&lt;p&gt;升级建议：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;新项目直接用 PHP 8.3，充分利用所有新特性&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;老项目先用 PHPCompatibility 扫描，逐步修复，配合完善的测试再切换&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;重点利用 readonly 类和枚举重构数据对象，代码会简洁很多&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;生产环境建议先在预发环境验证至少一周再全量切换&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;现在就开始你的 PHP 8.3 升级之旅吧，让代码更安全、更优雅、更高效！&lt;/p&gt;</description><pubDate>Mon, 06 Apr 2026 08:13:17 +0800</pubDate></item><item><title>MCP 工具开发实战：从零给 AI 接入任意 API 的完整指南</title><link>https://blog.resmic.cn/post/229.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260405080707177534762741405.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;MCP（Model Context Protocol）是 Anthropic 在 2024 年底发布的开放协议，旨在让 AI 助手能够安全、标准化地接入任意外部 API 和数据源。简单理解：它就像给 AI 装上了&amp;quot;插件系统&amp;quot;——你写一个 MCP Server，AI 就能调用你提供的工具，无论是查数据库、调用内部 API，还是操控本地文件系统，都能一把搞定。&lt;/p&gt;&lt;p&gt;本文以一个真实场景为例：&lt;strong&gt;为 AI 助手开发一个能查询公司内部项目状态的 MCP Server&lt;/strong&gt;，从零开始，覆盖协议原理、开发环境搭建、工具定义、鉴权处理、调试技巧到实际接入 Claude Desktop，让你看完就能动手。&lt;/p&gt;&lt;h2&gt;一、MCP 是什么？为什么你需要它&lt;/h2&gt;&lt;p&gt;在 MCP 出现之前，想让 AI 接入外部系统，每个平台都要单独实现&amp;quot;Function Calling&amp;quot;或&amp;quot;Tool Use&amp;quot;——OpenAI 有一套格式，Claude 有一套格式，各家 Agent 框架又各有不同。开发者苦不堪言，要么为每个平台单独维护集成代码，要么依赖某个框架的封装（但框架升级就可能崩）。&lt;/p&gt;&lt;p&gt;MCP 的目标是统一这个混乱局面。它定义了一套标准的 Client-Server 通信协议：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;MCP Server&lt;/strong&gt;：你开发的工具提供方，暴露一组可调用的&amp;quot;工具&amp;quot;（Tools）、可读取的&amp;quot;资源&amp;quot;（Resources）和可使用的&amp;quot;提示词&amp;quot;（Prompts）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;MCP Client&lt;/strong&gt;：集成在 AI 助手中的协议客户端，负责发现、调用 MCP Server 提供的能力&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;传输层&lt;/strong&gt;：支持 stdio（本地进程通信）和 HTTP+SSE（远程服务）两种方式&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;目前支持 MCP 的平台已经相当丰富：Claude Desktop、Cursor、Continue、Cline、以及各种基于 LangChain/LlamaIndex 构建的 Agent 框架。一次开发，处处可用。&lt;/p&gt;&lt;p&gt;回到我们的场景：公司有一套内部项目管理系统，提供 REST API，但 Claude Desktop 没法直接查它。通过 MCP，我们可以让 AI 直接回答&amp;quot;目前有哪些进行中的项目&amp;quot;、&amp;quot;张三负责哪些任务&amp;quot;这类问题，而不需要用户手动去系统里查再粘贴给 AI。&lt;/p&gt;&lt;h2&gt;二、开发环境搭建&lt;/h2&gt;&lt;p&gt;MCP SDK 目前官方支持 Python 和 TypeScript，本文使用 Python（更适合快速原型）。&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;推荐用&amp;nbsp;uv&amp;nbsp;管理&amp;nbsp;Python&amp;nbsp;环境（比&amp;nbsp;pip&amp;nbsp;+&amp;nbsp;venv&amp;nbsp;更快更干净）
curl&amp;nbsp;-LsSf&amp;nbsp;https://astral.sh/uv/install.sh&amp;nbsp;|&amp;nbsp;sh

#&amp;nbsp;创建项目
mkdir&amp;nbsp;mcp-project-server&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;cd&amp;nbsp;mcp-project-server
uv&amp;nbsp;init&amp;nbsp;--python&amp;nbsp;3.11
uv&amp;nbsp;add&amp;nbsp;mcp&amp;nbsp;httpx&amp;nbsp;python-dotenv

#&amp;nbsp;目录结构
.
├──&amp;nbsp;.env&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;存放&amp;nbsp;API&amp;nbsp;Token（不提交到&amp;nbsp;git）
├──&amp;nbsp;pyproject.toml
└──&amp;nbsp;src/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;server.py&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;MCP&amp;nbsp;Server&amp;nbsp;主文件&lt;/pre&gt;&lt;p&gt;关于 Python 版本：MCP SDK 要求 Python 3.10+，推荐 3.11 或 3.12，3.13 目前个别依赖还有兼容问题。&lt;/p&gt;&lt;p&gt;创建 &lt;code&gt;.env&lt;/code&gt; 文件，存放内部系统的 API 配置：&lt;/p&gt;&lt;pre&gt;PROJECT_API_BASE=https://internal.company.com/api/v2
PROJECT_API_TOKEN=your_secret_token_here&lt;/pre&gt;&lt;h2&gt;三、核心代码：定义你的第一个 MCP 工具&lt;/h2&gt;&lt;p&gt;MCP Server 的核心是定义&amp;quot;工具&amp;quot;——每个工具对应一个 AI 可以调用的操作，类似 OpenAI 的 Function Calling，但格式更标准化。&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;src/server.py
import&amp;nbsp;asyncio
import&amp;nbsp;os
from&amp;nbsp;dotenv&amp;nbsp;import&amp;nbsp;load_dotenv
import&amp;nbsp;httpx
from&amp;nbsp;mcp.server&amp;nbsp;import&amp;nbsp;Server
from&amp;nbsp;mcp.server.stdio&amp;nbsp;import&amp;nbsp;stdio_server
from&amp;nbsp;mcp&amp;nbsp;import&amp;nbsp;types

load_dotenv()

API_BASE&amp;nbsp;=&amp;nbsp;os.getenv(&amp;quot;PROJECT_API_BASE&amp;quot;)
API_TOKEN&amp;nbsp;=&amp;nbsp;os.getenv(&amp;quot;PROJECT_API_TOKEN&amp;quot;)

app&amp;nbsp;=&amp;nbsp;Server(&amp;quot;project-server&amp;quot;)

#&amp;nbsp;定义工具列表（AI&amp;nbsp;会根据这些描述决定什么时候调用哪个工具）
@app.list_tools()
async&amp;nbsp;def&amp;nbsp;list_tools()&amp;nbsp;-&amp;gt;&amp;nbsp;list[types.Tool]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;types.Tool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name=&amp;quot;list_projects&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description=&amp;quot;列出公司内部项目列表，可按状态筛选（进行中/已完成/暂停）&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inputSchema={
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;type&amp;quot;:&amp;nbsp;&amp;quot;object&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;properties&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;status&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;type&amp;quot;:&amp;nbsp;&amp;quot;string&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;enum&amp;quot;:&amp;nbsp;[&amp;quot;active&amp;quot;,&amp;nbsp;&amp;quot;completed&amp;quot;,&amp;nbsp;&amp;quot;paused&amp;quot;,&amp;nbsp;&amp;quot;all&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;description&amp;quot;:&amp;nbsp;&amp;quot;项目状态筛选，默认为&amp;nbsp;all&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;default&amp;quot;:&amp;nbsp;&amp;quot;all&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;limit&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;type&amp;quot;:&amp;nbsp;&amp;quot;integer&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;description&amp;quot;:&amp;nbsp;&amp;quot;返回数量上限，默认&amp;nbsp;20&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;default&amp;quot;:&amp;nbsp;20
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;types.Tool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name=&amp;quot;get_project_detail&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description=&amp;quot;获取指定项目的详细信息，包括任务列表、负责人、进度等&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inputSchema={
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;type&amp;quot;:&amp;nbsp;&amp;quot;object&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;properties&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;project_id&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;type&amp;quot;:&amp;nbsp;&amp;quot;string&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;description&amp;quot;:&amp;nbsp;&amp;quot;项目&amp;nbsp;ID&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;required&amp;quot;:&amp;nbsp;[&amp;quot;project_id&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;types.Tool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name=&amp;quot;search_tasks_by_assignee&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description=&amp;quot;查询某个员工负责的所有任务&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inputSchema={
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;type&amp;quot;:&amp;nbsp;&amp;quot;object&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;properties&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;assignee&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;type&amp;quot;:&amp;nbsp;&amp;quot;string&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;description&amp;quot;:&amp;nbsp;&amp;quot;员工姓名或工号&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;status&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;type&amp;quot;:&amp;nbsp;&amp;quot;string&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;enum&amp;quot;:&amp;nbsp;[&amp;quot;todo&amp;quot;,&amp;nbsp;&amp;quot;in_progress&amp;quot;,&amp;nbsp;&amp;quot;done&amp;quot;,&amp;nbsp;&amp;quot;all&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;default&amp;quot;:&amp;nbsp;&amp;quot;all&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;required&amp;quot;:&amp;nbsp;[&amp;quot;assignee&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]&lt;/pre&gt;&lt;p&gt;工具描述（&lt;code&gt;description&lt;/code&gt;）非常关键——AI 就是根据这段文字来判断什么时候应该调用这个工具的。描述要清晰、具体，说清楚这个工具能做什么、适合什么场景。模糊的描述会导致 AI 乱调或不调。&lt;/p&gt;&lt;h2&gt;四、实现工具逻辑：对接真实 API&lt;/h2&gt;&lt;p&gt;工具定义好之后，需要实现实际的调用逻辑。当 AI 决定使用某个工具时，MCP 框架会触发 &lt;code&gt;call_tool&lt;/code&gt; 处理器：&lt;/p&gt;&lt;pre&gt;@app.call_tool()
async&amp;nbsp;def&amp;nbsp;call_tool(name:&amp;nbsp;str,&amp;nbsp;arguments:&amp;nbsp;dict)&amp;nbsp;-&amp;gt;&amp;nbsp;list[types.TextContent]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Authorization&amp;quot;:&amp;nbsp;f&amp;quot;Bearer&amp;nbsp;{API_TOKEN}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Content-Type&amp;quot;:&amp;nbsp;&amp;quot;application/json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;async&amp;nbsp;with&amp;nbsp;httpx.AsyncClient(timeout=10.0)&amp;nbsp;as&amp;nbsp;client:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;name&amp;nbsp;==&amp;nbsp;&amp;quot;list_projects&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status&amp;nbsp;=&amp;nbsp;arguments.get(&amp;quot;status&amp;quot;,&amp;nbsp;&amp;quot;all&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;limit&amp;nbsp;=&amp;nbsp;arguments.get(&amp;quot;limit&amp;quot;,&amp;nbsp;20)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{&amp;quot;limit&amp;quot;:&amp;nbsp;limit}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;status&amp;nbsp;!=&amp;nbsp;&amp;quot;all&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params[&amp;quot;status&amp;quot;]&amp;nbsp;=&amp;nbsp;status
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;await&amp;nbsp;client.get(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;{API_BASE}/projects&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers=headers,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params=params
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp.raise_for_status()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;格式化输出——让&amp;nbsp;AI&amp;nbsp;更容易理解
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;projects&amp;nbsp;=&amp;nbsp;data.get(&amp;quot;items&amp;quot;,&amp;nbsp;[])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;projects:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[types.TextContent(type=&amp;quot;text&amp;quot;,&amp;nbsp;text=&amp;quot;没有找到符合条件的项目&amp;quot;)]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lines&amp;nbsp;=&amp;nbsp;[f&amp;quot;共找到&amp;nbsp;{len(projects)}&amp;nbsp;个项目：\n&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;p&amp;nbsp;in&amp;nbsp;projects:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lines.append(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;-&amp;nbsp;[{p[&amp;#39;id&amp;#39;]}]&amp;nbsp;{p[&amp;#39;name&amp;#39;]}&amp;nbsp;|&amp;nbsp;状态:&amp;nbsp;{p[&amp;#39;status&amp;#39;]}&amp;nbsp;|&amp;nbsp;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;负责人:&amp;nbsp;{p.get(&amp;#39;owner&amp;#39;,&amp;nbsp;&amp;#39;未分配&amp;#39;)}&amp;nbsp;|&amp;nbsp;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;截止日期:&amp;nbsp;{p.get(&amp;#39;due_date&amp;#39;,&amp;nbsp;&amp;#39;未设置&amp;#39;)}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[types.TextContent(type=&amp;quot;text&amp;quot;,&amp;nbsp;text=&amp;quot;\n&amp;quot;.join(lines))]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elif&amp;nbsp;name&amp;nbsp;==&amp;nbsp;&amp;quot;get_project_detail&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;project_id&amp;nbsp;=&amp;nbsp;arguments[&amp;quot;project_id&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;await&amp;nbsp;client.get(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;{API_BASE}/projects/{project_id}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers=headers
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp.raise_for_status()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;p&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tasks&amp;nbsp;=&amp;nbsp;p.get(&amp;quot;tasks&amp;quot;,&amp;nbsp;[])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;task_summary&amp;nbsp;=&amp;nbsp;&amp;quot;\n&amp;quot;.join([
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;&amp;nbsp;&amp;nbsp;-&amp;nbsp;{t[&amp;#39;title&amp;#39;]}&amp;nbsp;[{t[&amp;#39;status&amp;#39;]}]&amp;nbsp;负责人:&amp;nbsp;{t.get(&amp;#39;assignee&amp;#39;,&amp;nbsp;&amp;#39;未分配&amp;#39;)}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;t&amp;nbsp;in&amp;nbsp;tasks[:10]&amp;nbsp;&amp;nbsp;#&amp;nbsp;最多显示10条
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;f&amp;quot;&amp;quot;&amp;quot;项目详情：{p[&amp;#39;name&amp;#39;]}&amp;nbsp;(ID:&amp;nbsp;{p[&amp;#39;id&amp;#39;]})
状态:&amp;nbsp;{p[&amp;#39;status&amp;#39;]}
描述:&amp;nbsp;{p.get(&amp;#39;description&amp;#39;,&amp;nbsp;&amp;#39;无&amp;#39;)}
开始日期:&amp;nbsp;{p.get(&amp;#39;start_date&amp;#39;,&amp;nbsp;&amp;#39;未设置&amp;#39;)}
截止日期:&amp;nbsp;{p.get(&amp;#39;due_date&amp;#39;,&amp;nbsp;&amp;#39;未设置&amp;#39;)}
项目负责人:&amp;nbsp;{p.get(&amp;#39;owner&amp;#39;,&amp;nbsp;&amp;#39;未分配&amp;#39;)}
完成进度:&amp;nbsp;{p.get(&amp;#39;progress&amp;#39;,&amp;nbsp;0)}%

任务列表&amp;nbsp;(共{len(tasks)}条，显示前10条):
{task_summary}&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[types.TextContent(type=&amp;quot;text&amp;quot;,&amp;nbsp;text=result)]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elif&amp;nbsp;name&amp;nbsp;==&amp;nbsp;&amp;quot;search_tasks_by_assignee&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;assignee&amp;nbsp;=&amp;nbsp;arguments[&amp;quot;assignee&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status&amp;nbsp;=&amp;nbsp;arguments.get(&amp;quot;status&amp;quot;,&amp;nbsp;&amp;quot;all&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{&amp;quot;assignee&amp;quot;:&amp;nbsp;assignee}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;status&amp;nbsp;!=&amp;nbsp;&amp;quot;all&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params[&amp;quot;status&amp;quot;]&amp;nbsp;=&amp;nbsp;status
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;await&amp;nbsp;client.get(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;{API_BASE}/tasks/search&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers=headers,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params=params
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp.raise_for_status()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tasks&amp;nbsp;=&amp;nbsp;data.get(&amp;quot;items&amp;quot;,&amp;nbsp;[])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;tasks:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[types.TextContent(type=&amp;quot;text&amp;quot;,&amp;nbsp;text=f&amp;quot;未找到&amp;nbsp;{assignee}&amp;nbsp;的任务&amp;quot;)]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lines&amp;nbsp;=&amp;nbsp;[f&amp;quot;{assignee}&amp;nbsp;共有&amp;nbsp;{len(tasks)}&amp;nbsp;个任务：\n&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;t&amp;nbsp;in&amp;nbsp;tasks:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lines.append(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;-&amp;nbsp;[{t[&amp;#39;project_name&amp;#39;]}]&amp;nbsp;{t[&amp;#39;title&amp;#39;]}&amp;nbsp;|&amp;nbsp;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;状态:&amp;nbsp;{t[&amp;#39;status&amp;#39;]}&amp;nbsp;|&amp;nbsp;截止:&amp;nbsp;{t.get(&amp;#39;due_date&amp;#39;,&amp;nbsp;&amp;#39;未设置&amp;#39;)}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[types.TextContent(type=&amp;quot;text&amp;quot;,&amp;nbsp;text=&amp;quot;\n&amp;quot;.join(lines))]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[types.TextContent(type=&amp;quot;text&amp;quot;,&amp;nbsp;text=f&amp;quot;未知工具:&amp;nbsp;{name}&amp;quot;)]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;httpx.HTTPStatusError&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[types.TextContent(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type=&amp;quot;text&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text=f&amp;quot;API&amp;nbsp;调用失败:&amp;nbsp;HTTP&amp;nbsp;{e.response.status_code}&amp;nbsp;-&amp;nbsp;{e.response.text[:200]}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;httpx.TimeoutException:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[types.TextContent(type=&amp;quot;text&amp;quot;,&amp;nbsp;text=&amp;quot;请求超时，内部系统可能暂时不可用&amp;quot;)]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[types.TextContent(type=&amp;quot;text&amp;quot;,&amp;nbsp;text=f&amp;quot;意外错误:&amp;nbsp;{str(e)}&amp;quot;)]


#&amp;nbsp;启动&amp;nbsp;Server
if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;asyncio.run(stdio_server(app))&lt;/pre&gt;&lt;p&gt;有几个踩坑点值得注意：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;超时必须设置&lt;/strong&gt;：内部 API 有时会卡住，不设超时会导致 MCP 连接假死&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;错误信息要对 AI 友好&lt;/strong&gt;：不要直接抛异常，返回清晰的文字说明，让 AI 能告知用户具体出了什么问题&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;输出格式要清晰&lt;/strong&gt;：AI 会把工具返回的文本直接作为上下文，格式越清晰，AI 理解越准确，回答越好&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;五、本地调试：不依赖任何 AI 客户端&lt;/h2&gt;&lt;p&gt;开发过程中频繁切换到 Claude Desktop 测试非常低效。MCP SDK 提供了命令行调试工具，可以直接在终端验证工具是否正常工作：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;方法一：使用&amp;nbsp;mcp&amp;nbsp;dev&amp;nbsp;工具（推荐）
uv&amp;nbsp;run&amp;nbsp;mcp&amp;nbsp;dev&amp;nbsp;src/server.py

#&amp;nbsp;这会启动一个交互式&amp;nbsp;Inspector，在浏览器中打开&amp;nbsp;http://localhost:5173
#&amp;nbsp;可以直接列出工具、手动填参数调用、查看返回结果&lt;/pre&gt;&lt;p&gt;如果没有图形界面，也可以用脚本直接测试：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;方法二：写测试脚本
#&amp;nbsp;test_server.py
import&amp;nbsp;asyncio
from&amp;nbsp;mcp&amp;nbsp;import&amp;nbsp;ClientSession,&amp;nbsp;StdioServerParameters
from&amp;nbsp;mcp.client.stdio&amp;nbsp;import&amp;nbsp;stdio_client

async&amp;nbsp;def&amp;nbsp;test():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_params&amp;nbsp;=&amp;nbsp;StdioServerParameters(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;command=&amp;quot;uv&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;args=[&amp;quot;run&amp;quot;,&amp;nbsp;&amp;quot;src/server.py&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env=None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;async&amp;nbsp;with&amp;nbsp;stdio_client(server_params)&amp;nbsp;as&amp;nbsp;(read,&amp;nbsp;write):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;async&amp;nbsp;with&amp;nbsp;ClientSession(read,&amp;nbsp;write)&amp;nbsp;as&amp;nbsp;session:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;初始化
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await&amp;nbsp;session.initialize()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;列出所有工具
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tools&amp;nbsp;=&amp;nbsp;await&amp;nbsp;session.list_tools()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;可用工具：&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;t&amp;nbsp;in&amp;nbsp;tools.tools:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;&amp;nbsp;&amp;nbsp;-&amp;nbsp;{t.name}:&amp;nbsp;{t.description}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;调用工具测试
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;await&amp;nbsp;session.call_tool(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;list_projects&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;arguments={&amp;quot;status&amp;quot;:&amp;nbsp;&amp;quot;active&amp;quot;,&amp;nbsp;&amp;quot;limit&amp;quot;:&amp;nbsp;5}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n调用结果：&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;content&amp;nbsp;in&amp;nbsp;result.content:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(content.text)

asyncio.run(test())&lt;/pre&gt;&lt;p&gt;调试阶段的常见问题：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Server 启动失败&lt;/strong&gt;：99% 是 &lt;code&gt;.env&lt;/code&gt; 没有正确加载，加一行 &lt;code&gt;print(os.getenv(&amp;quot;PROJECT_API_BASE&amp;quot;))&lt;/code&gt; 确认&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;工具列表为空&lt;/strong&gt;：检查 &lt;code&gt;@app.list_tools()&lt;/code&gt; 装饰器是否正确，函数签名是否匹配&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;工具调用报错但无堆栈&lt;/strong&gt;：MCP 会吞掉未捕获的异常，记得在 &lt;code&gt;call_tool&lt;/code&gt; 里加宽泛的 &lt;code&gt;try/except&lt;/code&gt; 并打印&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;六、接入 Claude Desktop：最后一步&lt;/h2&gt;&lt;p&gt;代码调通之后，接入 Claude Desktop 只需修改一个配置文件。找到 Claude Desktop 的配置文件：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;macOS: &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Windows: &lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;{
&amp;nbsp;&amp;nbsp;&amp;quot;mcpServers&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;project-server&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;command&amp;quot;:&amp;nbsp;&amp;quot;uv&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;args&amp;quot;:&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;run&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;--project&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;/path/to/mcp-project-server&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;python&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;src/server.py&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;env&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;PROJECT_API_BASE&amp;quot;:&amp;nbsp;&amp;quot;https://internal.company.com/api/v2&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;PROJECT_API_TOKEN&amp;quot;:&amp;nbsp;&amp;quot;your_secret_token_here&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;保存后完全退出并重启 Claude Desktop（注意是完全退出，不是最小化）。重启后在对话框左下角会出现工具图标，点击可以看到你注册的工具列表。&lt;/p&gt;&lt;p&gt;现在你可以直接问 Claude：&amp;quot;帮我看看目前有哪些进行中的项目，以及张三负责什么任务？&amp;quot;它会自动决定调用哪些工具，汇总结果给你。&lt;/p&gt;&lt;p&gt;几个配置的注意事项：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;用 uv run 而不是 python&lt;/strong&gt;：确保使用项目虚拟环境中的依赖，避免包版本冲突&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;env 中的变量会覆盖系统环境变量&lt;/strong&gt;：不同项目可以有不同的 Token，互不干扰&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;路径用绝对路径&lt;/strong&gt;：相对路径在 Claude Desktop 启动时可能解析错误&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;七、进阶：添加 Resources 和 Prompts&lt;/h2&gt;&lt;p&gt;除了 Tools，MCP 还支持 Resources（让 AI 读取数据，如文档、日志）和 Prompts（预定义的提示词模板）。在我们的项目中，可以加一个 Resource，让 AI 直接读取项目的 README：&lt;/p&gt;&lt;pre&gt;from&amp;nbsp;mcp&amp;nbsp;import&amp;nbsp;types
from&amp;nbsp;mcp.server&amp;nbsp;import&amp;nbsp;Server

@app.list_resources()
async&amp;nbsp;def&amp;nbsp;list_resources()&amp;nbsp;-&amp;gt;&amp;nbsp;list[types.Resource]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;types.Resource(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uri=&amp;quot;project://docs/workflow&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name=&amp;quot;项目工作流规范&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description=&amp;quot;公司内部项目管理流程和规范文档&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mimeType=&amp;quot;text/markdown&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]

@app.read_resource()
async&amp;nbsp;def&amp;nbsp;read_resource(uri:&amp;nbsp;str)&amp;nbsp;-&amp;gt;&amp;nbsp;str:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;uri&amp;nbsp;==&amp;nbsp;&amp;quot;project://docs/workflow&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;从内部&amp;nbsp;API&amp;nbsp;或文件读取
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;async&amp;nbsp;with&amp;nbsp;httpx.AsyncClient()&amp;nbsp;as&amp;nbsp;client:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;await&amp;nbsp;client.get(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;{API_BASE}/docs/workflow&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers={&amp;quot;Authorization&amp;quot;:&amp;nbsp;f&amp;quot;Bearer&amp;nbsp;{API_TOKEN}&amp;quot;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;resp.text
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raise&amp;nbsp;ValueError(f&amp;quot;未知资源:&amp;nbsp;{uri}&amp;quot;)&lt;/pre&gt;&lt;p&gt;Resources 和 Tools 的区别：Resources 是 AI &amp;quot;主动读取&amp;quot;的数据，Tools 是 AI &amp;quot;主动调用&amp;quot;的操作。前者适合静态或半静态的内容（文档、配置），后者适合需要参数的动态查询。&lt;/p&gt;&lt;h2&gt;八、踩坑总结与最佳实践&lt;/h2&gt;&lt;p&gt;经过实际项目开发，总结了以下关键经验：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;工具数量不要太多&lt;/strong&gt;：超过 10-15 个工具，AI 的工具选择准确率会下降。相关功能尽量合并，用参数区分&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;描述比代码更重要&lt;/strong&gt;：Tool 的 description 写得好，AI 调用准确率能提升 30%+。花时间打磨描述&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;返回结构化文本而非 JSON&lt;/strong&gt;：虽然 JSON 看起来更&amp;quot;专业&amp;quot;，但 AI 处理自然语言描述更高效，报错也更清晰&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;做好降级处理&lt;/strong&gt;：API 不可用时，返回友好提示而非让 MCP 崩溃，用户体验差很多&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;注意敏感信息&lt;/strong&gt;：Token、密码不要打印到 stdout（MCP stdio 模式下 stdout 就是通信通道），用 stderr 打印日志&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;版本锁定&lt;/strong&gt;：&lt;code&gt;mcp&lt;/code&gt; SDK 还在快速迭代，&lt;code&gt;pyproject.toml&lt;/code&gt; 中锁定精确版本避免升级破坏&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;MCP 生态目前还在高速成长，官方 Server 仓库（&lt;a href=&quot;https://github.com/modelcontextprotocol/servers&quot;&gt;github.com/modelcontextprotocol/servers&lt;/a&gt;）已经有几十个现成实现可以参考，包括 GitHub、Slack、数据库等常见场景。如果你的需求和现有实现相似，直接 fork 改改比从零写更快。&lt;/p&gt;&lt;p&gt;最后：MCP 的价值不仅是技术上的标准化，更是工作方式的改变——以后不用打开十几个系统查信息，直接问 AI 就好了。&lt;/p&gt;</description><pubDate>Sun, 05 Apr 2026 08:07:27 +0800</pubDate></item><item><title>PHP 8.x 新特性实战：Enum、只读类、Fibers 完整开发教程</title><link>https://blog.resmic.cn/post/228.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260404080548177526114828471.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;为什么要升级到 PHP 8.x？&lt;/h2&gt;&lt;p&gt;PHP 8.x 系列（8.1、8.2、8.3）带来了大量改变游戏规则的新特性，很多特性能直接减少代码量、提升类型安全、增强运行性能。然而不少团队仍在跑 PHP 7.4，原因是&amp;quot;没时间研究新特性&amp;quot;或&amp;quot;不知道升了有什么用&amp;quot;。&lt;/p&gt;&lt;p&gt;本文不讲理论，全程用真实项目场景演示以下核心特性：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fibers（协程纤程）&lt;/strong&gt;：实现轻量级并发，告别 ReactPHP 的复杂度&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;枚举（Enum）&lt;/strong&gt;：替代常量类，消灭魔法字符串&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;只读属性与只读类&lt;/strong&gt;：构建不可变值对象，防止意外修改&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Intersection Types（交叉类型）&lt;/strong&gt;：更精确的类型约束&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;命名参数（Named Arguments）&lt;/strong&gt;：让函数调用自文档化&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;match 表达式&lt;/strong&gt;：switch 的强类型升级版&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;每个特性都会给出：用途说明 → 旧写法对比 → 新写法实战 → 踩坑点提醒。&lt;/p&gt;&lt;h2&gt;Fibers：PHP 的协程支持终于来了&lt;/h2&gt;&lt;p&gt;PHP 8.1 引入了 Fibers，这是 PHP 原生协程的基础。它允许暂停和恢复一段函数执行，非常适合实现异步任务调度器、流式响应等场景。&lt;/p&gt;&lt;p&gt;理解 Fiber 的关键：它不是多线程，而是协作式多任务——你主动让出控制权，主程序继续执行其他任务，之后再恢复你。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;基本用法示例：&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
//&amp;nbsp;创建一个&amp;nbsp;Fiber
$fiber&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Fiber(function():&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;Fiber&amp;nbsp;开始执行\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$value&amp;nbsp;=&amp;nbsp;Fiber::suspend(&amp;#39;第一次挂起&amp;#39;);&amp;nbsp;&amp;nbsp;//&amp;nbsp;挂起，传值给外部
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;Fiber&amp;nbsp;恢复，收到:&amp;nbsp;{$value}\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Fiber::suspend(&amp;#39;第二次挂起&amp;#39;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;Fiber&amp;nbsp;结束\n&amp;quot;;
});

//&amp;nbsp;启动&amp;nbsp;Fiber，直到第一次&amp;nbsp;suspend
$result1&amp;nbsp;=&amp;nbsp;$fiber-&amp;gt;start();
echo&amp;nbsp;&amp;quot;外部收到:&amp;nbsp;{$result1}\n&amp;quot;;&amp;nbsp;&amp;nbsp;//&amp;nbsp;输出:&amp;nbsp;外部收到:&amp;nbsp;第一次挂起

//&amp;nbsp;恢复&amp;nbsp;Fiber，传入一个值
$result2&amp;nbsp;=&amp;nbsp;$fiber-&amp;gt;resume(&amp;#39;外部传入的数据&amp;#39;);
echo&amp;nbsp;&amp;quot;外部收到:&amp;nbsp;{$result2}\n&amp;quot;;&amp;nbsp;&amp;nbsp;//&amp;nbsp;输出:&amp;nbsp;外部收到:&amp;nbsp;第二次挂起

$fiber-&amp;gt;resume();&amp;nbsp;&amp;nbsp;//&amp;nbsp;最后一次恢复，Fiber&amp;nbsp;结束&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;实战场景：简易任务调度器&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
class&amp;nbsp;SimpleScheduler&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;array&amp;nbsp;$fibers&amp;nbsp;=&amp;nbsp;[];

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;addTask(callable&amp;nbsp;$callback):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;fibers[]&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Fiber($callback);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;run():&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;初始化所有&amp;nbsp;Fiber
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foreach&amp;nbsp;($this-&amp;gt;fibers&amp;nbsp;as&amp;nbsp;$fiber)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$fiber-&amp;gt;start();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;循环调度，直到所有&amp;nbsp;Fiber&amp;nbsp;完成
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;(!empty($this-&amp;gt;fibers))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foreach&amp;nbsp;($this-&amp;gt;fibers&amp;nbsp;as&amp;nbsp;$key&amp;nbsp;=&amp;gt;&amp;nbsp;$fiber)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($fiber-&amp;gt;isSuspended())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$fiber-&amp;gt;resume();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;elseif&amp;nbsp;($fiber-&amp;gt;isTerminated())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;unset($this-&amp;gt;fibers[$key]);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

$scheduler&amp;nbsp;=&amp;nbsp;new&amp;nbsp;SimpleScheduler();

$scheduler-&amp;gt;addTask(function()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;任务A:&amp;nbsp;第一步\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Fiber::suspend();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;任务A:&amp;nbsp;第二步\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Fiber::suspend();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;任务A:&amp;nbsp;完成\n&amp;quot;;
});

$scheduler-&amp;gt;addTask(function()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;任务B:&amp;nbsp;第一步\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Fiber::suspend();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;任务B:&amp;nbsp;完成\n&amp;quot;;
});

$scheduler-&amp;gt;run();
//&amp;nbsp;输出顺序：任务A第一步&amp;nbsp;→&amp;nbsp;任务B第一步&amp;nbsp;→&amp;nbsp;任务A第二步&amp;nbsp;→&amp;nbsp;任务B完成&amp;nbsp;→&amp;nbsp;任务A完成&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;⚠️ 踩坑：&lt;/strong&gt;Fiber 不能跨 Fiber 调用 &lt;code&gt;Fiber::suspend()&lt;/code&gt;，必须在当前 Fiber 内部调用。另外，Fiber 异常不会自动冒泡到外部，需要用 &lt;code&gt;try/catch&lt;/code&gt; 在 Fiber 内部处理，或通过 &lt;code&gt;$fiber-&amp;gt;getReturn()&lt;/code&gt; 检查结果。&lt;/p&gt;&lt;h2&gt;枚举（Enum）：消灭魔法字符串和常量类&lt;/h2&gt;&lt;p&gt;PHP 8.1 之前，我们用常量类或 const 数组来表示有限状态集，代码重复且不类型安全。现在 Enum 原生支持了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;旧写法（常量类，PHP 7.x）：&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
class&amp;nbsp;OrderStatus&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;PENDING&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;pending&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;PAID&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;paid&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;SHIPPED&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;shipped&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;CANCELLED&amp;nbsp;=&amp;nbsp;&amp;#39;cancelled&amp;#39;;
}

//&amp;nbsp;调用时没有类型约束，传什么字符串都行，IDE&amp;nbsp;也不会报错
function&amp;nbsp;processOrder(string&amp;nbsp;$status):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($status&amp;nbsp;===&amp;nbsp;OrderStatus::PAID)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;...
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

processOrder(&amp;#39;piad&amp;#39;);&amp;nbsp;&amp;nbsp;//&amp;nbsp;拼写错误，运行时才发现&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;新写法（PHP 8.1+ Enum）：&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
enum&amp;nbsp;OrderStatus:&amp;nbsp;string&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Pending&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;pending&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Paid&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;paid&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Shipped&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;shipped&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Cancelled&amp;nbsp;=&amp;nbsp;&amp;#39;cancelled&amp;#39;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Enum&amp;nbsp;可以有方法！
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;label():&amp;nbsp;string&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;match($this)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OrderStatus::Pending&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;待支付&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OrderStatus::Paid&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;已支付&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OrderStatus::Shipped&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;已发货&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OrderStatus::Cancelled&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;已取消&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;canTransitionTo(OrderStatus&amp;nbsp;$next):&amp;nbsp;bool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;match($this)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OrderStatus::Pending&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;$next&amp;nbsp;===&amp;nbsp;OrderStatus::Paid&amp;nbsp;||&amp;nbsp;$next&amp;nbsp;===&amp;nbsp;OrderStatus::Cancelled,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OrderStatus::Paid&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;$next&amp;nbsp;===&amp;nbsp;OrderStatus::Shipped,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OrderStatus::Shipped&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OrderStatus::Cancelled&amp;nbsp;=&amp;gt;&amp;nbsp;false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;使用
function&amp;nbsp;processOrder(OrderStatus&amp;nbsp;$status):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;当前状态:&amp;nbsp;&amp;quot;&amp;nbsp;.&amp;nbsp;$status-&amp;gt;label()&amp;nbsp;.&amp;nbsp;&amp;quot;\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($status-&amp;gt;canTransitionTo(OrderStatus::Shipped))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;可以发货\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

processOrder(OrderStatus::Paid);&amp;nbsp;&amp;nbsp;//&amp;nbsp;✅
//&amp;nbsp;processOrder(&amp;#39;piad&amp;#39;);&amp;nbsp;&amp;nbsp;//&amp;nbsp;❌&amp;nbsp;直接类型错误，IDE&amp;nbsp;实时提示

//&amp;nbsp;从数据库值恢复&amp;nbsp;Enum
$dbValue&amp;nbsp;=&amp;nbsp;&amp;#39;paid&amp;#39;;
$status&amp;nbsp;=&amp;nbsp;OrderStatus::from($dbValue);&amp;nbsp;&amp;nbsp;//&amp;nbsp;找不到会抛异常
$status2&amp;nbsp;=&amp;nbsp;OrderStatus::tryFrom(&amp;#39;unknown&amp;#39;);&amp;nbsp;&amp;nbsp;//&amp;nbsp;找不到返回&amp;nbsp;null，更安全&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Enum 实现接口（高级用法）：&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
interface&amp;nbsp;HasColor&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;color():&amp;nbsp;string;
}

enum&amp;nbsp;Suit:&amp;nbsp;string&amp;nbsp;implements&amp;nbsp;HasColor&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Hearts&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;H&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Diamonds&amp;nbsp;=&amp;nbsp;&amp;#39;D&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Clubs&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;C&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Spades&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;S&amp;#39;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;color():&amp;nbsp;string&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;match($this)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Suit::Hearts,&amp;nbsp;Suit::Diamonds&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;red&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Suit::Clubs,&amp;nbsp;Suit::Spades&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;black&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

echo&amp;nbsp;Suit::Hearts-&amp;gt;color();&amp;nbsp;&amp;nbsp;//&amp;nbsp;输出:&amp;nbsp;red&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;⚠️ 踩坑：&lt;/strong&gt;纯 Enum（不带类型声明）的 case 没有 value，不能用 &lt;code&gt;-&amp;gt;value&lt;/code&gt;；带类型（&lt;code&gt;enum X: string&lt;/code&gt;）才有。&lt;code&gt;from()&lt;/code&gt; 和 &lt;code&gt;tryFrom()&lt;/code&gt; 只对 backed enum 有效。Enum 不支持 &lt;code&gt;new&lt;/code&gt; 实例化，也不支持 &lt;code&gt;extends&lt;/code&gt; 继承其他类。&lt;/p&gt;&lt;h2&gt;只读属性与只读类：构建不可变值对象&lt;/h2&gt;&lt;p&gt;PHP 8.1 引入了只读属性（&lt;code&gt;readonly&lt;/code&gt;），PHP 8.2 进一步引入了只读类（&lt;code&gt;readonly class&lt;/code&gt;）。这对于领域驱动设计（DDD）中的值对象（Value Object）来说是天作之合。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;场景：构建一个金额值对象&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
//&amp;nbsp;PHP&amp;nbsp;8.2+&amp;nbsp;只读类：所有属性自动变为&amp;nbsp;readonly
readonly&amp;nbsp;class&amp;nbsp;Money&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;int&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$amount,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;金额（分），防止浮点数精度问题
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;string&amp;nbsp;$currency,&amp;nbsp;&amp;nbsp;//&amp;nbsp;货币代码，如&amp;nbsp;CNY
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($amount&amp;nbsp;&amp;lt;&amp;nbsp;0)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;\InvalidArgumentException(&amp;quot;金额不能为负数:&amp;nbsp;{$amount}&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(strlen($currency)&amp;nbsp;!==&amp;nbsp;3)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;\InvalidArgumentException(&amp;quot;货币代码必须为3位:&amp;nbsp;{$currency}&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;add(Money&amp;nbsp;$other):&amp;nbsp;self&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($this-&amp;gt;currency&amp;nbsp;!==&amp;nbsp;$other-&amp;gt;currency)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;\DomainException(&amp;quot;不同币种不能相加&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;self($this-&amp;gt;amount&amp;nbsp;+&amp;nbsp;$other-&amp;gt;amount,&amp;nbsp;$this-&amp;gt;currency);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;format():&amp;nbsp;string&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;number_format($this-&amp;gt;amount&amp;nbsp;/&amp;nbsp;100,&amp;nbsp;2)&amp;nbsp;.&amp;nbsp;&amp;#39;&amp;nbsp;&amp;#39;&amp;nbsp;.&amp;nbsp;$this-&amp;gt;currency;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;equals(Money&amp;nbsp;$other):&amp;nbsp;bool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;$this-&amp;gt;amount&amp;nbsp;===&amp;nbsp;$other-&amp;gt;amount&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;$this-&amp;gt;currency&amp;nbsp;===&amp;nbsp;$other-&amp;gt;currency;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

$price&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Money(9900,&amp;nbsp;&amp;#39;CNY&amp;#39;);&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;99.00&amp;nbsp;CNY
$tax&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Money(891,&amp;nbsp;&amp;nbsp;&amp;#39;CNY&amp;#39;);&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;8.91&amp;nbsp;CNY
$total&amp;nbsp;=&amp;nbsp;$price-&amp;gt;add($tax);

echo&amp;nbsp;$total-&amp;gt;format();&amp;nbsp;&amp;nbsp;//&amp;nbsp;输出:&amp;nbsp;108.91&amp;nbsp;CNY

//&amp;nbsp;$price-&amp;gt;amount&amp;nbsp;=&amp;nbsp;100;&amp;nbsp;&amp;nbsp;//&amp;nbsp;❌&amp;nbsp;报错：Cannot&amp;nbsp;modify&amp;nbsp;readonly&amp;nbsp;property&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;PHP 8.1 单个属性只读（更细粒度控制）：&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
class&amp;nbsp;User&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$id;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;string&amp;nbsp;$name;&amp;nbsp;&amp;nbsp;//&amp;nbsp;可修改
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$email;&amp;nbsp;&amp;nbsp;//&amp;nbsp;注册后不可修改

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(string&amp;nbsp;$id,&amp;nbsp;string&amp;nbsp;$name,&amp;nbsp;string&amp;nbsp;$email)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;id&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;$id;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;name&amp;nbsp;&amp;nbsp;=&amp;nbsp;$name;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;email&amp;nbsp;=&amp;nbsp;$email;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;rename(string&amp;nbsp;$newName):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;name&amp;nbsp;=&amp;nbsp;$newName;&amp;nbsp;&amp;nbsp;//&amp;nbsp;✅&amp;nbsp;可以修改
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;$this-&amp;gt;email&amp;nbsp;=&amp;nbsp;&amp;#39;...&amp;#39;;&amp;nbsp;//&amp;nbsp;❌&amp;nbsp;报错
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;⚠️ 踩坑：&lt;/strong&gt;只读属性只能赋值一次，且只能在声明它的类内部赋值（构造函数内）。&lt;code&gt;clone&lt;/code&gt; 操作不会重置只读属性，所以无法用 &lt;code&gt;clone $obj; $clone-&amp;gt;readonly = newVal;&lt;/code&gt; 来&amp;quot;修改&amp;quot;。PHP 8.3 引入了 &lt;code&gt;clone with&lt;/code&gt; 语法（尚在 RFC 阶段），目前可用工厂方法代替。&lt;/p&gt;&lt;h2&gt;match 表达式与命名参数：日常必用的提效利器&lt;/h2&gt;&lt;p&gt;这两个特性虽然不如 Fibers 那么炫酷，但在日常开发中出现频率极高，值得专门介绍。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;match 表达式（PHP 8.0+）&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;match 是 switch 的严格类型版本：使用 &lt;code&gt;===&lt;/code&gt; 比较，必须有返回值，不会 fall-through，未匹配时抛出 &lt;code&gt;UnhandledMatchError&lt;/code&gt;。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
//&amp;nbsp;旧写法&amp;nbsp;switch（PHP&amp;nbsp;7.x）
function&amp;nbsp;getDiscount_old(string&amp;nbsp;$userType):&amp;nbsp;int&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;switch&amp;nbsp;($userType)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;&amp;#39;vip&amp;#39;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;20;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;&amp;#39;member&amp;#39;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;10;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;新写法&amp;nbsp;match（PHP&amp;nbsp;8.0+）
function&amp;nbsp;getDiscount(string&amp;nbsp;$userType):&amp;nbsp;int&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;match($userType)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;vip&amp;#39;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;20,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;member&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;10,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
}

//&amp;nbsp;match&amp;nbsp;可以匹配多个条件
$httpCode&amp;nbsp;=&amp;nbsp;404;
$message&amp;nbsp;=&amp;nbsp;match(true)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$httpCode&amp;nbsp;&amp;gt;=&amp;nbsp;500&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;服务器错误&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$httpCode&amp;nbsp;===&amp;nbsp;404&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;页面不存在&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$httpCode&amp;nbsp;===&amp;nbsp;401,&amp;nbsp;$httpCode&amp;nbsp;===&amp;nbsp;403&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;无权访问&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$httpCode&amp;nbsp;&amp;gt;=&amp;nbsp;200&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;$httpCode&amp;nbsp;&amp;lt;&amp;nbsp;300&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;成功&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;未知状态&amp;#39;,
};
echo&amp;nbsp;$message;&amp;nbsp;&amp;nbsp;//&amp;nbsp;输出:&amp;nbsp;页面不存在&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;命名参数（PHP 8.0+）&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;命名参数让函数调用更清晰，尤其是有多个可选参数时，无需记忆参数顺序。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
//&amp;nbsp;旧写法：必须记住参数顺序，第&amp;nbsp;3、4、5&amp;nbsp;个参数是什么鬼？
$result&amp;nbsp;=&amp;nbsp;array_slice($array,&amp;nbsp;0,&amp;nbsp;5,&amp;nbsp;true);

//&amp;nbsp;新写法：一目了然
$result&amp;nbsp;=&amp;nbsp;array_slice(array:&amp;nbsp;$array,&amp;nbsp;offset:&amp;nbsp;0,&amp;nbsp;length:&amp;nbsp;5,&amp;nbsp;preserve_keys:&amp;nbsp;true);

//&amp;nbsp;在构造函数中特别好用（和&amp;nbsp;Constructor&amp;nbsp;Promotion&amp;nbsp;组合）
class&amp;nbsp;PaginatedQuery&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$table,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;int&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$page&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;1,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;int&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$perPage&amp;nbsp;=&amp;nbsp;20,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$orderBy&amp;nbsp;=&amp;nbsp;&amp;#39;created_at&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$order&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;desc&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{}
}

//&amp;nbsp;只传你关心的参数
$query&amp;nbsp;=&amp;nbsp;new&amp;nbsp;PaginatedQuery(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;orders&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;perPage:&amp;nbsp;50,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;orderBy:&amp;nbsp;&amp;#39;total_amount&amp;#39;,
);&lt;/pre&gt;&lt;h2&gt;Intersection Types：更精确的接口约束&lt;/h2&gt;&lt;p&gt;PHP 8.1 引入交叉类型（Intersection Types），允许一个参数同时满足多个接口约束，不再需要为此创建空的组合接口。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
interface&amp;nbsp;Serializable&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;serialize():&amp;nbsp;string;
}

interface&amp;nbsp;Loggable&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;toLog():&amp;nbsp;array;
}

interface&amp;nbsp;Cacheable&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;cacheKey():&amp;nbsp;string;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;ttl():&amp;nbsp;int;
}

//&amp;nbsp;旧写法（PHP&amp;nbsp;8.0&amp;nbsp;以前）：必须创建一个组合接口
interface&amp;nbsp;CacheableAndLoggable&amp;nbsp;extends&amp;nbsp;Cacheable,&amp;nbsp;Loggable&amp;nbsp;{}

function&amp;nbsp;saveToCache(CacheableAndLoggable&amp;nbsp;$entity):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;...
}

//&amp;nbsp;新写法（PHP&amp;nbsp;8.1+&amp;nbsp;交叉类型）：直接用&amp;nbsp;&amp;amp;&amp;nbsp;组合
function&amp;nbsp;saveToCache(Cacheable&amp;amp;Loggable&amp;nbsp;$entity):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$key&amp;nbsp;&amp;nbsp;=&amp;nbsp;$entity-&amp;gt;cacheKey();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$ttl&amp;nbsp;&amp;nbsp;=&amp;nbsp;$entity-&amp;gt;ttl();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$log&amp;nbsp;&amp;nbsp;=&amp;nbsp;$entity-&amp;gt;toLog();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;存缓存&amp;nbsp;+&amp;nbsp;记日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;缓存&amp;nbsp;{$key}，TTL={$ttl}s\n&amp;quot;;
}

//&amp;nbsp;实际类只需实现对应接口即可
class&amp;nbsp;Order&amp;nbsp;implements&amp;nbsp;Cacheable,&amp;nbsp;Loggable,&amp;nbsp;Serializable&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(private&amp;nbsp;string&amp;nbsp;$id)&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;cacheKey():&amp;nbsp;string&amp;nbsp;{&amp;nbsp;return&amp;nbsp;&amp;quot;order:{$this-&amp;gt;id}&amp;quot;;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;ttl():&amp;nbsp;int&amp;nbsp;{&amp;nbsp;return&amp;nbsp;300;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;toLog():&amp;nbsp;array&amp;nbsp;{&amp;nbsp;return&amp;nbsp;[&amp;#39;order_id&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;$this-&amp;gt;id];&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;serialize():&amp;nbsp;string&amp;nbsp;{&amp;nbsp;return&amp;nbsp;json_encode([&amp;#39;id&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;$this-&amp;gt;id]);&amp;nbsp;}
}

saveToCache(new&amp;nbsp;Order(&amp;#39;ORD-001&amp;#39;));&amp;nbsp;&amp;nbsp;//&amp;nbsp;✅&amp;nbsp;Order&amp;nbsp;实现了&amp;nbsp;Cacheable&amp;nbsp;和&amp;nbsp;Loggable&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;⚠️ 踩坑：&lt;/strong&gt;交叉类型不能和 Union 类型混用（如 &lt;code&gt;A&amp;amp;B|C&lt;/code&gt; 会报错），也不能包含 &lt;code&gt;null&lt;/code&gt;。返回类型同样支持交叉类型声明。&lt;/p&gt;&lt;h2&gt;升级清单与兼容性检查&lt;/h2&gt;&lt;p&gt;升级前必须做的几件事：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;运行 php-compatibility-checker&lt;/strong&gt;：&lt;code&gt;composer require --dev phpcompatibility/php-compatibility&lt;/code&gt;，扫描项目中不兼容的写法&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;检查第三方库&lt;/strong&gt;：&lt;code&gt;composer require --dev roave/backward-compatibility-check&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;注意废弃函数&lt;/strong&gt;：PHP 8.x 废弃了 &lt;code&gt;utf8_encode/decode&lt;/code&gt;、部分 PCRE 函数，需要替换&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;错误处理变化&lt;/strong&gt;：PHP 8.0 将很多 Warning 升级为了 TypeError 或 ValueError，需要修复类型不匹配问题&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;#&amp;nbsp;检查兼容性（以&amp;nbsp;PHP&amp;nbsp;8.2&amp;nbsp;为目标）
./vendor/bin/phpcs&amp;nbsp;--standard=PHPCompatibility&amp;nbsp;\
&amp;nbsp;&amp;nbsp;--runtime-set&amp;nbsp;testVersion&amp;nbsp;8.2&amp;nbsp;\
&amp;nbsp;&amp;nbsp;--extensions=php&amp;nbsp;\
&amp;nbsp;&amp;nbsp;./src/

#&amp;nbsp;使用&amp;nbsp;Rector&amp;nbsp;自动迁移旧代码
composer&amp;nbsp;require&amp;nbsp;--dev&amp;nbsp;rector/rector
./vendor/bin/rector&amp;nbsp;process&amp;nbsp;src&amp;nbsp;--set-list&amp;nbsp;php82&lt;/pre&gt;&lt;p&gt;升级 PHP 8.x 是一次值得投资的技术债偿还。Enum 减少了 30% 的状态相关 bug，只读属性让值对象的设计更加清晰，Fibers 则为将来拥抱异步编程打下基础。建议从 PHP 8.1 开始，把 Enum 和只读属性用起来，这两个特性学习成本最低、收益最高。&lt;/p&gt;</description><pubDate>Sat, 04 Apr 2026 08:07:19 +0800</pubDate></item><item><title>Ollama 本地部署大模型完全指南：从安装配置到实际应用</title><link>https://blog.resmic.cn/post/227.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260403101802177518268274283.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;为什么选择 Ollama 在本地部署大模型？&lt;/h2&gt;&lt;p&gt;近年来，随着 LLaMA、Mistral、Qwen 等开源大语言模型的涌现，越来越多的开发者希望在本地运行大模型，而不是依赖 OpenAI 等云服务。本地部署的优势显而易见：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据隐私&lt;/strong&gt;：代码、文档、敏感信息不离开本地机器&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;无调用费用&lt;/strong&gt;：无需按 Token 计费，随意调用&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;离线可用&lt;/strong&gt;：无网络也能正常使用&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;自定义模型&lt;/strong&gt;：可以微调、量化、替换任意模型&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;低延迟&lt;/strong&gt;：本地推理延迟通常低于远程 API&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;而 &lt;strong&gt;Ollama&lt;/strong&gt; 正是让这一切变得极其简单的工具。它将模型下载、管理、推理服务全部封装成一个命令行工具，配合 REST API，可以无缝替换 OpenAI 接口。本文将从零开始，带你完整走通 Ollama 的安装、配置、模型管理，以及实际开发应用的全流程。&lt;/p&gt;&lt;h2&gt;环境准备与安装&lt;/h2&gt;&lt;p&gt;Ollama 支持 macOS、Linux 和 Windows（WSL2），以下以 Linux/macOS 为主，Windows 用户参考 WSL2 方案。&lt;/p&gt;&lt;h3&gt;硬件要求&lt;/h3&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;最低配置&lt;/strong&gt;：8GB 内存，可运行 7B 量化模型（Q4）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;推荐配置&lt;/strong&gt;：16GB 内存，运行 13B Q4 模型流畅&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;GPU 加速&lt;/strong&gt;：NVIDIA GPU（推荐 8GB+ 显存），支持 CUDA 11.8+&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apple Silicon&lt;/strong&gt;：M1/M2/M3 Mac 天然支持，Metal 加速极快&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;安装步骤&lt;/h3&gt;&lt;p&gt;Linux / macOS 一键安装：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;官方安装脚本（推荐）
curl&amp;nbsp;-fsSL&amp;nbsp;https://ollama.com/install.sh&amp;nbsp;|&amp;nbsp;sh

#&amp;nbsp;安装完成后验证
ollama&amp;nbsp;--version
#&amp;nbsp;输出：ollama&amp;nbsp;version&amp;nbsp;is&amp;nbsp;0.3.x&lt;/pre&gt;&lt;p&gt;macOS 也可以通过 Homebrew 安装：&lt;/p&gt;&lt;pre&gt;brew&amp;nbsp;install&amp;nbsp;ollama&lt;/pre&gt;&lt;p&gt;Windows 用户下载安装包：&lt;code&gt;https://ollama.com/download/windows&lt;/code&gt;&lt;/p&gt;&lt;p&gt;安装完成后，Ollama 会在后台以服务形式运行（默认监听 &lt;code&gt;localhost:11434&lt;/code&gt;）。你可以手动控制服务：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;启动服务（如果未自动启动）
ollama&amp;nbsp;serve

#&amp;nbsp;查看服务状态
systemctl&amp;nbsp;status&amp;nbsp;ollama&amp;nbsp;&amp;nbsp;#&amp;nbsp;Linux&amp;nbsp;systemd&amp;nbsp;系统

#&amp;nbsp;查看日志
journalctl&amp;nbsp;-u&amp;nbsp;ollama&amp;nbsp;-f&lt;/pre&gt;&lt;h2&gt;下载与管理模型&lt;/h2&gt;&lt;p&gt;Ollama 内置模型仓库，支持一键拉取主流开源模型。&lt;/p&gt;&lt;h3&gt;常用模型推荐&lt;/h3&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;llama3.2:3b&lt;/strong&gt; - Meta 最新 Llama，3B 参数，内存占用约 2GB，速度极快&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;qwen2.5:7b&lt;/strong&gt; - 阿里通义，中文能力强，7B Q4 约 5GB&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;mistral:7b&lt;/strong&gt; - 法国 Mistral，代码能力优秀&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;deepseek-coder:6.7b&lt;/strong&gt; - 专门针对代码优化，编程效果极佳&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;phi3:mini&lt;/strong&gt; - 微软 Phi-3，3.8B 参数，效果超越同量级模型&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;gemma2:9b&lt;/strong&gt; - Google Gemma2，综合能力强&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;模型下载与基本操作&lt;/h3&gt;&lt;pre&gt;#&amp;nbsp;下载模型（首次运行会自动下载）
ollama&amp;nbsp;pull&amp;nbsp;llama3.2:3b

#&amp;nbsp;查看已下载的模型
ollama&amp;nbsp;list
#&amp;nbsp;NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ID&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SIZE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MODIFIED
#&amp;nbsp;llama3.2:3b&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;a80c4f17acd5&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2.0&amp;nbsp;GB&amp;nbsp;&amp;nbsp;2&amp;nbsp;minutes&amp;nbsp;ago

#&amp;nbsp;交互式对话（命令行）
ollama&amp;nbsp;run&amp;nbsp;llama3.2:3b
#&amp;nbsp;&amp;gt;&amp;gt;&amp;gt;&amp;nbsp;输入你的问题

#&amp;nbsp;删除模型
ollama&amp;nbsp;rm&amp;nbsp;llama3.2:3b

#&amp;nbsp;查看模型详情
ollama&amp;nbsp;show&amp;nbsp;llama3.2:3b&lt;/pre&gt;&lt;p&gt;⚠️ &lt;strong&gt;踩坑记录&lt;/strong&gt;：首次下载大模型（如 70B）时可能需要几十分钟，请确保磁盘空间充足。模型默认存储在 &lt;code&gt;~/.ollama/models&lt;/code&gt;，可以通过环境变量 &lt;code&gt;OLLAMA_MODELS&lt;/code&gt; 修改存储路径：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;修改模型存储路径（加入&amp;nbsp;~/.bashrc&amp;nbsp;或&amp;nbsp;~/.zshrc）
export&amp;nbsp;OLLAMA_MODELS=/data/ollama-models

#&amp;nbsp;重启服务生效
sudo&amp;nbsp;systemctl&amp;nbsp;restart&amp;nbsp;ollama&lt;/pre&gt;&lt;h2&gt;通过 REST API 调用模型&lt;/h2&gt;&lt;p&gt;Ollama 提供了兼容 OpenAI 格式的 REST API，这让它可以无缝接入各种工具链。&lt;/p&gt;&lt;h3&gt;基础 API 调用&lt;/h3&gt;&lt;pre&gt;#&amp;nbsp;使用&amp;nbsp;curl&amp;nbsp;调用（流式输出）
curl&amp;nbsp;http://localhost:11434/api/generate&amp;nbsp;-d&amp;nbsp;&amp;#39;{
&amp;nbsp;&amp;nbsp;&amp;quot;model&amp;quot;:&amp;nbsp;&amp;quot;llama3.2:3b&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;quot;prompt&amp;quot;:&amp;nbsp;&amp;quot;用&amp;nbsp;Python&amp;nbsp;写一个快速排序算法&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;quot;stream&amp;quot;:&amp;nbsp;false
}&amp;#39;

#&amp;nbsp;返回示例
{
&amp;nbsp;&amp;nbsp;&amp;quot;model&amp;quot;:&amp;nbsp;&amp;quot;llama3.2:3b&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;quot;created_at&amp;quot;:&amp;nbsp;&amp;quot;2024-01-01T00:00:00Z&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;quot;response&amp;quot;:&amp;nbsp;&amp;quot;def&amp;nbsp;quicksort(arr):\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(arr)&lt;/pre&gt;&lt;h3&gt;OpenAI 兼容接口（推荐）&lt;/h3&gt;&lt;p&gt;Ollama 还提供了 &lt;code&gt;/v1/chat/completions&lt;/code&gt; 兼容接口，直接替换 OpenAI SDK：&lt;/p&gt;&lt;pre&gt;from&amp;nbsp;openai&amp;nbsp;import&amp;nbsp;OpenAI

#&amp;nbsp;只需替换&amp;nbsp;base_url，无需修改其他代码！
client&amp;nbsp;=&amp;nbsp;OpenAI(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base_url=&amp;quot;http://localhost:11434/v1&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;api_key=&amp;quot;ollama&amp;quot;,&amp;nbsp;&amp;nbsp;#&amp;nbsp;随意填写，本地不校验
)

response&amp;nbsp;=&amp;nbsp;client.chat.completions.create(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=&amp;quot;llama3.2:3b&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;messages=[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;quot;role&amp;quot;:&amp;nbsp;&amp;quot;system&amp;quot;,&amp;nbsp;&amp;quot;content&amp;quot;:&amp;nbsp;&amp;quot;你是一个专业的&amp;nbsp;Python&amp;nbsp;开发者&amp;quot;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;quot;role&amp;quot;:&amp;nbsp;&amp;quot;user&amp;quot;,&amp;nbsp;&amp;quot;content&amp;quot;:&amp;nbsp;&amp;quot;帮我实现一个二分查找函数&amp;quot;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
)
print(response.choices[0].message.content)&lt;/pre&gt;&lt;h3&gt;Python 原生 SDK 调用&lt;/h3&gt;&lt;pre&gt;pip&amp;nbsp;install&amp;nbsp;ollama

#&amp;nbsp;使用示例
import&amp;nbsp;ollama

#&amp;nbsp;简单对话
response&amp;nbsp;=&amp;nbsp;ollama.chat(model=&amp;#39;llama3.2:3b&amp;#39;,&amp;nbsp;messages=[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;#39;role&amp;#39;:&amp;nbsp;&amp;#39;user&amp;#39;,&amp;nbsp;&amp;#39;content&amp;#39;:&amp;nbsp;&amp;#39;解释一下&amp;nbsp;Python&amp;nbsp;的&amp;nbsp;GIL&amp;nbsp;锁&amp;#39;}
])
print(response[&amp;#39;message&amp;#39;][&amp;#39;content&amp;#39;])

#&amp;nbsp;流式输出
stream&amp;nbsp;=&amp;nbsp;ollama.chat(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=&amp;#39;qwen2.5:7b&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;messages=[{&amp;#39;role&amp;#39;:&amp;nbsp;&amp;#39;user&amp;#39;,&amp;nbsp;&amp;#39;content&amp;#39;:&amp;nbsp;&amp;#39;写一篇关于&amp;nbsp;AI&amp;nbsp;的短文&amp;#39;}],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stream=True,
)
for&amp;nbsp;chunk&amp;nbsp;in&amp;nbsp;stream:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(chunk[&amp;#39;message&amp;#39;][&amp;#39;content&amp;#39;],&amp;nbsp;end=&amp;#39;&amp;#39;,&amp;nbsp;flush=True)&lt;/pre&gt;&lt;h2&gt;自定义 Modelfile：创建专属模型&lt;/h2&gt;&lt;p&gt;Ollama 支持通过 &lt;code&gt;Modelfile&lt;/code&gt; 自定义模型行为，类似于 Docker 的 Dockerfile。&lt;/p&gt;&lt;h3&gt;Modelfile 基本结构&lt;/h3&gt;&lt;pre&gt;#&amp;nbsp;创建&amp;nbsp;Modelfile
cat&amp;nbsp;&amp;gt;&amp;nbsp;Modelfile&lt;/pre&gt;&lt;h3&gt;参数说明&lt;/h3&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;temperature&lt;/code&gt;：控制随机性，0.1-0.3 适合代码任务，0.7-0.9 适合创意写作&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;top_p&lt;/code&gt;：核采样，一般保持 0.9&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;num_ctx&lt;/code&gt;：上下文窗口大小，越大越吃内存，一般 4096-8192&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;repeat_penalty&lt;/code&gt;：重复惩罚，防止模型循环输出，推荐 1.1&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;⚠️ &lt;strong&gt;踩坑记录&lt;/strong&gt;：系统提示词中如果含有中文引号或特殊字符，Modelfile 解析可能出错。建议使用三引号 &lt;code&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/code&gt; 包裹，并避免在提示词中使用反斜杠。&lt;/p&gt;&lt;h2&gt;与主流开发工具集成&lt;/h2&gt;&lt;h3&gt;VS Code + Continue 插件&lt;/h3&gt;&lt;p&gt;Continue 是最受欢迎的 VS Code AI 编程插件，支持本地 Ollama 模型：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;在 VS Code 扩展市场安装 &lt;strong&gt;Continue&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;打开配置文件 &lt;code&gt;~/.continue/config.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;添加 Ollama 模型配置：&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;{
&amp;nbsp;&amp;nbsp;&amp;quot;models&amp;quot;:&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;&amp;quot;Qwen2.5&amp;nbsp;7B&amp;nbsp;(Local)&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;provider&amp;quot;:&amp;nbsp;&amp;quot;ollama&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;model&amp;quot;:&amp;nbsp;&amp;quot;qwen2.5:7b&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;apiBase&amp;quot;:&amp;nbsp;&amp;quot;http://localhost:11434&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;&amp;quot;DeepSeek&amp;nbsp;Coder&amp;nbsp;(Local)&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;provider&amp;quot;:&amp;nbsp;&amp;quot;ollama&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;model&amp;quot;:&amp;nbsp;&amp;quot;deepseek-coder:6.7b&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;apiBase&amp;quot;:&amp;nbsp;&amp;quot;http://localhost:11434&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;],
&amp;nbsp;&amp;nbsp;&amp;quot;tabAutocompleteModel&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;&amp;quot;Autocomplete&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;provider&amp;quot;:&amp;nbsp;&amp;quot;ollama&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;model&amp;quot;:&amp;nbsp;&amp;quot;qwen2.5-coder:1.5b&amp;quot;
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;Open WebUI：本地 ChatGPT 界面&lt;/h3&gt;&lt;p&gt;Open WebUI 提供了类似 ChatGPT 的 Web 界面，Docker 一键部署：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;前提：Ollama&amp;nbsp;已在本地运行
docker&amp;nbsp;run&amp;nbsp;-d&amp;nbsp;-p&amp;nbsp;3000:8080&amp;nbsp;\
&amp;nbsp;&amp;nbsp;--add-host=host.docker.internal:host-gateway&amp;nbsp;\
&amp;nbsp;&amp;nbsp;-v&amp;nbsp;open-webui:/app/backend/data&amp;nbsp;\
&amp;nbsp;&amp;nbsp;--name&amp;nbsp;open-webui&amp;nbsp;\
&amp;nbsp;&amp;nbsp;--restart&amp;nbsp;always&amp;nbsp;\
&amp;nbsp;&amp;nbsp;ghcr.io/open-webui/open-webui:main

#&amp;nbsp;访问&amp;nbsp;http://localhost:3000&lt;/pre&gt;&lt;p&gt;⚠️ &lt;strong&gt;踩坑记录&lt;/strong&gt;：Docker 容器内无法直接访问宿主机的 Ollama 服务。需要用 &lt;code&gt;--add-host=host.docker.internal:host-gateway&lt;/code&gt;，然后在 Open WebUI 设置中将 Ollama 地址改为 &lt;code&gt;http://host.docker.internal:11434&lt;/code&gt;。&lt;/p&gt;&lt;h3&gt;LangChain 集成&lt;/h3&gt;&lt;pre&gt;pip&amp;nbsp;install&amp;nbsp;langchain-community

from&amp;nbsp;langchain_community.llms&amp;nbsp;import&amp;nbsp;Ollama
from&amp;nbsp;langchain.prompts&amp;nbsp;import&amp;nbsp;PromptTemplate
from&amp;nbsp;langchain.chains&amp;nbsp;import&amp;nbsp;LLMChain

#&amp;nbsp;初始化&amp;nbsp;Ollama&amp;nbsp;LLM
llm&amp;nbsp;=&amp;nbsp;Ollama(model=&amp;quot;qwen2.5:7b&amp;quot;,&amp;nbsp;base_url=&amp;quot;http://localhost:11434&amp;quot;)

#&amp;nbsp;创建提示模板
prompt&amp;nbsp;=&amp;nbsp;PromptTemplate(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;input_variables=[&amp;quot;topic&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template=&amp;quot;请用中文写一篇关于{topic}的技术博客文章大纲，包含5个主要章节。&amp;quot;
)

#&amp;nbsp;创建链
chain&amp;nbsp;=&amp;nbsp;LLMChain(llm=llm,&amp;nbsp;prompt=prompt)

#&amp;nbsp;执行
result&amp;nbsp;=&amp;nbsp;chain.run(topic=&amp;quot;微服务架构设计模式&amp;quot;)
print(result)&lt;/pre&gt;&lt;h2&gt;性能优化与调优技巧&lt;/h2&gt;&lt;h3&gt;GPU 加速配置&lt;/h3&gt;&lt;p&gt;如果你有 NVIDIA GPU，Ollama 会自动检测并使用。验证 GPU 是否被使用：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;运行模型时查看&amp;nbsp;GPU&amp;nbsp;使用情况
watch&amp;nbsp;-n1&amp;nbsp;nvidia-smi

#&amp;nbsp;或在运行时检查&amp;nbsp;Ollama&amp;nbsp;日志
journalctl&amp;nbsp;-u&amp;nbsp;ollama&amp;nbsp;-f&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;&amp;quot;cuda\|gpu&amp;quot;&lt;/pre&gt;&lt;p&gt;强制使用指定 GPU（多卡环境）：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;只使用第&amp;nbsp;0&amp;nbsp;块&amp;nbsp;GPU
CUDA_VISIBLE_DEVICES=0&amp;nbsp;ollama&amp;nbsp;serve

#&amp;nbsp;使用多块&amp;nbsp;GPU（需要&amp;nbsp;Ollama&amp;nbsp;0.2.0+）
CUDA_VISIBLE_DEVICES=0,1&amp;nbsp;ollama&amp;nbsp;serve&lt;/pre&gt;&lt;h3&gt;内存优化&lt;/h3&gt;&lt;p&gt;对于内存不足的情况，可以使用更高压缩比的量化模型：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;model:Q8_0&lt;/code&gt; - 8bit 量化，质量最好，内存约原始的 50%&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;model:Q4_K_M&lt;/code&gt; - 4bit 量化，推荐平衡版，内存约原始的 25%&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;model:Q3_K_S&lt;/code&gt; - 3bit 量化，最省内存，质量有损失&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;#&amp;nbsp;下载特定量化版本
ollama&amp;nbsp;pull&amp;nbsp;qwen2.5:7b-instruct-q4_K_M

#&amp;nbsp;查看不同量化版本
ollama&amp;nbsp;list&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;qwen&lt;/pre&gt;&lt;h3&gt;并发请求配置&lt;/h3&gt;&lt;pre&gt;#&amp;nbsp;设置最大并发请求数（默认&amp;nbsp;1）
OLLAMA_NUM_PARALLEL=4&amp;nbsp;ollama&amp;nbsp;serve

#&amp;nbsp;设置最大加载模型数量
OLLAMA_MAX_LOADED_MODELS=3&amp;nbsp;ollama&amp;nbsp;serve

#&amp;nbsp;写入&amp;nbsp;systemd&amp;nbsp;服务配置（永久生效）
sudo&amp;nbsp;systemctl&amp;nbsp;edit&amp;nbsp;ollama
#&amp;nbsp;添加：
[Service]
Environment=&amp;quot;OLLAMA_NUM_PARALLEL=4&amp;quot;
Environment=&amp;quot;OLLAMA_MAX_LOADED_MODELS=2&amp;quot;&lt;/pre&gt;&lt;h2&gt;实战案例：构建本地 AI 代码助手&lt;/h2&gt;&lt;p&gt;下面我们来实现一个实用的本地 AI 代码助手，支持代码审查、Bug 修复和文档生成：&lt;/p&gt;&lt;pre&gt;import&amp;nbsp;ollama
import&amp;nbsp;sys
from&amp;nbsp;pathlib&amp;nbsp;import&amp;nbsp;Path

class&amp;nbsp;LocalCodeAssistant:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;__init__(self,&amp;nbsp;model=&amp;quot;qwen2.5:7b&amp;quot;):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.model&amp;nbsp;=&amp;nbsp;model
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.client&amp;nbsp;=&amp;nbsp;ollama.Client(host=&amp;quot;http://localhost:11434&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;review_code(self,&amp;nbsp;code:&amp;nbsp;str,&amp;nbsp;language:&amp;nbsp;str&amp;nbsp;=&amp;nbsp;&amp;quot;Python&amp;quot;)&amp;nbsp;-&amp;gt;&amp;nbsp;str:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;代码审查&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;prompt&amp;nbsp;=&amp;nbsp;f&amp;quot;&amp;quot;&amp;quot;请审查以下&amp;nbsp;{language}&amp;nbsp;代码，指出：
1.&amp;nbsp;潜在的&amp;nbsp;Bug
2.&amp;nbsp;安全问题
3.&amp;nbsp;性能优化点
4.&amp;nbsp;代码风格问题

代码：
```{language.lower()}
{code}
```

请用中文回答，格式清晰。&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response&amp;nbsp;=&amp;nbsp;self.client.chat(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=self.model,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;messages=[{&amp;quot;role&amp;quot;:&amp;nbsp;&amp;quot;user&amp;quot;,&amp;nbsp;&amp;quot;content&amp;quot;:&amp;nbsp;prompt}]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;response[&amp;quot;message&amp;quot;][&amp;quot;content&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;generate_docstring(self,&amp;nbsp;func_code:&amp;nbsp;str)&amp;nbsp;-&amp;gt;&amp;nbsp;str:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;生成函数文档&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;prompt&amp;nbsp;=&amp;nbsp;f&amp;quot;&amp;quot;&amp;quot;为以下函数生成详细的&amp;nbsp;docstring（Google&amp;nbsp;风格）：

{func_code}

只返回&amp;nbsp;docstring&amp;nbsp;内容，不要包含函数本身。&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response&amp;nbsp;=&amp;nbsp;self.client.chat(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=self.model,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;messages=[{&amp;quot;role&amp;quot;:&amp;nbsp;&amp;quot;user&amp;quot;,&amp;nbsp;&amp;quot;content&amp;quot;:&amp;nbsp;prompt}]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;response[&amp;quot;message&amp;quot;][&amp;quot;content&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;fix_bug(self,&amp;nbsp;code:&amp;nbsp;str,&amp;nbsp;error_msg:&amp;nbsp;str)&amp;nbsp;-&amp;gt;&amp;nbsp;str:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;修复&amp;nbsp;Bug&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;prompt&amp;nbsp;=&amp;nbsp;f&amp;quot;&amp;quot;&amp;quot;以下代码出现了错误，请帮我修复：

代码：
```python
{code}
```

错误信息：
{error_msg}

请提供修复后的完整代码，并解释修改原因。&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response&amp;nbsp;=&amp;nbsp;self.client.chat(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=self.model,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;messages=[{&amp;quot;role&amp;quot;:&amp;nbsp;&amp;quot;user&amp;quot;,&amp;nbsp;&amp;quot;content&amp;quot;:&amp;nbsp;prompt}]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;response[&amp;quot;message&amp;quot;][&amp;quot;content&amp;quot;]


#&amp;nbsp;使用示例
if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;assistant&amp;nbsp;=&amp;nbsp;LocalCodeAssistant(model=&amp;quot;qwen2.5:7b&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;示例：代码审查
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sample_code&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
def&amp;nbsp;get_user_data(user_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(&amp;#39;users.db&amp;#39;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor&amp;nbsp;=&amp;nbsp;conn.cursor()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;query&amp;nbsp;=&amp;nbsp;f&amp;quot;SELECT&amp;nbsp;*&amp;nbsp;FROM&amp;nbsp;users&amp;nbsp;WHERE&amp;nbsp;id&amp;nbsp;=&amp;nbsp;{user_id}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.execute(query)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;cursor.fetchone()
&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;===&amp;nbsp;代码审查结果&amp;nbsp;===&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;assistant.review_code(sample_code)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(result)&lt;/pre&gt;&lt;p&gt;运行效果：模型会准确指出上述代码存在 SQL 注入漏洞，并提供参数化查询的修复方案。&lt;/p&gt;&lt;h2&gt;常见问题与解决方案&lt;/h2&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;问题：&lt;code&gt;ollama: command not found&lt;/code&gt;&lt;/strong&gt;&lt;br/&gt;解决：检查 &lt;code&gt;/usr/local/bin/ollama&lt;/code&gt; 是否存在，重新执行安装脚本，或手动添加 PATH。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;问题：模型加载缓慢，CPU 使用率 100%&lt;/strong&gt;&lt;br/&gt;解决：确认 GPU 驱动已安装（&lt;code&gt;nvidia-smi&lt;/code&gt;），Ollama 会自动使用 GPU。如果是 CPU 推理，属于正常现象。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;问题：&lt;code&gt;Error: model not found&lt;/code&gt;&lt;/strong&gt;&lt;br/&gt;解决：先执行 &lt;code&gt;ollama pull 模型名&lt;/code&gt; 下载模型，或检查模型名拼写（区分大小写）。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;问题：内存不足，推理崩溃&lt;/strong&gt;&lt;br/&gt;解决：换用更小参数量或更高压缩比的量化模型，如 &lt;code&gt;:3b-q4_0&lt;/code&gt; 版本。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;问题：多轮对话上下文丢失&lt;/strong&gt;&lt;br/&gt;解决：使用 &lt;code&gt;/api/chat&lt;/code&gt; 接口并维护完整 messages 历史，而非 &lt;code&gt;/api/generate&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;问题：Open WebUI 连接不上 Ollama&lt;/strong&gt;&lt;br/&gt;解决：检查 Ollama 是否监听在 &lt;code&gt;0.0.0.0&lt;/code&gt;，可设置 &lt;code&gt;OLLAMA_HOST=0.0.0.0&lt;/code&gt; 环境变量。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;至此，你已经掌握了 Ollama 从安装到实际应用的完整流程。本地大模型部署的门槛已经大幅降低，无论是个人开发者还是企业团队，都可以用极低的成本搭建私有 AI 基础设施。建议从 &lt;code&gt;qwen2.5:7b&lt;/code&gt; 或 &lt;code&gt;llama3.2:3b&lt;/code&gt; 开始体验，根据实际需求逐步调优。&lt;/p&gt;</description><pubDate>Fri, 03 Apr 2026 10:19:38 +0800</pubDate></item><item><title>PHP 8.3 新特性实战：Typed Constants、readonly 改进与迁移指南</title><link>https://blog.resmic.cn/post/226.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260402080506177508830646316.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;为什么要升级到 PHP 8.3？&lt;/h2&gt;&lt;p&gt;PHP 8.3 于 2023 年 11 月正式发布，支持周期到 2027 年底。相比 8.2，它带来的不只是性能微调——多项语法特性直接影响日常编码质量。如果你还在犹豫是否升级，看完本文的实战案例，相信你会迫不及待地打开终端。&lt;/p&gt;&lt;p&gt;本文聚焦以下几个最值得关注的特性：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;类型化类常量（Typed Class Constants）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;readonly 属性在匿名类中的改进&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;新增 &lt;code&gt;json_validate()&lt;/code&gt; 函数&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;动态类常量获取语法&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;新的 Randomizer 扩展 API&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;类型化类常量：终于可以给常量加类型了&lt;/h2&gt;&lt;p&gt;PHP 8.2 及以前，类常量没有类型声明，任何类型都能赋值，子类覆写时也不会有类型检查。PHP 8.3 引入了类型化常量，解决了这个长期痛点。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;旧写法（PHP 8.2）：&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
class&amp;nbsp;Status&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;ACTIVE&amp;nbsp;=&amp;nbsp;1;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;没有类型，子类可以随意覆写
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;active&amp;#39;;
}

class&amp;nbsp;ExtendedStatus&amp;nbsp;extends&amp;nbsp;Status&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;ACTIVE&amp;nbsp;=&amp;nbsp;&amp;#39;yes&amp;#39;;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;PHP&amp;nbsp;8.2&amp;nbsp;不会报错，但语义混乱！
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;新写法（PHP 8.3）：&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
class&amp;nbsp;Status&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;int&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ACTIVE&amp;nbsp;=&amp;nbsp;1;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;active&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;float&amp;nbsp;&amp;nbsp;RATIO&amp;nbsp;&amp;nbsp;=&amp;nbsp;0.95;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;bool&amp;nbsp;&amp;nbsp;&amp;nbsp;IS_ENABLED&amp;nbsp;=&amp;nbsp;true;
}

//&amp;nbsp;子类覆写类型不匹配，直接报&amp;nbsp;Fatal&amp;nbsp;Error
class&amp;nbsp;ExtendedStatus&amp;nbsp;extends&amp;nbsp;Status&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;int&amp;nbsp;ACTIVE&amp;nbsp;=&amp;nbsp;&amp;#39;yes&amp;#39;;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Fatal:&amp;nbsp;Cannot&amp;nbsp;use&amp;nbsp;string&amp;nbsp;as&amp;nbsp;value&amp;nbsp;for&amp;nbsp;constant&amp;nbsp;of&amp;nbsp;type&amp;nbsp;int
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;实战建议：&lt;/strong&gt;&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;在 DTO、枚举类、配置类中优先使用类型化常量，可以在 CI 阶段就发现类型错误&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;支持的类型包括：int、float、string、bool、array、null，以及联合类型 int|string&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;接口（interface）中的常量同样支持类型声明，实现类必须遵守类型约束&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&amp;lt;?php
interface&amp;nbsp;HasVersion&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;&amp;#39;1.0.0&amp;#39;;
}

class&amp;nbsp;App&amp;nbsp;implements&amp;nbsp;HasVersion&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;&amp;#39;2.0.0&amp;#39;;&amp;nbsp;//&amp;nbsp;OK
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;const&amp;nbsp;int&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;2;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Fatal&amp;nbsp;Error
}&lt;/pre&gt;&lt;h2&gt;json_validate()：再也不用 json_decode 试错了&lt;/h2&gt;&lt;p&gt;过去验证一个字符串是否是合法 JSON，我们通常这样做：&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
//&amp;nbsp;旧方案：解码再检查
function&amp;nbsp;isValidJson(string&amp;nbsp;$json):&amp;nbsp;bool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;json_decode($json);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;json_last_error()&amp;nbsp;===&amp;nbsp;JSON_ERROR_NONE;
}&lt;/pre&gt;&lt;p&gt;这个方案有个显著缺点：json_decode 会构建完整的 PHP 数据结构，&lt;strong&gt;对于只需要验证合法性的场景，白白消耗了内存和 CPU&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;PHP 8.3 新增了 json_validate()，只做语法检查，不构建数据结构：&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
//&amp;nbsp;新方案：只验证，不解析
$payload&amp;nbsp;=&amp;nbsp;&amp;#39;{&amp;quot;user&amp;quot;:&amp;nbsp;&amp;quot;alice&amp;quot;,&amp;nbsp;&amp;quot;age&amp;quot;:&amp;nbsp;30}&amp;#39;;

if&amp;nbsp;(json_validate($payload))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$data&amp;nbsp;=&amp;nbsp;json_decode($payload,&amp;nbsp;true);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;处理数据...
}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;InvalidArgumentException(&amp;#39;Invalid&amp;nbsp;JSON&amp;nbsp;payload&amp;#39;);
}

//&amp;nbsp;也支持深度限制，防止嵌套过深的恶意输入
json_validate($payload,&amp;nbsp;depth:&amp;nbsp;5);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;最大嵌套&amp;nbsp;5&amp;nbsp;层
json_validate($payload,&amp;nbsp;flags:&amp;nbsp;JSON_INVALID_UTF8_IGNORE);&amp;nbsp;//&amp;nbsp;忽略无效&amp;nbsp;UTF-8&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;踩坑记录：&lt;/strong&gt; json_validate() 在验证失败时不会设置 json_last_error()，也没有返回错误信息。如果你需要知道具体错误，还是得用 json_decode + json_last_error_msg()。&lt;/p&gt;&lt;h2&gt;动态类常量获取：告别硬编码的常量名&lt;/h2&gt;&lt;p&gt;PHP 8.3 之前，访问类常量必须用字面量名称，无法使用变量：&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
class&amp;nbsp;Color&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;RED&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;#FF0000&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;GREEN&amp;nbsp;=&amp;nbsp;&amp;#39;#00FF00&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;BLUE&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;#0000FF&amp;#39;;
}

//&amp;nbsp;PHP&amp;nbsp;8.2：不能用变量访问常量
$name&amp;nbsp;=&amp;nbsp;&amp;#39;RED&amp;#39;;
//&amp;nbsp;echo&amp;nbsp;Color::$name;&amp;nbsp;&amp;nbsp;//&amp;nbsp;这是访问静态属性，不是常量！
constant(&amp;#39;Color::&amp;#39;&amp;nbsp;.&amp;nbsp;$name);&amp;nbsp;//&amp;nbsp;只能用&amp;nbsp;constant()&amp;nbsp;函数，略显丑陋

//&amp;nbsp;PHP&amp;nbsp;8.3：支持动态常量名
echo&amp;nbsp;Color::{$name};&amp;nbsp;//&amp;nbsp;输出&amp;nbsp;#FF0000&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;真实使用场景&lt;/strong&gt;——根据配置动态读取常量：&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
class&amp;nbsp;HttpStatus&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;int&amp;nbsp;OK&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;200;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;int&amp;nbsp;NOT_FOUND&amp;nbsp;=&amp;nbsp;404;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;int&amp;nbsp;ERROR&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;500;
}

function&amp;nbsp;getStatusCode(string&amp;nbsp;$statusName):&amp;nbsp;int&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;HttpStatus::{$statusName};
}

echo&amp;nbsp;getStatusCode(&amp;#39;OK&amp;#39;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;200
echo&amp;nbsp;getStatusCode(&amp;#39;NOT_FOUND&amp;#39;);&amp;nbsp;//&amp;nbsp;404&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt; 如果常量不存在，会抛出 Error，使用前建议加 defined() 检查或 try-catch。&lt;/p&gt;&lt;h2&gt;从 PHP 8.2 迁移的注意事项与升级步骤&lt;/h2&gt;&lt;p&gt;实际项目升级到 PHP 8.3，按以下步骤操作可以大幅降低风险：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;第一步：本地安装 PHP 8.3 并跑测试&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;Ubuntu/Debian
sudo&amp;nbsp;add-apt-repository&amp;nbsp;ppa:ondrej/php
sudo&amp;nbsp;apt&amp;nbsp;install&amp;nbsp;php8.3&amp;nbsp;php8.3-cli&amp;nbsp;php8.3-fpm&amp;nbsp;php8.3-mbstring&amp;nbsp;php8.3-xml&amp;nbsp;php8.3-curl

#&amp;nbsp;macOS&amp;nbsp;(Homebrew)
brew&amp;nbsp;install&amp;nbsp;php@8.3
brew&amp;nbsp;link&amp;nbsp;php@8.3&amp;nbsp;--force

#&amp;nbsp;验证版本
php&amp;nbsp;-v&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;第二步：检查废弃特性&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;用&amp;nbsp;PHP_CodeSniffer&amp;nbsp;扫描弃用特性
composer&amp;nbsp;require&amp;nbsp;--dev&amp;nbsp;squizlabs/php_codesniffer
./vendor/bin/phpcs&amp;nbsp;--standard=PHPCompatibility&amp;nbsp;--runtime-set&amp;nbsp;testVersion&amp;nbsp;8.3&amp;nbsp;src/&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;第三步：关注几个常见 Breaking Changes&lt;/strong&gt;&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;ReflectionProperty::setValue() 的参数已更新，第一个参数不再是可选的&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;静态变量在 trait 方法中的处理方式有所调整，多个类使用同一 trait 时每个类维护独立的静态变量&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;部分内置函数（如 str_pad）对类型参数更严格，确保传入正确类型&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;第四步：Composer 依赖更新&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;检查依赖是否支持&amp;nbsp;PHP&amp;nbsp;8.3
composer&amp;nbsp;why-not&amp;nbsp;php&amp;nbsp;8.3

#&amp;nbsp;更新全部依赖
composer&amp;nbsp;update&amp;nbsp;--with-all-dependencies&lt;/pre&gt;&lt;p&gt;按照这四步走，大多数 Laravel/Symfony 项目都能在半天内完成升级，升级后你立刻就能享受 PHP 8.3 带来的约 5~10% 的性能提升和更严格的类型安全。&lt;/p&gt;</description><pubDate>Thu, 02 Apr 2026 08:05:27 +0800</pubDate></item><item><title>Jetpack Compose 实战：从零到高性能 Android UI 的完整指南</title><link>https://blog.resmic.cn/post/225.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/03/20260331080548177491554889690.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Jetpack Compose 已经发布正式版超过两年，但很多 Android 开发者在实际项目中落地时仍然踩了不少坑：性能抖动、状态管理混乱、与旧 View 体系混用问题频发。本文基于真实项目经验，从「能跑」到「跑得好」，系统梳理 Compose 实战中的核心技巧与常见陷阱。&lt;/p&gt;&lt;h2&gt;一、为什么要用 Jetpack Compose？先看清楚再下手&lt;/h2&gt;&lt;p&gt;很多团队是&amp;quot;追风&amp;quot;式迁移到 Compose，结果踩坑后悔。在动手之前，先明确几点：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;优势：&lt;/strong&gt;声明式 UI、更少的样板代码、与 Kotlin 协程天然配合、热重载（Live Edit）支持&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;当前局限：&lt;/strong&gt;部分动画复杂场景性能弱于 View、与部分第三方 View 库兼容性差、编译速度较慢&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;适合场景：&lt;/strong&gt;新项目、纯 Kotlin 栈、以列表/表单/详情页为主的 UI&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如果你的项目大量依赖 Google Maps、WebView、自定义 SurfaceView，短期内不建议强行全迁移。&lt;/p&gt;&lt;h2&gt;二、项目结构设计：从 MVI 架构角度组织 Compose 代码&lt;/h2&gt;&lt;p&gt;Compose 最适合配合 MVI（Model-View-Intent）架构使用。推荐目录结构如下：&lt;/p&gt;&lt;pre&gt;app/
├──&amp;nbsp;ui/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;screen/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;home/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;HomeScreen.kt&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;UI&amp;nbsp;入口，只负责组合组件
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;HomeViewModel.kt&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;持有&amp;nbsp;UiState，处理事件
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;HomeUiState.kt&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;密封类定义页面状态
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;detail/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;component/&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;可复用的原子组件
│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;theme/&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;主题、颜色、字体
├──&amp;nbsp;domain/&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;业务逻辑层（纯&amp;nbsp;Kotlin）
└──&amp;nbsp;data/&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;数据层&lt;/pre&gt;&lt;p&gt;定义 UiState 时用 sealed class 或 data class：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;HomeUiState.kt
sealed&amp;nbsp;class&amp;nbsp;HomeUiState&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;object&amp;nbsp;Loading&amp;nbsp;:&amp;nbsp;HomeUiState()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;class&amp;nbsp;Success(val&amp;nbsp;items:&amp;nbsp;List&amp;lt;ArticleItem&amp;gt;)&amp;nbsp;:&amp;nbsp;HomeUiState()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;class&amp;nbsp;Error(val&amp;nbsp;message:&amp;nbsp;String)&amp;nbsp;:&amp;nbsp;HomeUiState()
}

//&amp;nbsp;HomeViewModel.kt
class&amp;nbsp;HomeViewModel(private&amp;nbsp;val&amp;nbsp;repo:&amp;nbsp;ArticleRepository)&amp;nbsp;:&amp;nbsp;ViewModel()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;_uiState&amp;nbsp;=&amp;nbsp;MutableStateFlow&amp;lt;HomeUiState&amp;gt;(HomeUiState.Loading)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;uiState:&amp;nbsp;StateFlow&amp;lt;HomeUiState&amp;gt;&amp;nbsp;=&amp;nbsp;_uiState.asStateFlow()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;loadArticles()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;loadArticles()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModelScope.launch&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_uiState.value&amp;nbsp;=&amp;nbsp;HomeUiState.Loading
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;items&amp;nbsp;=&amp;nbsp;repo.getArticles()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_uiState.value&amp;nbsp;=&amp;nbsp;HomeUiState.Success(items)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(e:&amp;nbsp;Exception)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_uiState.value&amp;nbsp;=&amp;nbsp;HomeUiState.Error(e.message&amp;nbsp;?:&amp;nbsp;&amp;quot;未知错误&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;Screen 层只负责消费状态，不写任何业务逻辑：&lt;/p&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;HomeScreen(viewModel:&amp;nbsp;HomeViewModel&amp;nbsp;=&amp;nbsp;hiltViewModel())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;uiState&amp;nbsp;by&amp;nbsp;viewModel.uiState.collectAsStateWithLifecycle()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;when&amp;nbsp;(uiState)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;HomeUiState.Loading&amp;nbsp;-&amp;gt;&amp;nbsp;LoadingIndicator()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;HomeUiState.Success&amp;nbsp;-&amp;gt;&amp;nbsp;ArticleList(items&amp;nbsp;=&amp;nbsp;(uiState&amp;nbsp;as&amp;nbsp;HomeUiState.Success).items)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;HomeUiState.Error&amp;nbsp;-&amp;gt;&amp;nbsp;ErrorView(message&amp;nbsp;=&amp;nbsp;(uiState&amp;nbsp;as&amp;nbsp;HomeUiState.Error).message)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModel.loadArticles()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;三、性能优化：避免不必要的重组（Recomposition）&lt;/h2&gt;&lt;p&gt;这是 Compose 实战中最容易踩的坑。每次状态变化都可能触发重组，如果组件设计不当，会导致严重的性能问题。&lt;/p&gt;&lt;h3&gt;3.1 用 remember 和 derivedStateOf 缓存计算&lt;/h3&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;错误写法：每次重组都重新计算
@Composable
fun&amp;nbsp;ArticleList(items:&amp;nbsp;List&amp;lt;ArticleItem&amp;gt;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;sortedItems&amp;nbsp;=&amp;nbsp;items.sortedByDescending&amp;nbsp;{&amp;nbsp;it.publishTime&amp;nbsp;}&amp;nbsp;//&amp;nbsp;每次重组都排序！
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(sortedItems)&amp;nbsp;{&amp;nbsp;ArticleCard(it)&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;✅&amp;nbsp;正确写法：用&amp;nbsp;remember&amp;nbsp;缓存，derivedStateOf&amp;nbsp;监听依赖变化
@Composable
fun&amp;nbsp;ArticleList(items:&amp;nbsp;List&amp;lt;ArticleItem&amp;gt;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;sortedItems&amp;nbsp;by&amp;nbsp;remember(items)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;derivedStateOf&amp;nbsp;{&amp;nbsp;items.sortedByDescending&amp;nbsp;{&amp;nbsp;it.publishTime&amp;nbsp;}&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(sortedItems,&amp;nbsp;key&amp;nbsp;=&amp;nbsp;{&amp;nbsp;it.id&amp;nbsp;})&amp;nbsp;{&amp;nbsp;ArticleCard(it)&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;3.2 stable 和 immutable 注解减少重组范围&lt;/h3&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;List&amp;nbsp;是不稳定类型，会导致父组件重组时子组件一起重组
data&amp;nbsp;class&amp;nbsp;UserProfile(val&amp;nbsp;name:&amp;nbsp;String,&amp;nbsp;val&amp;nbsp;tags:&amp;nbsp;List&amp;lt;String&amp;gt;)

//&amp;nbsp;✅&amp;nbsp;使用&amp;nbsp;@Immutable&amp;nbsp;标注数据类为稳定类型
@Immutable
data&amp;nbsp;class&amp;nbsp;UserProfile(val&amp;nbsp;name:&amp;nbsp;String,&amp;nbsp;val&amp;nbsp;tags:&amp;nbsp;List&amp;lt;String&amp;gt;)&lt;/pre&gt;&lt;p&gt;或者使用 Kotlin 不可变集合库 `kotlinx.collections.immutable`：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;build.gradle.kts
implementation(&amp;quot;org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7&amp;quot;)

//&amp;nbsp;使用
data&amp;nbsp;class&amp;nbsp;UserProfile(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;name:&amp;nbsp;String,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;tags:&amp;nbsp;ImmutableList&amp;lt;String&amp;gt;&amp;nbsp;&amp;nbsp;//&amp;nbsp;稳定类型，Compose&amp;nbsp;可感知
)&lt;/pre&gt;&lt;h3&gt;3.3 用 Layout Inspector 实际测量重组次数&lt;/h3&gt;&lt;p&gt;Android Studio 的 Layout Inspector 可以实时显示每个 Composable 的重组次数。步骤：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;运行 Debug 包，连接设备&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;菜单：View → Tool Windows → Layout Inspector&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;勾选「Show recomposition counts」&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;操作 UI，观察红色高亮（重组频繁）的组件&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;四、与旧 View 体系混用：AndroidView 和 ComposeView 的正确姿势&lt;/h2&gt;&lt;p&gt;实际项目中完全不用 View 几乎不可能，以下是混用的标准写法：&lt;/p&gt;&lt;h3&gt;4.1 在 Compose 中嵌入 View：AndroidView&lt;/h3&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;WebViewComposable(url:&amp;nbsp;String)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AndroidView(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory&amp;nbsp;=&amp;nbsp;{&amp;nbsp;context&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WebView(context).apply&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;webViewClient&amp;nbsp;=&amp;nbsp;WebViewClient()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;settings.javaScriptEnabled&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;update&amp;nbsp;=&amp;nbsp;{&amp;nbsp;webView&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;update&amp;nbsp;在每次重组时调用，注意避免重复加载
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;webView.loadUrl(url)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.fillMaxSize()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
}&lt;/pre&gt;&lt;p&gt;⚠️ 注意：`update` lambda 会在每次 Composable 重组时执行，如果 `url` 没变化却重新 loadUrl 会导致页面闪烁。应该用 `LaunchedEffect` 或在 update 中判断：&lt;/p&gt;&lt;pre&gt;update&amp;nbsp;=&amp;nbsp;{&amp;nbsp;webView&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(webView.url&amp;nbsp;!=&amp;nbsp;url)&amp;nbsp;{&amp;nbsp;&amp;nbsp;//&amp;nbsp;只有&amp;nbsp;URL&amp;nbsp;变化才加载
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;webView.loadUrl(url)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;4.2 在 XML 布局中嵌入 Compose：ComposeView&lt;/h3&gt;&lt;pre&gt;&amp;lt;!--&amp;nbsp;fragment_profile.xml&amp;nbsp;--&amp;gt;
&amp;lt;LinearLayout&amp;nbsp;...&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;TextView&amp;nbsp;android:id=&amp;quot;@+id/title&amp;quot;&amp;nbsp;...&amp;nbsp;/&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;androidx.compose.ui.platform.ComposeView
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:id=&amp;quot;@+id/compose_view&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_width=&amp;quot;match_parent&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_height=&amp;quot;wrap_content&amp;quot;&amp;nbsp;/&amp;gt;
&amp;lt;/LinearLayout&amp;gt;&lt;/pre&gt;&lt;pre&gt;//&amp;nbsp;ProfileFragment.kt
override&amp;nbsp;fun&amp;nbsp;onViewCreated(view:&amp;nbsp;View,&amp;nbsp;savedInstanceState:&amp;nbsp;Bundle?)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;binding.composeView.apply&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setContent&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MaterialTheme&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ProfileCard(viewModel&amp;nbsp;=&amp;nbsp;viewModel)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;⚠️ 必须设置 `setViewCompositionStrategy`，否则在 Fragment 中会有内存泄漏风险。&lt;/p&gt;&lt;h2&gt;五、导航：Navigation Compose 实战与深链接配置&lt;/h2&gt;&lt;p&gt;推荐用 Navigation Compose 统一管理路由，定义路由常量避免字符串硬编码：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;NavRoutes.kt
object&amp;nbsp;NavRoutes&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;val&amp;nbsp;HOME&amp;nbsp;=&amp;nbsp;&amp;quot;home&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;val&amp;nbsp;ARTICLE_DETAIL&amp;nbsp;=&amp;nbsp;&amp;quot;article/{articleId}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;val&amp;nbsp;PROFILE&amp;nbsp;=&amp;nbsp;&amp;quot;profile&amp;quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;articleDetail(id:&amp;nbsp;String)&amp;nbsp;=&amp;nbsp;&amp;quot;article/$id&amp;quot;
}

//&amp;nbsp;AppNavGraph.kt
@Composable
fun&amp;nbsp;AppNavGraph(navController:&amp;nbsp;NavHostController)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NavHost(navController,&amp;nbsp;startDestination&amp;nbsp;=&amp;nbsp;NavRoutes.HOME)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;composable(NavRoutes.HOME)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;HomeScreen(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onArticleClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;id&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;navController.navigate(NavRoutes.articleDetail(id))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;composable(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;route&amp;nbsp;=&amp;nbsp;NavRoutes.ARTICLE_DETAIL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;arguments&amp;nbsp;=&amp;nbsp;listOf(navArgument(&amp;quot;articleId&amp;quot;)&amp;nbsp;{&amp;nbsp;type&amp;nbsp;=&amp;nbsp;NavType.StringType&amp;nbsp;}),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;deepLinks&amp;nbsp;=&amp;nbsp;listOf(navDeepLink&amp;nbsp;{&amp;nbsp;uriPattern&amp;nbsp;=&amp;nbsp;&amp;quot;myapp://article/{articleId}&amp;quot;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{&amp;nbsp;backStackEntry&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;articleId&amp;nbsp;=&amp;nbsp;backStackEntry.arguments?.getString(&amp;quot;articleId&amp;quot;)&amp;nbsp;?:&amp;nbsp;return@composable
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ArticleDetailScreen(articleId&amp;nbsp;=&amp;nbsp;articleId)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;六、主题系统：Material3 动态配色的正确接入方式&lt;/h2&gt;&lt;pre&gt;//&amp;nbsp;Theme.kt
@Composable
fun&amp;nbsp;AppTheme(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;darkTheme:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;isSystemInDarkTheme(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dynamicColor:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;true,&amp;nbsp;&amp;nbsp;//&amp;nbsp;Android&amp;nbsp;12+&amp;nbsp;动态配色
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content:&amp;nbsp;@Composable&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit
)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;colorScheme&amp;nbsp;=&amp;nbsp;when&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dynamicColor&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;Build.VERSION.SDK_INT&amp;nbsp;&amp;gt;=&amp;nbsp;Build.VERSION_CODES.S&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;context&amp;nbsp;=&amp;nbsp;LocalContext.current
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(darkTheme)&amp;nbsp;dynamicDarkColorScheme(context)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&amp;nbsp;dynamicLightColorScheme(context)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;darkTheme&amp;nbsp;-&amp;gt;&amp;nbsp;DarkColorScheme
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&amp;nbsp;-&amp;gt;&amp;nbsp;LightColorScheme
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MaterialTheme(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;colorScheme&amp;nbsp;=&amp;nbsp;colorScheme,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;typography&amp;nbsp;=&amp;nbsp;AppTypography,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content&amp;nbsp;=&amp;nbsp;content
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
}&lt;/pre&gt;&lt;h2&gt;七、实战踩坑总结与最佳实践清单&lt;/h2&gt;&lt;p&gt;以下是从真实项目中整理的高频坑点：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;❌ 不要在 Composable 函数中直接创建 ViewModel（用 hiltViewModel() 或 viewModel()）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;❌ 不要在 Composable 中直接读写 SharedPreferences（应该通过 ViewModel/Repository 层）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;❌ 不要用 GlobalScope 启动协程（Compose 侧用 LaunchedEffect，ViewModel 用 viewModelScope）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ LazyColumn 的 items 必须提供 key 参数，避免列表更新时的全量重组&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 图片加载推荐 Coil（完整 Compose 支持）：&lt;code&gt;AsyncImage(model = url, contentDescription = null)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 需要访问 Context 时用 &lt;code&gt;LocalContext.current&lt;/code&gt;，不要把 Context 传入 ViewModel&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 列表分页用 Paging 3 + &lt;code&gt;collectAsLazyPagingItems()&lt;/code&gt;，官方 Compose 扩展很成熟&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;//&amp;nbsp;分页列表标准写法
@Composable
fun&amp;nbsp;ArticlePagingList(viewModel:&amp;nbsp;HomeViewModel&amp;nbsp;=&amp;nbsp;hiltViewModel())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;pagingItems&amp;nbsp;=&amp;nbsp;viewModel.articlePager.collectAsLazyPagingItems()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;count&amp;nbsp;=&amp;nbsp;pagingItems.itemCount,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;pagingItems.itemKey&amp;nbsp;{&amp;nbsp;it.id&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{&amp;nbsp;index&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;item&amp;nbsp;=&amp;nbsp;pagingItems[index]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(item&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;ArticleCard(item)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&amp;nbsp;ArticleCardPlaceholder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;when&amp;nbsp;(val&amp;nbsp;loadState&amp;nbsp;=&amp;nbsp;pagingItems.loadState.append)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;LoadState.Loading&amp;nbsp;-&amp;gt;&amp;nbsp;item&amp;nbsp;{&amp;nbsp;CircularProgressIndicator()&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;LoadState.Error&amp;nbsp;-&amp;gt;&amp;nbsp;item&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RetryButton(onClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;pagingItems.retry()&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&amp;nbsp;-&amp;gt;&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;Jetpack Compose 的学习曲线主要在于思维转换，从「如何操作 View」变为「如何描述状态」。遇到性能问题不要慌，先用 Layout Inspector 定位重组热点，再针对性优化。实际上大多数日常 UI 场景，按本文的结构设计走，不做任何特殊优化也能有很好的性能表现。&lt;/p&gt;</description><pubDate>Tue, 31 Mar 2026 08:07:23 +0800</pubDate></item><item><title>PHP 项目重构实战：Repository + Service + Controller 三层架构落地指南</title><link>https://blog.resmic.cn/post/224.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/03/20260329080330177474261098670.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;为什么你的 PHP 代码越来越难维护？&lt;/h2&gt;&lt;p&gt;很多 PHP 项目在初期写得飞快，但随着功能迭代，代码库逐渐变成一个&amp;quot;屎山&amp;quot;——数据库查询散落在各处、业务逻辑和展示层混在一起、测试几乎无从下手。这不是 PHP 语言的问题，而是缺少分层架构的必然结果。&lt;/p&gt;&lt;p&gt;本文将以一个电商订单模块为例，手把手带你把一段典型的&amp;quot;意大利面条&amp;quot;代码重构为清晰的三层架构（Repository + Service + Controller），让你的代码可读、可测、可维护。&lt;/p&gt;&lt;h2&gt;第一步：认识问题——原始代码长什么样&lt;/h2&gt;&lt;p&gt;先看一段典型的&amp;quot;一把梭&amp;quot;PHP 代码，很多中小项目都长这样：&lt;/p&gt;&lt;pre&gt;&amp;lt;?php//&amp;nbsp;OrderController.php（反面教材）class&amp;nbsp;OrderController&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;create()&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$db&amp;nbsp;=&amp;nbsp;new&amp;nbsp;PDO(&amp;#39;mysql:host=localhost;dbname=shop&amp;#39;,&amp;nbsp;&amp;#39;root&amp;#39;,&amp;nbsp;&amp;#39;123456&amp;#39;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$userId&amp;nbsp;=&amp;nbsp;$_POST[&amp;#39;user_id&amp;#39;];&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$productId&amp;nbsp;=&amp;nbsp;$_POST[&amp;#39;product_id&amp;#39;];&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$qty&amp;nbsp;=&amp;nbsp;(int)$_POST[&amp;#39;qty&amp;#39;];&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;直接查库存&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$stmt&amp;nbsp;=&amp;nbsp;$db-&amp;gt;prepare(&amp;quot;SELECT&amp;nbsp;stock&amp;nbsp;FROM&amp;nbsp;products&amp;nbsp;WHERE&amp;nbsp;id&amp;nbsp;=&amp;nbsp;?&amp;quot;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$stmt-&amp;gt;execute([$productId]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$product&amp;nbsp;=&amp;nbsp;$stmt-&amp;gt;fetch();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($product[&amp;#39;stock&amp;#39;]&amp;nbsp;&amp;lt;&amp;nbsp;$qty)&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;json_encode([&amp;#39;error&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;库存不足&amp;#39;]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;扣库存&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$db-&amp;gt;prepare(&amp;quot;UPDATE&amp;nbsp;products&amp;nbsp;SET&amp;nbsp;stock&amp;nbsp;=&amp;nbsp;stock&amp;nbsp;-&amp;nbsp;?&amp;nbsp;WHERE&amp;nbsp;id&amp;nbsp;=&amp;nbsp;?&amp;quot;)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;gt;execute([$qty,&amp;nbsp;$productId]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;写订单&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$db-&amp;gt;prepare(&amp;quot;INSERT&amp;nbsp;INTO&amp;nbsp;orders&amp;nbsp;(user_id,&amp;nbsp;product_id,&amp;nbsp;qty,&amp;nbsp;status)&amp;nbsp;VALUES&amp;nbsp;(?,?,?,&amp;#39;pending&amp;#39;)&amp;quot;)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;gt;execute([$userId,&amp;nbsp;$productId,&amp;nbsp;$qty]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;发邮件（直接塞在这里）&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mail(&amp;#39;admin@example.com&amp;#39;,&amp;nbsp;&amp;#39;新订单&amp;#39;,&amp;nbsp;&amp;quot;用户{$userId}下单了{$qty}个商品{$productId}&amp;quot;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;json_encode([&amp;#39;success&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;true]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}}&lt;/pre&gt;&lt;p&gt;这段代码有哪些问题？&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;数据库连接硬编码，密码暴露在代码里&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;SQL 语句散落在 Controller，无法复用&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;业务逻辑（库存校验、扣减）和 HTTP 层混在一起&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;发邮件是副作用，单元测试根本无法隔离&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;任何改动都可能牵一发而动全身&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;第二步：分层设计——Repository + Service + Controller&lt;/h2&gt;&lt;p&gt;我们采用经典的三层架构：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repository 层&lt;/strong&gt;：只负责数据库操作，封装所有 SQL，返回领域对象或数组&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service 层&lt;/strong&gt;：核心业务逻辑，编排 Repository，处理事务和规则&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Controller 层&lt;/strong&gt;：只负责解析 HTTP 请求、调用 Service、返回响应&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;目录结构如下：&lt;/p&gt;&lt;pre&gt;app/├──&amp;nbsp;Controllers/│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;OrderController.php├──&amp;nbsp;Services/│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;OrderService.php├──&amp;nbsp;Repositories/│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;OrderRepository.php│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;ProductRepository.php└──&amp;nbsp;Models/&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;Order.php&lt;/pre&gt;&lt;h2&gt;第三步：实现 Repository 层&lt;/h2&gt;&lt;p&gt;Repository 只做一件事：数据库读写。它不包含任何业务逻辑。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php//&amp;nbsp;Repositories/ProductRepository.phpclass&amp;nbsp;ProductRepository&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(private&amp;nbsp;PDO&amp;nbsp;$db)&amp;nbsp;{}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;findById(int&amp;nbsp;$id):&amp;nbsp;?array&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$stmt&amp;nbsp;=&amp;nbsp;$this-&amp;gt;db-&amp;gt;prepare(&amp;quot;SELECT&amp;nbsp;*&amp;nbsp;FROM&amp;nbsp;products&amp;nbsp;WHERE&amp;nbsp;id&amp;nbsp;=&amp;nbsp;?&amp;quot;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$stmt-&amp;gt;execute([$id]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;$stmt-&amp;gt;fetch(PDO::FETCH_ASSOC)&amp;nbsp;?:&amp;nbsp;null;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;decrementStock(int&amp;nbsp;$productId,&amp;nbsp;int&amp;nbsp;$qty):&amp;nbsp;void&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;db-&amp;gt;prepare(&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;UPDATE&amp;nbsp;products&amp;nbsp;SET&amp;nbsp;stock&amp;nbsp;=&amp;nbsp;stock&amp;nbsp;-&amp;nbsp;?&amp;nbsp;WHERE&amp;nbsp;id&amp;nbsp;=&amp;nbsp;?&amp;nbsp;AND&amp;nbsp;stock&amp;nbsp;&amp;gt;=&amp;nbsp;?&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)-&amp;gt;execute([$qty,&amp;nbsp;$productId,&amp;nbsp;$qty]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}}//&amp;nbsp;Repositories/OrderRepository.phpclass&amp;nbsp;OrderRepository&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(private&amp;nbsp;PDO&amp;nbsp;$db)&amp;nbsp;{}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;create(int&amp;nbsp;$userId,&amp;nbsp;int&amp;nbsp;$productId,&amp;nbsp;int&amp;nbsp;$qty):&amp;nbsp;int&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$stmt&amp;nbsp;=&amp;nbsp;$this-&amp;gt;db-&amp;gt;prepare(&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;INSERT&amp;nbsp;INTO&amp;nbsp;orders&amp;nbsp;(user_id,&amp;nbsp;product_id,&amp;nbsp;qty,&amp;nbsp;status,&amp;nbsp;created_at)&amp;nbsp;VALUES&amp;nbsp;(?,?,?,&amp;#39;pending&amp;#39;,NOW())&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$stmt-&amp;gt;execute([$userId,&amp;nbsp;$productId,&amp;nbsp;$qty]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(int)$this-&amp;gt;db-&amp;gt;lastInsertId();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;踩坑记录&lt;/strong&gt;：&lt;code&gt;decrementStock&lt;/code&gt; 中故意在 WHERE 加了 &lt;code&gt;AND stock &amp;gt;= ?&lt;/code&gt;，这是一个乐观锁技巧——如果并发场景下库存已被其他请求扣掉，这条 SQL 会影响 0 行，配合 &lt;code&gt;rowCount()&lt;/code&gt; 检查可以避免超卖。&lt;/p&gt;&lt;h2&gt;第四步：实现 Service 层&lt;/h2&gt;&lt;p&gt;Service 层是业务大脑，负责编排 Repository 并处理事务：&lt;/p&gt;&lt;pre&gt;&amp;lt;?php//&amp;nbsp;Services/OrderService.phpclass&amp;nbsp;OrderService&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;PDO&amp;nbsp;$db,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;ProductRepository&amp;nbsp;$productRepo,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;OrderRepository&amp;nbsp;$orderRepo,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;MailerInterface&amp;nbsp;$mailer&amp;nbsp;&amp;nbsp;//&amp;nbsp;依赖注入，方便&amp;nbsp;mock&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;createOrder(int&amp;nbsp;$userId,&amp;nbsp;int&amp;nbsp;$productId,&amp;nbsp;int&amp;nbsp;$qty):&amp;nbsp;int&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$product&amp;nbsp;=&amp;nbsp;$this-&amp;gt;productRepo-&amp;gt;findById($productId);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!$product)&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;\RuntimeException(&amp;#39;商品不存在&amp;#39;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($product[&amp;#39;stock&amp;#39;]&amp;nbsp;&amp;lt;&amp;nbsp;$qty)&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;\DomainException(&amp;#39;库存不足，当前库存：&amp;#39;&amp;nbsp;.&amp;nbsp;$product[&amp;#39;stock&amp;#39;]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;db-&amp;gt;beginTransaction();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;productRepo-&amp;gt;decrementStock($productId,&amp;nbsp;$qty);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;验证扣减是否成功（防超卖）&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$updated&amp;nbsp;=&amp;nbsp;$this-&amp;gt;db-&amp;gt;query(&amp;quot;SELECT&amp;nbsp;ROW_COUNT()&amp;quot;)-&amp;gt;fetchColumn();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($updated&amp;nbsp;==&amp;nbsp;0)&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;\DomainException(&amp;#39;库存扣减失败，请重试&amp;#39;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$orderId&amp;nbsp;=&amp;nbsp;$this-&amp;gt;orderRepo-&amp;gt;create($userId,&amp;nbsp;$productId,&amp;nbsp;$qty);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;db-&amp;gt;commit();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;发通知（在事务外，失败不影响下单）&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;mailer-&amp;gt;send(&amp;#39;admin@example.com&amp;#39;,&amp;nbsp;&amp;#39;新订单&amp;#39;,&amp;nbsp;&amp;quot;订单#{$orderId}已创建&amp;quot;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;$orderId;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(\Exception&amp;nbsp;$e)&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&amp;gt;db-&amp;gt;rollBack();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;$e;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;关键设计点&lt;/strong&gt;：发邮件放在 &lt;code&gt;commit()&lt;/code&gt; 之后、事务之外，这样邮件失败不会导致订单回滚。&lt;/p&gt;&lt;h2&gt;第五步：精简 Controller&lt;/h2&gt;&lt;p&gt;重构后的 Controller 只做三件事：解析请求、调用 Service、返回响应。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php//&amp;nbsp;Controllers/OrderController.phpclass&amp;nbsp;OrderController&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(private&amp;nbsp;OrderService&amp;nbsp;$orderService)&amp;nbsp;{}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;create(Request&amp;nbsp;$request):&amp;nbsp;Response&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$orderId&amp;nbsp;=&amp;nbsp;$this-&amp;gt;orderService-&amp;gt;createOrder(&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userId:&amp;nbsp;(int)$request-&amp;gt;input(&amp;#39;user_id&amp;#39;),&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;productId:&amp;nbsp;(int)$request-&amp;gt;input(&amp;#39;product_id&amp;#39;),&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;qty:&amp;nbsp;(int)$request-&amp;gt;input(&amp;#39;qty&amp;#39;)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response::json([&amp;#39;order_id&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;$orderId],&amp;nbsp;201);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(\DomainException&amp;nbsp;$e)&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response::json([&amp;#39;error&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;$e-&amp;gt;getMessage()],&amp;nbsp;422);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(\Exception&amp;nbsp;$e)&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;记录日志，不暴露内部错误&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger()-&amp;gt;error(&amp;#39;下单失败&amp;#39;,&amp;nbsp;[&amp;#39;exception&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;$e]);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response::json([&amp;#39;error&amp;#39;&amp;nbsp;=&amp;gt;&amp;nbsp;&amp;#39;服务异常，请稍后重试&amp;#39;],&amp;nbsp;500);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}}&lt;/pre&gt;&lt;p&gt;注意异常处理的分层：&lt;code&gt;DomainException&lt;/code&gt; 是业务异常，直接告知用户；其他未知异常记录日志后返回通用错误，绝不暴露内部堆栈给客户端。&lt;/p&gt;&lt;h2&gt;重构前后对比与总结&lt;/h2&gt;&lt;p&gt;经过这次重构，代码的可维护性有了质的提升。以下是直观对比：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;可测试性&lt;/strong&gt;：Service 层通过依赖注入，单元测试时可以 mock Repository 和 Mailer，无需真实数据库&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;可复用性&lt;/strong&gt;：&lt;code&gt;ProductRepository::findById&lt;/code&gt; 可以被多个 Service 复用，不再重复写 SQL&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;可读性&lt;/strong&gt;：每个类职责单一，新人接手代码时一眼知道去哪里找什么&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;防超卖&lt;/strong&gt;：通过乐观锁 + ROW_COUNT 双重保障，解决了原始代码中的并发超卖问题&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这套架构不依赖任何框架，在原生 PHP 项目或 Laravel、Symfony 等框架中均可应用。下一步可以继续引入 DTO（Data Transfer Object）来规范数据传递，或者配合 PHP 8.1 的枚举类型来强化订单状态管理。&lt;/p&gt;&lt;p&gt;好的架构不是一步到位的，但每一次小重构都在让代码变得更健康。&lt;/p&gt;</description><pubDate>Sun, 29 Mar 2026 08:04:59 +0800</pubDate></item><item><title>Android Jetpack Compose 实战：从零构建现代化 UI 组件库</title><link>https://blog.resmic.cn/post/223.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/03/20260328080531177465633153735.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Jetpack Compose 已经成为 Android 开发的主流 UI 框架，Google 官方也在不断推动开发者从传统 View 体系迁移到 Compose。然而，很多团队在实际落地时面临一个共同问题：如何设计一套可维护、可复用的组件库，而不是到处复制粘贴代码？&lt;/p&gt;&lt;p&gt;本文将带你从零构建一套团队级的 Compose UI 组件库，涵盖主题系统、常用组件封装、动画效果和性能踩坑。所有代码均基于 Compose 1.6.x + Material3，可直接应用到生产项目。&lt;/p&gt;&lt;h2&gt;一、项目结构与模块划分&lt;/h2&gt;&lt;p&gt;在开始写组件之前，先确定好目录结构。一个合理的组件库模块应该像这样组织：&lt;/p&gt;&lt;pre&gt;ui-components/
├──&amp;nbsp;src/main/java/com/yourapp/ui/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;theme/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;Color.kt&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;颜色系统
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;Typography.kt&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;字体系统
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;Shape.kt&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;形状系统
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;Theme.kt&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;主题入口
│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;components/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;button/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;AppButton.kt
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;IconButton.kt
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;card/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;AppCard.kt
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;input/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;AppTextField.kt
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;SearchBar.kt
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;feedback/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;AppToast.kt
│&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;LoadingOverlay.kt
│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;preview/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;ComponentPreview.kt&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;统一&amp;nbsp;Preview&amp;nbsp;配置&lt;/pre&gt;&lt;p&gt;这种结构的好处是：每个组件有独立目录，便于多人协作；theme 层统一管理设计 token；preview 集中管理，方便 UI Review。&lt;/p&gt;&lt;h2&gt;二、主题系统设计——设计 Token 的正确实践&lt;/h2&gt;&lt;p&gt;很多项目直接用硬编码的颜色值，这在换肤或品牌升级时会是噩梦。正确做法是建立设计 Token 体系：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;Color.kt
val&amp;nbsp;Primary&amp;nbsp;=&amp;nbsp;Color(0xFF1976D2)
val&amp;nbsp;PrimaryContainer&amp;nbsp;=&amp;nbsp;Color(0xFFBBDEFB)
val&amp;nbsp;OnPrimary&amp;nbsp;=&amp;nbsp;Color(0xFFFFFFFF)
val&amp;nbsp;Error&amp;nbsp;=&amp;nbsp;Color(0xFFB00020)
val&amp;nbsp;Surface&amp;nbsp;=&amp;nbsp;Color(0xFFFAFAFA)

//&amp;nbsp;定义完整的&amp;nbsp;ColorScheme
val&amp;nbsp;LightColorScheme&amp;nbsp;=&amp;nbsp;lightColorScheme(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;primary&amp;nbsp;=&amp;nbsp;Primary,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;primaryContainer&amp;nbsp;=&amp;nbsp;PrimaryContainer,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onPrimary&amp;nbsp;=&amp;nbsp;OnPrimary,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error&amp;nbsp;=&amp;nbsp;Error,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;surface&amp;nbsp;=&amp;nbsp;Surface,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;...&amp;nbsp;其他颜色
)

val&amp;nbsp;DarkColorScheme&amp;nbsp;=&amp;nbsp;darkColorScheme(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;primary&amp;nbsp;=&amp;nbsp;Color(0xFF90CAF9),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;primaryContainer&amp;nbsp;=&amp;nbsp;Color(0xFF1565C0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onPrimary&amp;nbsp;=&amp;nbsp;Color(0xFF003087),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;...&amp;nbsp;暗色适配
)&lt;/pre&gt;&lt;pre&gt;//&amp;nbsp;Theme.kt
@Composable
fun&amp;nbsp;AppTheme(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;darkTheme:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;isSystemInDarkTheme(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dynamicColor:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;false,&amp;nbsp;&amp;nbsp;//&amp;nbsp;Android&amp;nbsp;12+&amp;nbsp;动态取色
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content:&amp;nbsp;@Composable&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit
)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;colorScheme&amp;nbsp;=&amp;nbsp;when&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dynamicColor&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;Build.VERSION.SDK_INT&amp;nbsp;&amp;gt;=&amp;nbsp;Build.VERSION_CODES.S&amp;nbsp;-&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;context&amp;nbsp;=&amp;nbsp;LocalContext.current
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(darkTheme)&amp;nbsp;dynamicDarkColorScheme(context)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&amp;nbsp;dynamicLightColorScheme(context)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;darkTheme&amp;nbsp;-&amp;gt;&amp;nbsp;DarkColorScheme
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&amp;nbsp;-&amp;gt;&amp;nbsp;LightColorScheme
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MaterialTheme(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;colorScheme&amp;nbsp;=&amp;nbsp;colorScheme,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;typography&amp;nbsp;=&amp;nbsp;AppTypography,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shapes&amp;nbsp;=&amp;nbsp;AppShapes,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content&amp;nbsp;=&amp;nbsp;content
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;踩坑记录&lt;/strong&gt;：不要直接在组件内调用 &lt;code&gt;MaterialTheme.colorScheme.primary&lt;/code&gt; 来硬编码组件颜色，而是通过参数传入或使用 &lt;code&gt;LocalContentColor&lt;/code&gt;。这样组件才能真正跟随主题变化。&lt;/p&gt;&lt;h2&gt;三、Button 组件封装——状态与样式分离&lt;/h2&gt;&lt;p&gt;官方的 &lt;code&gt;Button&lt;/code&gt; 已经足够好用，但在项目中往往需要统一 loading 状态、disabled 样式和点击防抖。来看封装后的效果：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;AppButton.kt
@Composable
fun&amp;nbsp;AppButton(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text:&amp;nbsp;String,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onClick:&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier:&amp;nbsp;Modifier&amp;nbsp;=&amp;nbsp;Modifier,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;loading:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;buttonType:&amp;nbsp;ButtonType&amp;nbsp;=&amp;nbsp;ButtonType.Primary,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;leadingIcon:&amp;nbsp;ImageVector?&amp;nbsp;=&amp;nbsp;null
)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;containerColor&amp;nbsp;=&amp;nbsp;when&amp;nbsp;(buttonType)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ButtonType.Primary&amp;nbsp;-&amp;gt;&amp;nbsp;MaterialTheme.colorScheme.primary
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ButtonType.Secondary&amp;nbsp;-&amp;gt;&amp;nbsp;MaterialTheme.colorScheme.secondary
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ButtonType.Danger&amp;nbsp;-&amp;gt;&amp;nbsp;MaterialTheme.colorScheme.error
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ButtonType.Ghost&amp;nbsp;-&amp;gt;&amp;nbsp;Color.Transparent
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Button(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;onClick,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;modifier.defaultMinSize(minHeight&amp;nbsp;=&amp;nbsp;48.dp),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled&amp;nbsp;=&amp;nbsp;enabled&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;!loading,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;colors&amp;nbsp;=&amp;nbsp;ButtonDefaults.buttonColors(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containerColor&amp;nbsp;=&amp;nbsp;containerColor,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;disabledContainerColor&amp;nbsp;=&amp;nbsp;containerColor.copy(alpha&amp;nbsp;=&amp;nbsp;0.38f)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shape&amp;nbsp;=&amp;nbsp;MaterialTheme.shapes.medium
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AnimatedContent(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetState&amp;nbsp;=&amp;nbsp;loading,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;transitionSpec&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fadeIn()&amp;nbsp;togetherWith&amp;nbsp;fadeOut()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;label&amp;nbsp;=&amp;nbsp;&amp;quot;button_content&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{&amp;nbsp;isLoading&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(isLoading)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CircularProgressIndicator(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.size(18.dp),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color&amp;nbsp;=&amp;nbsp;contentColorFor(containerColor),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;strokeWidth&amp;nbsp;=&amp;nbsp;2.dp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Row(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;verticalAlignment&amp;nbsp;=&amp;nbsp;Alignment.CenterVertically,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;horizontalArrangement&amp;nbsp;=&amp;nbsp;Arrangement.spacedBy(8.dp)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;leadingIcon?.let&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Icon(imageVector&amp;nbsp;=&amp;nbsp;it,&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;null,&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.size(18.dp))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(text&amp;nbsp;=&amp;nbsp;text)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

enum&amp;nbsp;class&amp;nbsp;ButtonType&amp;nbsp;{&amp;nbsp;Primary,&amp;nbsp;Secondary,&amp;nbsp;Danger,&amp;nbsp;Ghost&amp;nbsp;}&lt;/pre&gt;&lt;p&gt;使用示例：&lt;/p&gt;&lt;pre&gt;var&amp;nbsp;isLoading&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;mutableStateOf(false)&amp;nbsp;}

AppButton(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text&amp;nbsp;=&amp;nbsp;&amp;quot;提交&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isLoading&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModel.submit&amp;nbsp;{&amp;nbsp;isLoading&amp;nbsp;=&amp;nbsp;false&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;loading&amp;nbsp;=&amp;nbsp;isLoading,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;leadingIcon&amp;nbsp;=&amp;nbsp;Icons.Default.Send
)&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;点击防抖处理&lt;/strong&gt;：在快速点击场景下，可以在 ViewModel 层控制，也可以在组件内用 &lt;code&gt;debounce&lt;/code&gt; 包装 onClick：&lt;/p&gt;&lt;pre&gt;fun&amp;nbsp;Modifier.clickDebounced(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debounceTime:&amp;nbsp;Long&amp;nbsp;=&amp;nbsp;500L,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onClick:&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit
):&amp;nbsp;Modifier&amp;nbsp;=&amp;nbsp;composed&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;lastClickTime&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;mutableStateOf(0L)&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.clickable&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;currentTime&amp;nbsp;=&amp;nbsp;System.currentTimeMillis()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(currentTime&amp;nbsp;-&amp;nbsp;lastClickTime&amp;nbsp;&amp;gt;=&amp;nbsp;debounceTime)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lastClickTime&amp;nbsp;=&amp;nbsp;currentTime
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onClick()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;四、TextField 封装——统一错误状态与焦点管理&lt;/h2&gt;&lt;p&gt;表单场景是 Compose 的高频痛点。官方 &lt;code&gt;OutlinedTextField&lt;/code&gt; 用起来没问题，但项目里往往需要统一错误提示、字符计数和清除按钮：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;AppTextField.kt
@Composable
fun&amp;nbsp;AppTextField(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value:&amp;nbsp;String,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onValueChange:&amp;nbsp;(String)&amp;nbsp;-&amp;gt;&amp;nbsp;Unit,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;label:&amp;nbsp;String,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier:&amp;nbsp;Modifier&amp;nbsp;=&amp;nbsp;Modifier,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;placeholder:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error:&amp;nbsp;String?&amp;nbsp;=&amp;nbsp;null,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;maxLength:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;Int.MAX_VALUE,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;showCount:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clearable:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keyboardOptions:&amp;nbsp;KeyboardOptions&amp;nbsp;=&amp;nbsp;KeyboardOptions.Default,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keyboardActions:&amp;nbsp;KeyboardActions&amp;nbsp;=&amp;nbsp;KeyboardActions.Default
)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Column(modifier&amp;nbsp;=&amp;nbsp;modifier)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OutlinedTextField(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value&amp;nbsp;=&amp;nbsp;value,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onValueChange&amp;nbsp;=&amp;nbsp;{&amp;nbsp;if&amp;nbsp;(it.length&amp;nbsp;&amp;nbsp;Icon(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Icons.Default.Error,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;&amp;quot;错误&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tint&amp;nbsp;=&amp;nbsp;MaterialTheme.colorScheme.error
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clearable&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;value.isNotEmpty()&amp;nbsp;-&amp;gt;&amp;nbsp;IconButton(onClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;onValueChange(&amp;quot;&amp;quot;)&amp;nbsp;})&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Icon(Icons.Default.Clear,&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;&amp;quot;清除&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;showCount&amp;nbsp;-&amp;gt;&amp;nbsp;Text(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text&amp;nbsp;=&amp;nbsp;&amp;quot;${value.length}/$maxLength&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;style&amp;nbsp;=&amp;nbsp;MaterialTheme.typography.labelSmall,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color&amp;nbsp;=&amp;nbsp;if&amp;nbsp;(value.length&amp;nbsp;&amp;gt;=&amp;nbsp;maxLength)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MaterialTheme.colorScheme.error
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MaterialTheme.colorScheme.onSurfaceVariant
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keyboardOptions&amp;nbsp;=&amp;nbsp;keyboardOptions,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keyboardActions&amp;nbsp;=&amp;nbsp;keyboardActions,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.fillMaxWidth()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;错误信息行
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AnimatedVisibility(visible&amp;nbsp;=&amp;nbsp;error&amp;nbsp;!=&amp;nbsp;null)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text&amp;nbsp;=&amp;nbsp;error&amp;nbsp;?:&amp;nbsp;&amp;quot;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color&amp;nbsp;=&amp;nbsp;MaterialTheme.colorScheme.error,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;style&amp;nbsp;=&amp;nbsp;MaterialTheme.typography.labelMedium,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.padding(start&amp;nbsp;=&amp;nbsp;16.dp,&amp;nbsp;top&amp;nbsp;=&amp;nbsp;4.dp)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;焦点链管理&lt;/strong&gt;（多个输入框按回车切换焦点）：&lt;/p&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;LoginForm()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;focusManager&amp;nbsp;=&amp;nbsp;LocalFocusManager.current
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;username&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;mutableStateOf(&amp;quot;&amp;quot;)&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;password&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;mutableStateOf(&amp;quot;&amp;quot;)&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppTextField(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value&amp;nbsp;=&amp;nbsp;username,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onValueChange&amp;nbsp;=&amp;nbsp;{&amp;nbsp;username&amp;nbsp;=&amp;nbsp;it&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;label&amp;nbsp;=&amp;nbsp;&amp;quot;用户名&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keyboardOptions&amp;nbsp;=&amp;nbsp;KeyboardOptions(imeAction&amp;nbsp;=&amp;nbsp;ImeAction.Next),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keyboardActions&amp;nbsp;=&amp;nbsp;KeyboardActions(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onNext&amp;nbsp;=&amp;nbsp;{&amp;nbsp;focusManager.moveFocus(FocusDirection.Down)&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppTextField(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value&amp;nbsp;=&amp;nbsp;password,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onValueChange&amp;nbsp;=&amp;nbsp;{&amp;nbsp;password&amp;nbsp;=&amp;nbsp;it&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;label&amp;nbsp;=&amp;nbsp;&amp;quot;密码&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keyboardOptions&amp;nbsp;=&amp;nbsp;KeyboardOptions(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keyboardType&amp;nbsp;=&amp;nbsp;KeyboardType.Password,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;imeAction&amp;nbsp;=&amp;nbsp;ImeAction.Done
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keyboardActions&amp;nbsp;=&amp;nbsp;KeyboardActions(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onDone&amp;nbsp;=&amp;nbsp;{&amp;nbsp;focusManager.clearFocus()&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
}&lt;/pre&gt;&lt;h2&gt;五、动画组件——让 UI 更有生命力&lt;/h2&gt;&lt;p&gt;Compose 动画 API 非常强大，但很多开发者只停留在 &lt;code&gt;AnimatedVisibility&lt;/code&gt;。这里介绍几个实用的动画模式：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. 骨架屏（Shimmer 效果）&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;ShimmerEffect(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier:&amp;nbsp;Modifier&amp;nbsp;=&amp;nbsp;Modifier,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;widthOfShadowBrush:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;500,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;angleOfAxisY:&amp;nbsp;Float&amp;nbsp;=&amp;nbsp;270f,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;durationMillis:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;1000
)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;shimmerColors&amp;nbsp;=&amp;nbsp;listOf(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Color.White.copy(alpha&amp;nbsp;=&amp;nbsp;0.3f),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Color.White.copy(alpha&amp;nbsp;=&amp;nbsp;0.5f),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Color.White.copy(alpha&amp;nbsp;=&amp;nbsp;1.0f),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Color.White.copy(alpha&amp;nbsp;=&amp;nbsp;0.5f),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Color.White.copy(alpha&amp;nbsp;=&amp;nbsp;0.3f),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;transition&amp;nbsp;=&amp;nbsp;rememberInfiniteTransition(label&amp;nbsp;=&amp;nbsp;&amp;quot;shimmer&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;translateAnimation&amp;nbsp;=&amp;nbsp;transition.animateFloat(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;initialValue&amp;nbsp;=&amp;nbsp;0f,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetValue&amp;nbsp;=&amp;nbsp;(durationMillis&amp;nbsp;+&amp;nbsp;widthOfShadowBrush).toFloat(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;animationSpec&amp;nbsp;=&amp;nbsp;infiniteRepeatable(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;animation&amp;nbsp;=&amp;nbsp;tween(durationMillis&amp;nbsp;=&amp;nbsp;durationMillis,&amp;nbsp;easing&amp;nbsp;=&amp;nbsp;LinearEasing),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;repeatMode&amp;nbsp;=&amp;nbsp;RepeatMode.Restart,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;label&amp;nbsp;=&amp;nbsp;&amp;quot;shimmer_translate&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;brush&amp;nbsp;=&amp;nbsp;Brush.linearGradient(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;colors&amp;nbsp;=&amp;nbsp;shimmerColors,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;start&amp;nbsp;=&amp;nbsp;Offset(x&amp;nbsp;=&amp;nbsp;translateAnimation.value&amp;nbsp;-&amp;nbsp;widthOfShadowBrush,&amp;nbsp;y&amp;nbsp;=&amp;nbsp;0.0f),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;=&amp;nbsp;Offset(x&amp;nbsp;=&amp;nbsp;translateAnimation.value,&amp;nbsp;y&amp;nbsp;=&amp;nbsp;angleOfAxisY),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Box(modifier&amp;nbsp;=&amp;nbsp;modifier.background(brush))
}

//&amp;nbsp;使用方式
@Composable
fun&amp;nbsp;UserCardSkeleton()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Row(modifier&amp;nbsp;=&amp;nbsp;Modifier.padding(16.dp))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ShimmerEffect(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.size(48.dp).clip(CircleShape)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Spacer(modifier&amp;nbsp;=&amp;nbsp;Modifier.width(12.dp))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Column(verticalArrangement&amp;nbsp;=&amp;nbsp;Arrangement.spacedBy(8.dp))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ShimmerEffect(modifier&amp;nbsp;=&amp;nbsp;Modifier.width(120.dp).height(16.dp).clip(RoundedCornerShape(4.dp)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ShimmerEffect(modifier&amp;nbsp;=&amp;nbsp;Modifier.width(80.dp).height(12.dp).clip(RoundedCornerShape(4.dp)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;2. 展开/收起动画&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;ExpandableCard(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title:&amp;nbsp;String,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content:&amp;nbsp;@Composable&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit
)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;expanded&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;mutableStateOf(false)&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;rotationAngle&amp;nbsp;by&amp;nbsp;animateFloatAsState(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetValue&amp;nbsp;=&amp;nbsp;if&amp;nbsp;(expanded)&amp;nbsp;180f&amp;nbsp;else&amp;nbsp;0f,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;animationSpec&amp;nbsp;=&amp;nbsp;tween(durationMillis&amp;nbsp;=&amp;nbsp;300),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;label&amp;nbsp;=&amp;nbsp;&amp;quot;arrow_rotation&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Card(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;expanded&amp;nbsp;=&amp;nbsp;!expanded&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.fillMaxWidth()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Column(modifier&amp;nbsp;=&amp;nbsp;Modifier.padding(16.dp))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Row(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.fillMaxWidth(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;horizontalArrangement&amp;nbsp;=&amp;nbsp;Arrangement.SpaceBetween,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;verticalAlignment&amp;nbsp;=&amp;nbsp;Alignment.CenterVertically
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(text&amp;nbsp;=&amp;nbsp;title,&amp;nbsp;style&amp;nbsp;=&amp;nbsp;MaterialTheme.typography.titleMedium)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Icon(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Icons.Default.ExpandMore,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;null,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.rotate(rotationAngle)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AnimatedVisibility(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;visible&amp;nbsp;=&amp;nbsp;expanded,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enter&amp;nbsp;=&amp;nbsp;expandVertically()&amp;nbsp;+&amp;nbsp;fadeIn(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exit&amp;nbsp;=&amp;nbsp;shrinkVertically()&amp;nbsp;+&amp;nbsp;fadeOut()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Box(modifier&amp;nbsp;=&amp;nbsp;Modifier.padding(top&amp;nbsp;=&amp;nbsp;8.dp))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;六、LazyColumn 性能优化——避免常见陷阱&lt;/h2&gt;&lt;p&gt;Compose 列表是性能问题的重灾区。这里总结最关键的几个优化点：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. 始终为列表项指定 key&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;错误：不指定&amp;nbsp;key，滚动时&amp;nbsp;item&amp;nbsp;会被错误复用
LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(userList)&amp;nbsp;{&amp;nbsp;user&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UserCard(user&amp;nbsp;=&amp;nbsp;user)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;✅&amp;nbsp;正确：指定稳定的&amp;nbsp;key
LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items&amp;nbsp;=&amp;nbsp;userList,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;{&amp;nbsp;user&amp;nbsp;-&amp;gt;&amp;nbsp;user.id&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{&amp;nbsp;user&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UserCard(user&amp;nbsp;=&amp;nbsp;user)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;2. 避免在 items 中创建 Lambda&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;每次重组都会创建新的&amp;nbsp;Lambda&amp;nbsp;对象
LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(userList,&amp;nbsp;key&amp;nbsp;=&amp;nbsp;{&amp;nbsp;it.id&amp;nbsp;})&amp;nbsp;{&amp;nbsp;user&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UserCard(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;user,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;viewModel.onUserClick(user.id)&amp;nbsp;}&amp;nbsp;&amp;nbsp;//&amp;nbsp;每次重组都是新对象
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;✅&amp;nbsp;将回调提升或用&amp;nbsp;remember&amp;nbsp;缓存
@Stable
data&amp;nbsp;class&amp;nbsp;UserActions(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;onUserClick:&amp;nbsp;(String)&amp;nbsp;-&amp;gt;&amp;nbsp;Unit,
)

LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(userList,&amp;nbsp;key&amp;nbsp;=&amp;nbsp;{&amp;nbsp;it.id&amp;nbsp;})&amp;nbsp;{&amp;nbsp;user&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UserCard(user&amp;nbsp;=&amp;nbsp;user,&amp;nbsp;actions&amp;nbsp;=&amp;nbsp;actions)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;3. 使用 derivedStateOf 优化滚动状态监听&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;listState.firstVisibleItemIndex&amp;nbsp;变化时会触发不必要的重组
val&amp;nbsp;showScrollTop&amp;nbsp;=&amp;nbsp;listState.firstVisibleItemIndex&amp;nbsp;&amp;gt;&amp;nbsp;0

//&amp;nbsp;✅&amp;nbsp;只有布尔值变化时才触发重组
val&amp;nbsp;showScrollTop&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;derivedStateOf&amp;nbsp;{&amp;nbsp;listState.firstVisibleItemIndex&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;踩坑：图片加载导致的列表抖动&lt;/strong&gt;：在加载网络图片时，如果没有指定尺寸，图片加载完成后高度变化会导致列表跳动。解决方案是始终为图片指定固定高度或宽高比：&lt;/p&gt;&lt;pre&gt;AsyncImage(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model&amp;nbsp;=&amp;nbsp;imageUrl,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;null,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;contentScale&amp;nbsp;=&amp;nbsp;ContentScale.Crop,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.fillMaxWidth()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.aspectRatio(16f&amp;nbsp;/&amp;nbsp;9f)&amp;nbsp;&amp;nbsp;//&amp;nbsp;固定宽高比，避免加载前后高度变化
)&lt;/pre&gt;&lt;h2&gt;七、Preview 管理——提升团队开发效率&lt;/h2&gt;&lt;p&gt;一个良好的 Preview 体系能大幅提升 UI 开发效率，减少真机调试次数：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;ComponentPreview.kt&amp;nbsp;——&amp;nbsp;统一&amp;nbsp;Preview&amp;nbsp;配置
@Preview(name&amp;nbsp;=&amp;nbsp;&amp;quot;Light&amp;nbsp;Mode&amp;quot;,&amp;nbsp;showBackground&amp;nbsp;=&amp;nbsp;true)
@Preview(name&amp;nbsp;=&amp;nbsp;&amp;quot;Dark&amp;nbsp;Mode&amp;quot;,&amp;nbsp;showBackground&amp;nbsp;=&amp;nbsp;true,&amp;nbsp;uiMode&amp;nbsp;=&amp;nbsp;Configuration.UI_MODE_NIGHT_YES)
@Preview(name&amp;nbsp;=&amp;nbsp;&amp;quot;Large&amp;nbsp;Font&amp;quot;,&amp;nbsp;showBackground&amp;nbsp;=&amp;nbsp;true,&amp;nbsp;fontScale&amp;nbsp;=&amp;nbsp;1.5f)
annotation&amp;nbsp;class&amp;nbsp;MultiPreview

//&amp;nbsp;在组件文件中使用
@MultiPreview
@Composable
private&amp;nbsp;fun&amp;nbsp;AppButtonPreview()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppTheme&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Column(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.padding(16.dp),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;verticalArrangement&amp;nbsp;=&amp;nbsp;Arrangement.spacedBy(8.dp)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppButton(text&amp;nbsp;=&amp;nbsp;&amp;quot;Primary&amp;nbsp;Button&amp;quot;,&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;{})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppButton(text&amp;nbsp;=&amp;nbsp;&amp;quot;Loading...&amp;quot;,&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;{},&amp;nbsp;loading&amp;nbsp;=&amp;nbsp;true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppButton(text&amp;nbsp;=&amp;nbsp;&amp;quot;Disabled&amp;quot;,&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;{},&amp;nbsp;enabled&amp;nbsp;=&amp;nbsp;false)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppButton(text&amp;nbsp;=&amp;nbsp;&amp;quot;Danger&amp;quot;,&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;{},&amp;nbsp;buttonType&amp;nbsp;=&amp;nbsp;ButtonType.Danger)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;通过自定义 &lt;code&gt;@MultiPreview&lt;/code&gt; 注解，一次写 Preview 就能同时看到亮色/暗色/大字体效果，无需重复标注。&lt;/p&gt;&lt;h2&gt;八、总结与最佳实践清单&lt;/h2&gt;&lt;p&gt;经过以上几个核心模块的封装实践，整理出以下最佳实践清单，建议在团队内制定为规范：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;✅ 所有颜色、字体、间距通过 Theme 系统管理，禁止硬编码&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 组件参数设计遵循&amp;quot;最小必要原则&amp;quot;，复杂样式通过 &lt;code&gt;Modifier&lt;/code&gt; 扩展传入&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 有副作用的 UI 状态（loading、error）统一作为参数传入，不在组件内部管理&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ LazyColumn/LazyRow 中的 item 必须指定稳定的 key&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 监听滚动、键盘等高频状态时使用 &lt;code&gt;derivedStateOf&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 网络图片必须指定固定尺寸或宽高比&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 每个公共组件必须有覆盖亮暗模式的 Preview&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 动画使用 &lt;code&gt;AnimatedContent&lt;/code&gt;/&lt;code&gt;AnimatedVisibility&lt;/code&gt; 而非手动控制透明度&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Compose 组件库的建设是一个持续演进的过程。建议从最高频的按钮、输入框、卡片开始封装，在实际项目使用中不断打磨，逐步沉淀出真正适合团队的组件体系。&lt;/p&gt;&lt;p&gt;完整代码已整理为模板项目，有需要的同学欢迎在评论区留言交流。&lt;/p&gt;</description><pubDate>Sat, 28 Mar 2026 08:10:48 +0800</pubDate></item><item><title>Android Hilt 依赖注入实战：从零搭建 Clean Architecture 注入体系</title><link>https://blog.resmic.cn/post/222.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/03/20260327080506177456990688766.jpg&quot; alt=&quot;\u6587\u7ae0\u5c01\u9762&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;\n\n&lt;/p&gt;&lt;h2&gt;\u4e3a\u4ec0\u4e48\u8981\u5728 Android \u9879\u76ee\u4e2d\u5f15\u5165 Hilt\uff1f&lt;/h2&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u4f9d\u8d56\u6ce8\u5165\uff08Dependency Injection\uff0cDI\uff09\u662f\u73b0\u4ee3 Android \u5f00\u53d1\u4e2d\u51e0\u4e4e\u65e0\u6cd5\u7ed5\u5f00\u7684\u8bdd\u9898\u3002\u624b\u52a8\u7ba1\u7406\u5bf9\u8c61\u4f9d\u8d56\u4e0d\u4ec5\u7e41\u7410\uff0c\u8fd8\u5bb9\u6613\u5728 Activity/Fragment \u751f\u547d\u5468\u671f\u4e2d\u5f15\u53d1\u5185\u5b58\u6cc4\u6f0f\u3002Google \u5b98\u65b9\u63a8\u8350\u7684 Hilt \u6846\u67b6\u57fa\u4e8e Dagger2\uff0c\u901a\u8fc7\u7f16\u8bd1\u671f\u4ee3\u7801\u751f\u6210\uff0c\u5728\u4fdd\u6301\u9ad8\u6027\u80fd\u7684\u540c\u65f6\u5927\u5e45\u964d\u4f4e\u4e86\u4e0a\u624b\u95e8\u69db\u3002&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u672c\u6587\u4ee5\u4e00\u4e2a\u771f\u5b9e\u7684\u201c\u7528\u6237\u767b\u5f55 + \u6570\u636e\u52a0\u8f7d\u201d\u529f\u80fd\u4e3a\u7ebf\u7d22\uff0c\u5e26\u4f60\u4ece\u96f6\u641e\u5efa\u4e00\u5957\u7b26\u5408 Clean Architecture \u7684 Hilt \u6ce8\u5165\u4f53\u7cfb\uff0c\u8986\u76d6\u4ee5\u4e0b\u573a\u666f\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;ViewModel \u4e2d\u6ce8\u5165 Repository&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Repository \u4e2d\u6ce8\u5165 Retrofit \u548c Room&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\u591a\u6a21\u5757\u9879\u76ee\u4e2d\u7684\u8de8\u6a21\u5757\u6ce8\u5165&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\u5355\u5143\u6d4b\u8bd5\u4e2d\u66ff\u6362\u771f\u5b9e\u4f9d\u8d56&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;\n\n&lt;/p&gt;&lt;h2&gt;\u7b2c\u4e00\u6b65\uff1a\u6dfb\u52a0\u4f9d\u8d56\u4e0e\u57fa\u7840\u914d\u7f6e&lt;/h2&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u5728\u9879\u76ee\u6839\u76ee\u5f55 build.gradle \u4e2d\u6dfb\u52a0 Hilt \u63d2\u4ef6\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;\u6839\u76ee\u5f55&amp;nbsp;build.gradle\nplugins&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;\&amp;#39;com.google.dagger.hilt.android\&amp;#39;&amp;nbsp;version&amp;nbsp;\&amp;#39;2.51.1\&amp;#39;&amp;nbsp;apply&amp;nbsp;false\n}\n&lt;/pre&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u5728 app \u6a21\u5757 build.gradle \u4e2d\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;plugins&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;\&amp;#39;com.google.dagger.hilt.android\&amp;#39;\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;\&amp;#39;kotlin-kapt\&amp;#39;\n}\n\ndependencies&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation&amp;nbsp;&amp;quot;com.google.dagger:hilt-android:2.51.1&amp;quot;\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kapt&amp;nbsp;&amp;quot;com.google.dagger:hilt-android-compiler:2.51.1&amp;quot;\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation&amp;nbsp;&amp;quot;androidx.hilt:hilt-navigation-compose:1.2.0&amp;quot;\n}\n&lt;/pre&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u63a5\u7740\u7ed9 Application \u7c7b\u52a0\u4e0a @HiltAndroidApp \u6ce8\u89e3\uff0c\u8fd9\u662f Hilt \u7684\u5165\u53e3\uff0c\u4e0d\u80fd\u7701\u7565\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;@HiltAndroidApp\nclass&amp;nbsp;MyApp&amp;nbsp;:&amp;nbsp;Application()\n&lt;/pre&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u522b\u5fd8\u4e86\u5728 AndroidManifest.xml \u4e2d\u6307\u5b9a android:name=&amp;quot;.MyApp&amp;quot;\u3002&lt;/p&gt;&lt;p&gt;\n\n&lt;/p&gt;&lt;h2&gt;\u7b2c\u4e8c\u6b65\uff1a\u6784\u5efa Network \u548c Database \u6a21\u5757&lt;/h2&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;Hilt \u901a\u8fc7 @Module + @InstallIn \u6765\u58f0\u660e\u5982\u4f55\u63d0\u4f9b\u4f9d\u8d56\u3002\u7f51\u7edc\u5c42\u548c\u6570\u636e\u5e93\u5c42\u901a\u5e38\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u5e94\u7528\u751f\u547d\u5468\u671f\uff0c\u4f7f\u7528 SingletonComponent\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;@Module\n@InstallIn(SingletonComponent::class)\nobject&amp;nbsp;NetworkModule&amp;nbsp;{\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideOkHttpClient():&amp;nbsp;OkHttpClient&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;OkHttpClient.Builder()\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addInterceptor(HttpLoggingInterceptor().apply&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;level&amp;nbsp;=&amp;nbsp;HttpLoggingInterceptor.Level.BODY\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.connectTimeout(30,&amp;nbsp;TimeUnit.SECONDS)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideRetrofit(okHttpClient:&amp;nbsp;OkHttpClient):&amp;nbsp;Retrofit&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Retrofit.Builder()\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.baseUrl(&amp;quot;https://api.example.com/&amp;quot;)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.client(okHttpClient)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addConverterFactory(GsonConverterFactory.create())\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideUserApi(retrofit:&amp;nbsp;Retrofit):&amp;nbsp;UserApi&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;retrofit.create(UserApi::class.java)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n}\n&lt;/pre&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u6570\u636e\u5e93\u6a21\u5757\u540c\u7406\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;@Module\n@InstallIn(SingletonComponent::class)\nobject&amp;nbsp;DatabaseModule&amp;nbsp;{\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideDatabase(@ApplicationContext&amp;nbsp;context:&amp;nbsp;Context):&amp;nbsp;AppDatabase&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Room.databaseBuilder(\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context,\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppDatabase::class.java,\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;app_database&amp;quot;\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;).build()\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideUserDao(database:&amp;nbsp;AppDatabase):&amp;nbsp;UserDao&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;database.userDao()\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n}\n&lt;/pre&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u8e29\u5751\u63d0\u793a\uff1aUserDao \u4e0d\u52a0 @Singleton\uff0c\u56e0\u4e3a Room \u4f1a\u81ea\u5df1\u7ba1\u7406 DAO \u7684\u751f\u547d\u5468\u671f\uff0c\u91cd\u590d\u5305\u88c5\u53cd\u800c\u53ef\u80fd\u5f15\u53d1\u95ee\u9898\u3002&lt;/p&gt;&lt;p&gt;\n\n&lt;/p&gt;&lt;h2&gt;\u7b2c\u4e09\u6b65\uff1a\u6ce8\u5165 Repository \u548c ViewModel&lt;/h2&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;Repository \u5c42\u8d1f\u8d23\u6574\u5408\u7f51\u7edc\u548c\u672c\u5730\u6570\u636e\u6e90\uff0c\u4f7f\u7528\u6784\u9020\u51fd\u6570\u6ce8\u5165\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;class&amp;nbsp;UserRepository&amp;nbsp;@Inject&amp;nbsp;constructor(\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;userApi:&amp;nbsp;UserApi,\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;userDao:&amp;nbsp;UserDao\n)&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suspend&amp;nbsp;fun&amp;nbsp;login(username:&amp;nbsp;String,&amp;nbsp;password:&amp;nbsp;String):&amp;nbsp;Result&amp;lt;User&amp;gt;&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;try&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;user&amp;nbsp;=&amp;nbsp;userApi.login(LoginRequest(username,&amp;nbsp;password))\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userDao.insertUser(user)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Result.success(user)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(e:&amp;nbsp;Exception)&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Result.failure(e)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;getCachedUser():&amp;nbsp;Flow&amp;lt;User?&amp;gt;&amp;nbsp;=&amp;nbsp;userDao.getUser()\n}\n&lt;/pre&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;ViewModel \u4e2d\u4f7f\u7528 @HiltViewModel + @Inject constructor\uff0c\u65e0\u9700\u624b\u52a8\u521b\u5efa ViewModelFactory\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;@HiltViewModel\nclass&amp;nbsp;LoginViewModel&amp;nbsp;@Inject&amp;nbsp;constructor(\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;userRepository:&amp;nbsp;UserRepository\n)&amp;nbsp;:&amp;nbsp;ViewModel()&amp;nbsp;{\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;_loginState&amp;nbsp;=&amp;nbsp;MutableStateFlow&amp;lt;LoginState&amp;gt;(LoginState.Idle)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;loginState:&amp;nbsp;StateFlow&amp;lt;LoginState&amp;gt;&amp;nbsp;=&amp;nbsp;_loginState.asStateFlow()\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;login(username:&amp;nbsp;String,&amp;nbsp;password:&amp;nbsp;String)&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModelScope.launch&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_loginState.value&amp;nbsp;=&amp;nbsp;LoginState.Loading\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;result&amp;nbsp;=&amp;nbsp;userRepository.login(username,&amp;nbsp;password)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_loginState.value&amp;nbsp;=&amp;nbsp;if&amp;nbsp;(result.isSuccess)&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LoginState.Success(result.getOrNull()!!)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LoginState.Error(result.exceptionOrNull()?.message&amp;nbsp;?:&amp;nbsp;&amp;quot;\u767b\u5f55\u5931\u8d25&amp;quot;)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n}\n&lt;/pre&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u5728 Activity/Fragment \u4e2d\u83b7\u53d6 ViewModel \u53ea\u9700\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;@AndroidEntryPoint\nclass&amp;nbsp;LoginActivity&amp;nbsp;:&amp;nbsp;AppCompatActivity()&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;viewModel:&amp;nbsp;LoginViewModel&amp;nbsp;by&amp;nbsp;viewModels()\n}\n&lt;/pre&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u6ce8\u610f Activity \u5fc5\u987b\u52a0 @AndroidEntryPoint\uff0c\u5426\u5219 Hilt \u65e0\u6cd5\u6ce8\u5165\u3002&lt;/p&gt;&lt;p&gt;\n\n&lt;/p&gt;&lt;h2&gt;\u7b2c\u56db\u6b65\uff1a\u5355\u5143\u6d4b\u8bd5\u4e2d\u66ff\u6362\u4f9d\u8d56&lt;/h2&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;Hilt \u5bf9\u6d4b\u8bd5\u7684\u652f\u6301\u975e\u5e38\u5b8c\u5584\uff0c\u901a\u8fc7 @TestInstallIn \u53ef\u4ee5\u66ff\u6362\u6b63\u5f0f\u6a21\u5757\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;@TestInstallIn(\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;components&amp;nbsp;=&amp;nbsp;[SingletonComponent::class],\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;replaces&amp;nbsp;=&amp;nbsp;[NetworkModule::class]\n)\n@Module\nobject&amp;nbsp;FakeNetworkModule&amp;nbsp;{\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideUserApi():&amp;nbsp;UserApi&amp;nbsp;=&amp;nbsp;FakeUserApi()\n}\n&lt;/pre&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u6d4b\u8bd5\u7c7b\uff1a&lt;/p&gt;&lt;p&gt;\n&lt;/p&gt;&lt;pre&gt;@HiltAndroidTest\nclass&amp;nbsp;LoginViewModelTest&amp;nbsp;{\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@get:Rule\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;hiltRule&amp;nbsp;=&amp;nbsp;HiltAndroidRule(this)\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Inject\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lateinit&amp;nbsp;var&amp;nbsp;userRepository:&amp;nbsp;UserRepository\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Before\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;init()&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hiltRule.inject()\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Test\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;testLoginSuccess()&amp;nbsp;=&amp;nbsp;runTest&amp;nbsp;{\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;viewModel&amp;nbsp;=&amp;nbsp;LoginViewModel(userRepository)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModel.login(&amp;quot;test_user&amp;quot;,&amp;nbsp;&amp;quot;password123&amp;quot;)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;assertTrue(viewModel.loginState.value&amp;nbsp;is&amp;nbsp;LoginState.Success)\n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}\n}\n&lt;/pre&gt;&lt;p&gt;\n\n&lt;/p&gt;&lt;h2&gt;\u5e38\u89c1\u95ee\u9898\u4e0e\u6700\u4f73\u5b9e\u8df5&lt;/h2&gt;&lt;p&gt;\n&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\u4f5c\u7528\u57df\u9009\u62e9\uff1aActivity \u5185\u7684\u4f9d\u8d56\u7528 ActivityComponent\uff0c\u907f\u514d Singleton \u6301\u6709 Activity \u5f15\u7528\u9020\u6210\u6cc4\u6f0f&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;@Qualifier \u533a\u5206\u540c\u7c7b\u578b\uff1a\u82e5\u6709\u591a\u4e2a OkHttpClient\uff0c\u7528 @Named \u6216\u81ea\u5b9a\u4e49 Qualifier \u533a\u5206&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\u61d2\u52a0\u8f7d\uff1a\u7528 Lazy \u5305\u88c5\u4e0d\u9700\u8981\u7acb\u5373\u521d\u59cb\u5316\u7684\u4f9d\u8d56\uff0c\u51cf\u5c11\u542f\u52a8\u8017\u65f6&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\u591a\u6a21\u5757\u9879\u76ee\uff1a\u6bcf\u4e2a feature \u6a21\u5757\u53ef\u4ee5\u6709\u81ea\u5df1\u7684 @InstallIn(ActivityComponent::class) \u6a21\u5757\uff0cHilt \u4f1a\u81ea\u52a8\u5408\u5e76&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;kapt \u7f16\u8bd1\u6162\uff1f\u5347\u7ea7\u5230 KSP \u53ef\u4ee5\u663e\u8457\u63d0\u5347\u7f16\u8bd1\u901f\u5ea6\uff0cHilt \u5df2\u652f\u6301 KSP&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;\n&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;\n&lt;/p&gt;&lt;p&gt;\u638c\u63e1\u8fd9\u5957\u6a21\u5f0f\u540e\uff0c\u4f60\u4f1a\u53d1\u73b0\u4ee3\u7801\u53ef\u6d4b\u8bd5\u6027\u3001\u53ef\u7ef4\u62a4\u6027\u90fd\u6709\u8d28\u7684\u63d0\u5347\u3002\u4f9d\u8d56\u6ce8\u5165\u4e0d\u53ea\u662f\u6846\u67b6\u7684\u4f7f\u7528\uff0c\u66f4\u662f\u4e00\u79cd\u9762\u5411\u63a5\u53e3\u7f16\u7a0b\u7684\u601d\u7ef4\u65b9\u5f0f\u3002&lt;/p&gt;</description><pubDate>Fri, 27 Mar 2026 08:06:49 +0800</pubDate></item><item><title>PHP 8.3 新特性实战：类型系统增强与性能优化完全指南</title><link>https://blog.resmic.cn/post/221.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/03/20260326080631177448359123828.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;为什么要升级到 PHP 8.3？&lt;/h2&gt;&lt;p&gt;PHP 8.3 于 2023 年 11 月正式发布，带来了一系列令开发者兴奋的新特性。这不只是一次小版本更新，它在类型系统、性能和内置函数层面都做了有意义的改进。如果你还在用 PHP 7.x 或 PHP 8.0/8.1，现在是一个绝佳的升级时机。&lt;/p&gt;&lt;p&gt;本文将通过真实代码示例，逐一讲解 PHP 8.3 最值得关注的新特性，并附上升级过程中常见的踩坑点，让你看完就能动手实践。&lt;/p&gt;&lt;h2&gt;特性一：类型化类常量（Typed Class Constants）&lt;/h2&gt;&lt;p&gt;在 PHP 8.3 之前，类常量没有类型声明，这导致继承时可能出现类型不一致的隐患。PHP 8.3 引入了类型化常量，让代码更加健壮。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
//&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;之前——无类型约束
class&amp;nbsp;Config&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;&amp;#39;1.0.0&amp;#39;;
}

//&amp;nbsp;PHP&amp;nbsp;8.3——可以声明常量类型
class&amp;nbsp;Config&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;&amp;#39;1.0.0&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;int&amp;nbsp;MAX_RETRY&amp;nbsp;=&amp;nbsp;3;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;float&amp;nbsp;TIMEOUT&amp;nbsp;=&amp;nbsp;30.0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;array&amp;nbsp;ALLOWED_METHODS&amp;nbsp;=&amp;nbsp;[&amp;#39;GET&amp;#39;,&amp;nbsp;&amp;#39;POST&amp;#39;];
}

//&amp;nbsp;继承时类型必须兼容，否则报错
class&amp;nbsp;ChildConfig&amp;nbsp;extends&amp;nbsp;Config&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;以下会抛出&amp;nbsp;TypeError
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;const&amp;nbsp;string&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;123;&amp;nbsp;&amp;nbsp;//&amp;nbsp;❌&amp;nbsp;类型不符
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;&amp;#39;2.0.0&amp;#39;;&amp;nbsp;//&amp;nbsp;✅&amp;nbsp;正确
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;踩坑记录：&lt;/strong&gt;如果你在枚举（enum）中使用类型化常量，需注意枚举常量的类型必须与枚举的 backed type 兼容，否则在运行时会报错。&lt;/p&gt;&lt;h2&gt;特性二：json_validate() 函数——终于不用 json_decode 了&lt;/h2&gt;&lt;p&gt;过去我们要验证一个字符串是否为合法 JSON，只能先 &lt;code&gt;json_decode&lt;/code&gt; 再判断 &lt;code&gt;json_last_error()&lt;/code&gt;，既浪费内存又不直观。PHP 8.3 直接提供了 &lt;code&gt;json_validate()&lt;/code&gt; 函数。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
//&amp;nbsp;PHP&amp;nbsp;8.2&amp;nbsp;及以前的写法
function&amp;nbsp;isValidJson(string&amp;nbsp;$json):&amp;nbsp;bool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;json_decode($json);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;json_last_error()&amp;nbsp;===&amp;nbsp;JSON_ERROR_NONE;
}

//&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;新写法——更高效，不需要解析整个&amp;nbsp;JSON
$validJson&amp;nbsp;=&amp;nbsp;&amp;#39;{&amp;quot;name&amp;quot;:&amp;nbsp;&amp;quot;Alice&amp;quot;,&amp;nbsp;&amp;quot;age&amp;quot;:&amp;nbsp;30}&amp;#39;;
$invalidJson&amp;nbsp;=&amp;nbsp;&amp;#39;{name:&amp;nbsp;&amp;quot;Alice&amp;quot;}&amp;#39;;

var_dump(json_validate($validJson));&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;bool(true)
var_dump(json_validate($invalidJson));&amp;nbsp;//&amp;nbsp;bool(false)

//&amp;nbsp;还支持设置最大嵌套深度
var_dump(json_validate(&amp;#39;{&amp;quot;a&amp;quot;:{&amp;quot;b&amp;quot;:{&amp;quot;c&amp;quot;:1}}}&amp;#39;,&amp;nbsp;depth:&amp;nbsp;2));&amp;nbsp;//&amp;nbsp;bool(false)，超过深度限制&lt;/pre&gt;&lt;p&gt;这个函数在 API 数据接收、消息队列消费场景中非常实用，性能比原来的方式提升了约 15%～20%，因为它不需要在内存中构建 PHP 数组。&lt;/p&gt;&lt;h2&gt;特性三：Readonly 属性增强&lt;/h2&gt;&lt;p&gt;PHP 8.1 引入了 readonly 属性，但有一个痛点：无法在克隆对象时修改 readonly 属性。PHP 8.3 对此做出了改进，允许在 &lt;code&gt;__clone&lt;/code&gt; 方法中对 readonly 属性重新赋值。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
class&amp;nbsp;User&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;int&amp;nbsp;$id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$email,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;PHP&amp;nbsp;8.3：可以在&amp;nbsp;__clone&amp;nbsp;中修改&amp;nbsp;readonly&amp;nbsp;属性
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;withEmail(string&amp;nbsp;$newEmail):&amp;nbsp;static&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$clone&amp;nbsp;=&amp;nbsp;clone&amp;nbsp;$this;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$clone-&amp;gt;email&amp;nbsp;=&amp;nbsp;$newEmail;&amp;nbsp;//&amp;nbsp;✅&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;允许，8.2&amp;nbsp;会报错
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;$clone;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

$user&amp;nbsp;=&amp;nbsp;new&amp;nbsp;User(1,&amp;nbsp;&amp;#39;Alice&amp;#39;,&amp;nbsp;&amp;#39;alice@example.com&amp;#39;);
$updatedUser&amp;nbsp;=&amp;nbsp;$user-&amp;gt;withEmail(&amp;#39;newalice@example.com&amp;#39;);

echo&amp;nbsp;$user-&amp;gt;email;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;alice@example.com（原对象不变）
echo&amp;nbsp;$updatedUser-&amp;gt;email;&amp;nbsp;//&amp;nbsp;newalice@example.com&lt;/pre&gt;&lt;p&gt;这个特性配合不可变对象（Immutable Object）模式非常好用，适合用在 DDD（领域驱动设计）的值对象中，实现真正的&amp;quot;不可变但可派生&amp;quot;的设计。&lt;/p&gt;&lt;h2&gt;特性四：新的随机数 API——Randomizer 增强&lt;/h2&gt;&lt;p&gt;PHP 8.2 引入了 &lt;code&gt;Random\Randomizer&lt;/code&gt; 类，PHP 8.3 在此基础上增加了两个实用方法：&lt;code&gt;getBytesFromString()&lt;/code&gt; 和 &lt;code&gt;getFloat()&lt;/code&gt;。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php
use&amp;nbsp;Random\Randomizer;

$randomizer&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Randomizer();

//&amp;nbsp;从指定字符集中随机取&amp;nbsp;N&amp;nbsp;个字节——非常适合生成验证码
$captcha&amp;nbsp;=&amp;nbsp;$randomizer-&amp;gt;getBytesFromString(&amp;#39;ABCDEFGHJKLMNPQRSTUVWXYZ23456789&amp;#39;,&amp;nbsp;6);
echo&amp;nbsp;$captcha;&amp;nbsp;//&amp;nbsp;例如：K7MN3P

//&amp;nbsp;生成指定范围内的随机浮点数
$price&amp;nbsp;=&amp;nbsp;$randomizer-&amp;gt;getFloat(9.99,&amp;nbsp;99.99);
echo&amp;nbsp;round($price,&amp;nbsp;2);&amp;nbsp;//&amp;nbsp;例如：42.57

//&amp;nbsp;配合&amp;nbsp;InteractWithSeed&amp;nbsp;引擎，可以生成可重现的随机数（测试场景）
$seeded&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Randomizer(new&amp;nbsp;Random\Engine\Xoshiro256StarStar(42));
echo&amp;nbsp;$seeded-&amp;gt;getBytesFromString(&amp;#39;0123456789&amp;#39;,&amp;nbsp;8);&amp;nbsp;//&amp;nbsp;每次固定输出相同结果&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;实战场景：&lt;/strong&gt;生成优惠码、邀请码时，用 &lt;code&gt;getBytesFromString&lt;/code&gt; 代替 &lt;code&gt;substr(str_shuffle(...))&lt;/code&gt;，既安全又简洁，还避免了字符分布不均的问题。&lt;/p&gt;&lt;h2&gt;升级到 PHP 8.3 的踩坑总结&lt;/h2&gt;&lt;p&gt;在实际项目升级过程中，笔者遇到了以下几个常见问题，整理如下供参考：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;旧版 Composer 包兼容问题：&lt;/strong&gt;升级前用 &lt;code&gt;composer outdated&lt;/code&gt; 检查依赖，部分老旧扩展（如 &lt;code&gt;carbon/carbon &amp;lt; 2.72&lt;/code&gt;）需要同步升级。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;已弃用特性报错：&lt;/strong&gt;&lt;code&gt;mb_strtolower&lt;/code&gt; 等函数的部分用法在 PHP 8.3 中被标记为 deprecated，要留意 error_log 中的 deprecation warning。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;类型声明更严格：&lt;/strong&gt;PHP 8.3 对动态属性访问更加严格，未声明的动态属性赋值会产生 deprecated 警告，建议添加 &lt;code&gt;#[AllowDynamicProperties]&lt;/code&gt; 注解或重构代码。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker 镜像更新：&lt;/strong&gt;推荐使用 &lt;code&gt;php:8.3-fpm-alpine&lt;/code&gt; 镜像，体积小、版本新，配合 OPcache 开箱即用性能表现优秀。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;#&amp;nbsp;Dockerfile&amp;nbsp;示例
FROM&amp;nbsp;php:8.3-fpm-alpine

#&amp;nbsp;安装常用扩展
RUN&amp;nbsp;docker-php-ext-install&amp;nbsp;pdo&amp;nbsp;pdo_mysql&amp;nbsp;opcache

#&amp;nbsp;OPcache&amp;nbsp;配置
RUN&amp;nbsp;echo&amp;nbsp;&amp;quot;opcache.enable=1&amp;quot;&amp;nbsp;&amp;gt;&amp;gt;&amp;nbsp;/usr/local/etc/php/conf.d/opcache.ini&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;echo&amp;nbsp;&amp;quot;opcache.jit_buffer_size=128M&amp;quot;&amp;nbsp;&amp;gt;&amp;gt;&amp;nbsp;/usr/local/etc/php/conf.d/opcache.ini&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;echo&amp;nbsp;&amp;quot;opcache.jit=1255&amp;quot;&amp;nbsp;&amp;gt;&amp;gt;&amp;nbsp;/usr/local/etc/php/conf.d/opcache.ini&lt;/pre&gt;&lt;h2&gt;总结&lt;/h2&gt;&lt;p&gt;PHP 8.3 的新特性虽然看起来都不大，但每一项都解决了实际开发中的真实痛点：类型化常量让继承更安全，&lt;code&gt;json_validate&lt;/code&gt; 让 JSON 校验更高效，readonly 克隆增强让不可变对象设计更优雅，新的随机数 API 让安全随机数生成更简单。&lt;/p&gt;&lt;p&gt;建议先在测试环境中运行 &lt;code&gt;php -l&lt;/code&gt; 语法检查和单元测试，逐步验证兼容性，再推进到生产环境。如果你的项目已经在 PHP 8.1/8.2 上运行，升级到 8.3 的成本是非常低的，性能收益却立竿见影。&lt;/p&gt;</description><pubDate>Thu, 26 Mar 2026 08:07:30 +0800</pubDate></item></channel></rss>