<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>智核技术笔记</title><description>记录技术学习与实践，分享人工智能工具、智能体应用和编程开发方面的学习笔记与使用经验。</description><link>https://www.aicore.icu</link><item><title>如何给 AI 工具写清晰、可执行的任务说明</title><link>https://www.aicore.icu/blog/ai-task-instructions</link><guid isPermaLink="true">https://www.aicore.icu/blog/ai-task-instructions</guid><description>把一次交给 AI 工具的任务写成目标、范围、输入和完成标准都明确的说明，减少返工和误解。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;同一件事情，写成「帮我看看这段代码」和写成「指出这个函数在空列表时为什么返回错误，不要改其他函数」，得到的结果差很多。AI 工具会补上你没写的部分。没写的部分越多，它就越可能改到不该动的文件，或者交回一段无法判断对错的说明。&lt;/p&gt;
&lt;p&gt;下面说的是任务说明怎么写，不涉及某个具体产品的注册和设置。&lt;/p&gt;
&lt;h2&gt;一条说明里放齐五件事&lt;/h2&gt;
&lt;p&gt;可执行的说明通常要回答五个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;目标。&lt;/strong&gt; 做完以后要得到什么，用一个能观察的结果来写。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;范围。&lt;/strong&gt; 允许改哪些文件、哪些不能动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入。&lt;/strong&gt; 相关代码、报错、示例数据放在哪里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;约束。&lt;/strong&gt; 必须使用的写法，以及明确不要做的事。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完成标准。&lt;/strong&gt; 怎样算做完，例如测试通过、页面能打开、输出包含某几项。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;比较这两条：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;帮我优化一下配置读取。
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;目标：让 check_json_config.py 在 app.json 缺少 port 时退出，并提示缺少的字段名。
范围：只改这个文件，不要新增依赖。
输入：配置是 UTF-8 的 JSON，字段有 name、port、debug。
约束：继续使用标准库 json 和 pathlib。
完成：用一份缺少 port 的配置运行，退出信息里要出现 port。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二条没有增加多少字，但每一句都能拿去核对。第一条里的「优化」可以理解成改名、提速、换格式或加功能，工具只能猜。&lt;/p&gt;
&lt;h2&gt;把不能做的事写成一句&lt;/h2&gt;
&lt;p&gt;范围如果只写「可以改配置相关代码」，仍可能连带改文档、升级依赖、提交版本库。需要保住的边界要直接写出来，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不要升级已经写明版本的依赖。&lt;/li&gt;
&lt;li&gt;不要新建账号，不要改域名解析。&lt;/li&gt;
&lt;li&gt;不要把示例里的密钥、口令写进文章或日志。&lt;/li&gt;
&lt;li&gt;没有运行结果时，不要写「已经测试通过」。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些句子不是态度，是验收条件。做完以后可以逐条看有没有越过。&lt;/p&gt;
&lt;h2&gt;输入要能复现，不要只形容&lt;/h2&gt;
&lt;p&gt;「这里报错了」不足以让工具定位。把报错原文、执行的命令和当前目录一并放上。报错很长时，保留异常类型、最后几行和你执行的命令，不要只留一句「失败了」。&lt;/p&gt;
&lt;p&gt;希望它仿照某种格式时，给一个短例子。比如要一段 JSON，就贴出字段名和类型，而不是说「按常规配置来」。常规在不同项目里不是同一件事。&lt;/p&gt;
&lt;p&gt;例子也要真实可运行。临时编一个不存在的函数名，工具会顺着这个名字继续写，错误会扩散到后面的步骤。&lt;/p&gt;
&lt;h2&gt;一次只交一件事&lt;/h2&gt;
&lt;p&gt;「把文章写完、翻译、配图、再部署到服务器」是四件任务。前一件的结果要作为后一件的输入时，分成几次交。一次交完，中间错了也不容易停下来改。&lt;/p&gt;
&lt;p&gt;如果任务本身有顺序，就写成编号步骤，并写明某一步失败时停住，不要自行换一种做法。例如：检查发现端口被占用，就报告占用情况，而不是改去另一个端口继续。&lt;/p&gt;
&lt;h2&gt;常见错误&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;用「随便」「好看一点」「正常一点」这种无法核对的词当完成标准。&lt;/li&gt;
&lt;li&gt;把背景写了很长，真正要做的动作放在最后半句。工具可能把背景当成要执行的步骤。&lt;/li&gt;
&lt;li&gt;同时给出两个互相矛盾的要求，例如「不要改接口」又「给接口加一个必填参数」。&lt;/li&gt;
&lt;li&gt;没说输出现在哪里。结果写进了聊天记录，没有写进约定的文件，下一轮还得再找一次。&lt;/li&gt;
&lt;li&gt;发现结果不对，只回复「还是不行」。补充实际输出和预期输出，修改才有依据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;交出去之前自己读一遍：如果把工具换成一位不了解项目的同事，他能不能只靠这段文字动手，并且知道做到哪一步该停。不能的话，缺的就是某一条边界，而不是更华丽的措辞。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;清楚的任务说明由目标、范围、输入、约束和完成标准组成。每一样都写成可以观察的事实，并把不许跨越的边界单独列出。一次交一件事，返回结果用原来的完成标准核对，返工会少很多。&lt;/p&gt;</content:encoded></item><item><title>文件路径、相对路径与绝对路径的常见问题</title><link>https://www.aicore.icu/blog/file-paths</link><guid isPermaLink="true">https://www.aicore.icu/blog/file-paths</guid><description>说明相对路径到底相对哪里、为什么换个目录脚本就找不到文件，以及怎样用所在目录拼出稳定路径。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;脚本在自己的目录里能读到配置，换一个目录执行就报「找不到文件」。路径字符串没改过，变化的是程序把它当成相对路径时的起点。把这个起点说清楚，这类问题就比较好判断。&lt;/p&gt;
&lt;h2&gt;两种路径差在哪&lt;/h2&gt;
&lt;p&gt;绝对路径从盘符或根目录写起，本身就能定位到一个位置。Windows 上 &lt;code&gt;I:\vps\aicore-site\examples\app.json&lt;/code&gt; 是绝对路径，Linux 上 &lt;code&gt;/srv/aicore/app.json&lt;/code&gt; 也是。&lt;/p&gt;
&lt;p&gt;相对路径没有这个起点，例如 &lt;code&gt;examples\app.json&lt;/code&gt; 或 &lt;code&gt;../config/app.json&lt;/code&gt;。它相对的是进程的当前工作目录，不是脚本文件所在的目录，也不是用户主目录。&lt;/p&gt;
&lt;p&gt;在项目目录 &lt;code&gt;I:\vps\aicore-site&lt;/code&gt; 里，相对路径 &lt;code&gt;examples\app.json&lt;/code&gt; 会落到 &lt;code&gt;I:\vps\aicore-site\examples\app.json&lt;/code&gt;。把当前目录换成别的地方再执行同一条命令，它会改到新目录下面的 &lt;code&gt;examples\app.json&lt;/code&gt;，原来的文件还在原地，只是这条相对路径已经不指向它。&lt;/p&gt;
&lt;h2&gt;用一次小程序看清楚&lt;/h2&gt;
&lt;p&gt;下面的程序使用 Python 标准库 &lt;code&gt;pathlib&lt;/code&gt;，在 Python 3.14.3 里运行过。它同时打印三种写法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pathlib import Path

def describe(label: str, path: Path) -&gt; None:
    resolved = path.resolve()
    print(label)
    print(f&quot;  写法：{path}&quot;)
    print(f&quot;  是否绝对路径：{path.is_absolute()}&quot;)
    print(f&quot;  解析结果：{resolved}&quot;)
    print(f&quot;  文件存在：{resolved.is_file()}&quot;)

here = Path(__file__).resolve().parent
describe(&quot;脚本所在目录下的配置&quot;, here / &quot;app.json&quot;)
describe(&quot;从当前工作目录出发的相对路径&quot;, Path(&quot;examples&quot;) / &quot;app.json&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Path(__file__).resolve().parent&lt;/code&gt; 取的是这个脚本自己所在的目录，和从哪里启动无关。&lt;code&gt;Path(&quot;examples&quot;) / &quot;app.json&quot;&lt;/code&gt; 则从当前工作目录往下找。&lt;/p&gt;
&lt;p&gt;在项目目录执行时，两种写法都能找到文件。换到另一个目录、用绝对路径启动同一个脚本时，第一种仍然指向脚本旁边的 &lt;code&gt;app.json&lt;/code&gt;，第二种会指向新工作目录下的 &lt;code&gt;examples\app.json&lt;/code&gt;，&lt;code&gt;is_file()&lt;/code&gt; 变成假。&lt;/p&gt;
&lt;p&gt;所以配置、模板、数据文件如果和脚本放在一起，就从 &lt;code&gt;__file__&lt;/code&gt; 出发去拼，不要从「我以为的当前目录」出发。&lt;/p&gt;
&lt;h2&gt;拼接时别用手写斜杠&lt;/h2&gt;
&lt;p&gt;不同系统的分隔符不一样。Windows 常见反斜杠，站点路径和 Linux 用正斜杠。用字符串相加很容易拼出 &lt;code&gt;examplesapp.json&lt;/code&gt;，或者在中间多出两层分隔符。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pathlib&lt;/code&gt; 的 &lt;code&gt;/&lt;/code&gt; 负责插入当前系统的分隔符：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;config = Path(__file__).resolve().parent / &quot;app.json&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要把路径交给只接受字符串的旧接口时，最后再 &lt;code&gt;str(config)&lt;/code&gt;。在那之前尽量让它保持为路径对象，存在性用 &lt;code&gt;is_file()&lt;/code&gt;、&lt;code&gt;is_dir()&lt;/code&gt; 判断，不要先换成字符串再到处拼接。&lt;/p&gt;
&lt;h2&gt;常见错误&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;把相对路径写进开机自启、计划任务或服务配置里。这些程序的当前目录常常是系统目录，不是你开发时的项目目录。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;..&lt;/code&gt; 连续向上跳，例如 &lt;code&gt;../../../../data&lt;/code&gt;。目录一调整，这条路径就指向项目外面，甚至指向不该读的位置。&lt;/li&gt;
&lt;li&gt;把用户输入直接接在基础目录后面。规范化之后如果结果不在预定目录里，应拒绝访问，而不是继续打开。&lt;/li&gt;
&lt;li&gt;只比较路径字符串。&lt;code&gt;.\app.json&lt;/code&gt; 和 &lt;code&gt;app.json&lt;/code&gt; 写法不同，解析后可能是同一个文件。比较前先 &lt;code&gt;resolve()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;忽略大小写差异。Windows 的路径比较通常不区分大小写，Linux 区分。同一套代码换系统后，&lt;code&gt;Config.json&lt;/code&gt; 和 &lt;code&gt;config.json&lt;/code&gt; 不会自动当成同一个文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从计划任务或服务里启动程序时，优先把要读的文件写成绝对路径，或者在启动命令里明确设置工作目录。不要依赖「上次在终端里 cd 到了哪里」。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;相对路径的起点是当前工作目录，不是脚本所在目录。和程序放在一起的文件，从脚本位置拼过去；交给系统长期运行的任务，把路径写绝对，并在启动时打印解析后的结果。路径出问题时，先打印「原始写法、是否绝对、解析结果、文件是否存在」这四项，比反复改斜杠方向更快。&lt;/p&gt;</content:encoded></item><item><title>HTTP 与 HTTPS：个人网站需要了解的基础知识</title><link>https://www.aicore.icu/blog/http-https-basics</link><guid isPermaLink="true">https://www.aicore.icu/blog/http-https-basics</guid><description>说明浏览器如何用 HTTP 请求一个页面、HTTPS 在此之上补了什么，以及个人网站上线时证书和跳转要核对的地方。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;浏览器地址栏里的 &lt;code&gt;http://&lt;/code&gt; 和 &lt;code&gt;https://&lt;/code&gt; 只差一个字母，打开失败时的原因却常常不在页面文件，而在这两套访问方式没有配齐。个人网站上线前，把请求、响应、证书和跳转这几件事分开看，排错会直接一些。&lt;/p&gt;
&lt;h2&gt;一次访问其实是一组请求&lt;/h2&gt;
&lt;p&gt;在地址栏输入页面地址后，浏览器按 HTTP 向网站所在的服务器发请求。请求里最关键的是三样东西：方法、路径和主机名。打开首页时方法一般是 &lt;code&gt;GET&lt;/code&gt;，路径是 &lt;code&gt;/&lt;/code&gt;，主机名来自地址栏，例如 &lt;code&gt;www.aicore.icu&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;服务器返回一段状态和正文。常见状态里，&lt;code&gt;200&lt;/code&gt; 表示拿到了内容，&lt;code&gt;301&lt;/code&gt; 或 &lt;code&gt;302&lt;/code&gt; 表示要改去另一个地址，&lt;code&gt;404&lt;/code&gt; 表示这条路径上没有内容。正文就是 HTML、样式、图片或脚本。&lt;/p&gt;
&lt;p&gt;一个页面很少只发一次请求。HTML 里引用了样式表、字库和脚本，浏览器会按这些地址再发请求。其中任何一个地址写成了别的网站，页面显示时就要依赖那一个网站；那个网站慢、打不开或内容变了，自己的页面都会受影响。个人站点把样式、字库、图片和脚本放到自己的域名下，页面是否完整就不由别人决定。&lt;/p&gt;
&lt;p&gt;HTTP 本身不管这些文件放在哪一台机器上，它只规定请求和响应怎么写。文件是静态生成好的，还是临时拼出来的，对浏览器来说都是一次响应。&lt;/p&gt;
&lt;h2&gt;HTTPS 比 HTTP 多了身份和加密&lt;/h2&gt;
&lt;p&gt;HTTP 把请求和响应直接发出去。在咖啡店、小区和公司的网络里，链路上的设备有机会看到或改动这些内容。HTTPS 先完成 TLS 握手，再在这条加密通道里传送原来的 HTTP 报文。&lt;/p&gt;
&lt;p&gt;握手时服务器要出示证书。浏览器检查三件事：证书是受信任的机构签发的，证书没有过期，证书里的名字覆盖地址栏里的主机名。&lt;code&gt;www.aicore.icu&lt;/code&gt; 和 &lt;code&gt;aicore.icu&lt;/code&gt; 是两个名字。证书只写了其中一个时，另一个会报名称不匹配，即使 IP 地址相同也一样。&lt;/p&gt;
&lt;p&gt;证书过期或名字不对时，浏览器会拦住访问。这时候页面文件通常还在，问题出在证书，而不是文章内容。&lt;/p&gt;
&lt;h2&gt;个人网站上线时要核对的四项&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;两个域名都解析到站点所在的服务器。只解析了带 &lt;code&gt;www&lt;/code&gt; 的那一个，另一个地址无法打开。&lt;/li&gt;
&lt;li&gt;证书同时覆盖要使用的名字，或者每个名字都有自己的有效证书。&lt;/li&gt;
&lt;li&gt;选定一个正式地址。本文的站点以 &lt;code&gt;https://www.aicore.icu&lt;/code&gt; 为准，另一个域名的 HTTP 和 HTTPS 都跳到它，避免同一篇文章出现两个地址。&lt;/li&gt;
&lt;li&gt;跳转使用 &lt;code&gt;301&lt;/code&gt;，并且只跳到 HTTPS 的正式地址，不要在两个域名之间来回跳。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;静态页面托管在 Web 服务器上时，这些规则写在服务器配置里，不用放进每一篇文章。改完配置后，用浏览器无痕窗口分别打开四个地址：两个域名，各自的 HTTP 与 HTTPS。最终都应落在同一个带锁的正式地址，刷新以后地址不再变化。&lt;/p&gt;
&lt;h2&gt;常见错误&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;页面能用 IP 打开，用域名却失败。多半是域名没有解析到这台机器，或证书里没有这个域名。&lt;/li&gt;
&lt;li&gt;只开了 HTTPS，没有把 HTTP 跳过去。访客从旧链接进来会连接失败，或者看到未加密的重复内容。&lt;/li&gt;
&lt;li&gt;证书续期只覆盖了主域名，忘了带 &lt;code&gt;www&lt;/code&gt; 的名字。到期前主站正常，另一个入口先报错。&lt;/li&gt;
&lt;li&gt;页面里的图片、字库或脚本仍指向别的网站。主站是 HTTPS，那些资源一旦失败，版式和功能会缺一块。&lt;/li&gt;
&lt;li&gt;把跳转写成了页面里的刷新标签，而服务器仍返回 &lt;code&gt;200&lt;/code&gt;。检索和收藏会把两个地址当成两页。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本机用 &lt;code&gt;curl -I&lt;/code&gt; 看响应头时，关注两行就够：状态是不是 &lt;code&gt;301&lt;/code&gt;，&lt;code&gt;Location&lt;/code&gt; 是不是正式的 HTTPS 地址。浏览器如果提示证书无效，先看证书的名称和有效期，不要先去改文章。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;HTTP 负责提出要哪一页、返回那一页；HTTPS 在传输前确认服务器证书并加密内容。个人网站把正式地址定成一个 HTTPS 域名，让其余入口都跳过去，并把页面依赖的文件放在自己的站点里。这三件事做到，访问失败时也能分清是解析、证书、跳转还是页面本身的问题。&lt;/p&gt;</content:encoded></item><item><title>Markdown 技术笔记写作与排版指南</title><link>https://www.aicore.icu/blog/markdown-notes</link><guid isPermaLink="true">https://www.aicore.icu/blog/markdown-notes</guid><description>用标题、列表、代码块和链接把一篇技术笔记写成别人打开就能顺着读下去的样子，并避开常见排版错误。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;写技术笔记时，最常见的问题不是不会用某个符号，而是打开文档以后找不到重点。一段里混着现象、命令和结论，读者要来回翻才能知道下一步该做什么。Markdown 本身只提供少数几种结构，把这几种用稳，笔记就会清楚很多。&lt;/p&gt;
&lt;p&gt;本文按 &lt;a href=&quot;https://spec.commonmark.org/&quot;&gt;CommonMark&lt;/a&gt; 里稳定的基础语法来写。不同编辑器会加自己的扩展，笔记如果要在网站、文档和代码仓库之间搬来搬去，先用这些基础写法更稳妥。&lt;/p&gt;
&lt;h2&gt;先定结构，再填内容&lt;/h2&gt;
&lt;p&gt;一篇能读完的笔记，通常只有四块：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这篇要解决什么问题。&lt;/li&gt;
&lt;li&gt;正确做法是什么，按顺序写。&lt;/li&gt;
&lt;li&gt;哪些写法看起来对、实际会错。&lt;/li&gt;
&lt;li&gt;最后用几句话收回结论。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;标题用来标出这四块，不要拿它当装饰。一级标题留给文章题目。正文从二级标题开始，同一层标题讲同一类事情。如果一个小节里又出现了独立的步骤，再往下用三级标题，不要跳级。&lt;/p&gt;
&lt;p&gt;标题单独占一行，前后留空行。和上一行粘在一起时，有的渲染器会把它当成普通文字。&lt;/p&gt;
&lt;h2&gt;把步骤写成列表&lt;/h2&gt;
&lt;p&gt;操作顺序用有序列表，并列的注意事项用无序列表。列表项尽量写成完整的一句，不要只丢一个词。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;1. 把配置保存为 UTF-8。
2. 用检查工具确认括号和引号成对。
3. 再启动使用这份配置的程序。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;列表前后也要留空行。少了空行时，列表可能被并进上一段，序号和缩进都会乱。&lt;/p&gt;
&lt;p&gt;需要强调的词用一对 &lt;code&gt;**&lt;/code&gt;，例如 &lt;strong&gt;先备份再覆盖&lt;/strong&gt;。斜体在中文里辨识度不高，少用。不要整段加粗，加粗多了等于没有重点。&lt;/p&gt;
&lt;h2&gt;代码和命令单独成块&lt;/h2&gt;
&lt;p&gt;文件名、命令、配置键放在行内代码里，用一对反引号包住，例如 &lt;code&gt;package.json&lt;/code&gt;。超过一行的内容放进围栏代码块，并写上语言名称，方便阅读器着色：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;```python
from pathlib import Path
text = Path(&quot;app.json&quot;).read_text(encoding=&quot;utf-8&quot;)
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;语言名称写常见的小写名字即可，如 &lt;code&gt;python&lt;/code&gt;、&lt;code&gt;json&lt;/code&gt;、&lt;code&gt;bash&lt;/code&gt;。不确定就留空，不要写一个阅读器不认识的名字。&lt;/p&gt;
&lt;p&gt;代码块里再出现三个反引号时，外层改用四个反引号，否则代码块会提前结束。命令如果依赖当前目录，在代码块上面用一句话写明「在项目目录中执行」，不要让读者猜。&lt;/p&gt;
&lt;h2&gt;链接只放真正有用的地址&lt;/h2&gt;
&lt;p&gt;正文里需要出处时，用方括号写读者能看懂的文字，圆括号里放地址：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;格式说明见 [CommonMark 规范](https://spec.commonmark.org/)。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;链接文字写成「点击这里」没有信息量。地址很长时，也不要把整串网址铺在句子中间。本地笔记里的相对链接，例如 &lt;code&gt;[安装说明](./install.md)&lt;/code&gt;，换了目录以后可能失效，发布前要点一次确认。&lt;/p&gt;
&lt;p&gt;图片同理：&lt;code&gt;![配置文件截图](./config.png)&lt;/code&gt;。方括号里写这张图在说什么。纯装饰图可以留空，但说明性的图不要留空，否则读屏工具只能读到「图片」。&lt;/p&gt;
&lt;h2&gt;常见错误&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;中文和英文标点混着用，列表符号又换成全角，渲染器认不出列表。&lt;/li&gt;
&lt;li&gt;用 Tab 和空格混着缩进。嵌套列表时，子项前面统一空两格或四格，并在全文保持一致。&lt;/li&gt;
&lt;li&gt;在 JSON、命令示例里加 Markdown 才有的 &lt;code&gt;&gt;&lt;/code&gt; 引用符号，复制出去以后命令无法执行。&lt;/li&gt;
&lt;li&gt;表格列对不齐就硬排。列数不稳定时，改用列表，比一张挤乱的表更容易读。&lt;/li&gt;
&lt;li&gt;把整篇笔记放在一个一级标题下面，中间不再分段。读者无法跳着查。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;写完以后先只看标题列表：如果标题串起来不能说明这篇笔记的顺序，正文再长也难用。然后再把代码块复制到对应的工具里跑一次，确认围栏没有把命令截断。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;Markdown 笔记靠标题分层、列表表顺序、代码块隔离可复制内容、链接给出处。这四样保持空行和层级正确，排版就不会跟内容抢注意力。扩展语法可以以后再加，基础结构稳定之后，换编辑器也不容易乱。&lt;/p&gt;</content:encoded></item><item><title>使用 Python 读取与校验 JSON 配置文件</title><link>https://www.aicore.icu/blog/python-json-config</link><guid isPermaLink="true">https://www.aicore.icu/blog/python-json-config</guid><description>用 Python 标准库读取 UTF-8 的 JSON 配置，检查字段是否齐全、类型是否正确，并给出能直接运行的示例。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;程序启动时读一份 JSON 配置，最怕的不是文件不存在，而是文件能打开、内容却少了一个字段，或者端口被写成了字符串。程序继续往下跑，等到真正用到这个值才报错，排查时已经离配置文件很远了。&lt;/p&gt;
&lt;p&gt;下面的做法只使用 Python 标准库。当前文档以 Python 3.14 为准：&lt;code&gt;json.loads&lt;/code&gt; 在文本不是合法 JSON 时抛出 &lt;code&gt;json.JSONDecodeError&lt;/code&gt;，这个异常带有行号和列号。示例在 Python 3.14.3 里运行过。&lt;/p&gt;
&lt;h2&gt;先约定配置长什么样&lt;/h2&gt;
&lt;p&gt;一份本机服务配置可以是这样，保存为 &lt;code&gt;app.json&lt;/code&gt;，编码用 UTF-8，不要带注释：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;笔记站点&quot;,
  &quot;port&quot;: 8080,
  &quot;debug&quot;: false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSON 没有注释语法。文件里出现 &lt;code&gt;//&lt;/code&gt; 或 &lt;code&gt;/* */&lt;/code&gt;，解析会直接失败。需要说明时，把说明写进旁边的文档，不要写进配置文件。&lt;/p&gt;
&lt;h2&gt;读取并检查字段&lt;/h2&gt;
&lt;p&gt;把下面的程序保存为 &lt;code&gt;check_json_config.py&lt;/code&gt;，和 &lt;code&gt;app.json&lt;/code&gt; 放在同一目录：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json
from pathlib import Path

REQUIRED = {
    &quot;name&quot;: str,
    &quot;port&quot;: int,
    &quot;debug&quot;: bool,
}

def load_config(path: Path) -&gt; dict:
    try:
        text = path.read_text(encoding=&quot;utf-8&quot;)
    except FileNotFoundError:
        raise SystemExit(f&quot;找不到配置文件：{path}&quot;)
    except UnicodeDecodeError as exc:
        raise SystemExit(f&quot;文件不是 UTF-8 文本：{exc}&quot;)

    try:
        data = json.loads(text)
    except json.JSONDecodeError as exc:
        raise SystemExit(
            f&quot;JSON 格式错误：第 {exc.lineno} 行，第 {exc.colno} 列，{exc.msg}&quot;
        )

    if not isinstance(data, dict):
        raise SystemExit(&quot;配置文件的最外层必须是对象，不能是数组或单个值&quot;)

    missing = [key for key in REQUIRED if key not in data]
    if missing:
        raise SystemExit(&quot;缺少字段：&quot; + &quot;、&quot;.join(missing))

    for key, expected in REQUIRED.items():
        value = data[key]
        if isinstance(value, bool) and expected is int:
            raise SystemExit(f&quot;字段 {key} 应该是整数，不能是 true 或 false&quot;)
        if not isinstance(value, expected):
            raise SystemExit(f&quot;字段 {key} 的类型应为 {expected.__name__}&quot;)

    if not 1 &amp;#x3C;= data[&quot;port&quot;] &amp;#x3C;= 65535:
        raise SystemExit(f&quot;端口 {data[&apos;port&apos;]} 超出 1 到 65535 的范围&quot;)

    return data

if __name__ == &quot;__main__&quot;:
    config = load_config(Path(__file__).with_name(&quot;app.json&quot;))
    print(f&quot;配置可用：{config[&apos;name&apos;]}，端口 {config[&apos;port&apos;]}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在该目录执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python check_json_config.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置正确时输出：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;配置可用：笔记站点，端口 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有两个容易忽略的点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Path.read_text(encoding=&quot;utf-8&quot;)&lt;/code&gt; 把编码说死。Windows 上不写编码时，会随系统区域设置变化，同一份含中文的文件换一台机器就可能读失败。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bool&lt;/code&gt; 是 &lt;code&gt;int&lt;/code&gt; 的子类。如果只写 &lt;code&gt;isinstance(True, int)&lt;/code&gt;，结果是真。端口写成 &lt;code&gt;true&lt;/code&gt; 时会混过去，所以要先把布尔值单独拒绝。&lt;/p&gt;
&lt;h2&gt;常见错误&lt;/h2&gt;
&lt;p&gt;最后一项后面多一个逗号，是手写 JSON 最常见的错误。解析器会报告类似：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;第 3 行，第 15 列，Illegal trailing comma before end of object
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他几类也值得在启动时拦住：&lt;/p&gt;
&lt;p&gt;| 文件内容 | 程序给出的结果 |
| --- | --- |
| 没有 &lt;code&gt;port&lt;/code&gt; | 缺少字段：port |
| &lt;code&gt;&quot;port&quot;: true&lt;/code&gt; | 字段 port 应该是整数，不能是 true 或 false |
| &lt;code&gt;&quot;port&quot;: 70000&lt;/code&gt; | 端口超出 1 到 65535 的范围 |
| 最外层是数组 | 最外层必须是对象 |
| 文件不存在 | 找不到配置文件 |&lt;/p&gt;
&lt;p&gt;只想先确认括号和引号，可以用标准库自带的命令。它默认会把非英文字符转成 &lt;code&gt;\u&lt;/code&gt; 形式，阅读中文时加上 &lt;code&gt;--no-ensure-ascii&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -m json.tool --no-ensure-ascii app.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令能打印出排好的内容，只说明语法正确，不说明字段齐全。字段和取值范围仍要由程序自己检查。&lt;/p&gt;
&lt;p&gt;不要用 &lt;code&gt;eval&lt;/code&gt; 去读配置。JSON 里出现计算表达式或函数调用时，那已经不是配置文件该做的事，也会把不该执行的内容带进来。&lt;/p&gt;
&lt;h2&gt;类型和编码还会碰到的情况&lt;/h2&gt;
&lt;p&gt;数字并不都是整数。&lt;code&gt;json.loads(&quot;1.5&quot;)&lt;/code&gt; 得到的是浮点数，&lt;code&gt;isinstance(1.5, int)&lt;/code&gt; 为假，所以上面的端口检查会拒绝 &lt;code&gt;&quot;port&quot;: 1.5&lt;/code&gt;。如果某个字段确实要小数，期望类型就写成 &lt;code&gt;float&lt;/code&gt;，同时接受整数，因为 JSON 的 &lt;code&gt;1&lt;/code&gt; 会变成 &lt;code&gt;int&lt;/code&gt; 而不是 &lt;code&gt;float&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;空值是 &lt;code&gt;None&lt;/code&gt;，不是空字符串。配置里写 &lt;code&gt;&quot;name&quot;: null&lt;/code&gt; 时，类型检查会要求字符串，从而在启动时失败。这比读到一半才发现名字是空的要早。&lt;/p&gt;
&lt;p&gt;文件开头如果带了编辑器加的 BOM，&lt;code&gt;utf-8&lt;/code&gt; 解码会失败，错误信息里会出现 &lt;code&gt;BOM&lt;/code&gt;。解决办法是把文件另存为不带 BOM 的 UTF-8，而不是在程序里悄悄忽略所有解码错误。忽略错误会把后面的中文读成乱码，配置仍能「打开」，内容却是错的。&lt;/p&gt;
&lt;p&gt;字段多出来时，这份示例选择保留，不报错。名称写错时，例如把 &lt;code&gt;port&lt;/code&gt; 写成 &lt;code&gt;ports&lt;/code&gt;，缺少字段的检查仍会抓住真正要用的 &lt;code&gt;port&lt;/code&gt;。如果配置越来越长，再把允许的字段列成白名单，多出来的键也一并提示，避免拼写错误被静默留下。&lt;/p&gt;
&lt;p&gt;检查通过之后再把配置交给真正启动服务的代码。不要在业务逻辑里重复写一套 &lt;code&gt;if &quot;port&quot; in data&lt;/code&gt;。校验集中在 &lt;code&gt;load_config&lt;/code&gt;，后面的代码就可以认为这三个字段一定存在，而且类型正确。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;读取 JSON 配置时，把「文件打不开」「不是 UTF-8」「语法不对」「字段缺失」「类型不对」「取值越界」分成几步报出来。错误出在启动阶段，并带上行号，比服务跑起来以后再查要直接得多。&lt;/p&gt;</content:encoded></item><item><title>静态网站的文章更新、备份与回滚实践</title><link>https://www.aicore.icu/blog/static-site-release</link><guid isPermaLink="true">https://www.aicore.icu/blog/static-site-release</guid><description>说明静态网站改文章后如何构建、备份和替换上线目录，以及新版本出错时怎样用备份目录切回去。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;静态网站的页面是构建时生成好的文件。更新一篇文章，不是在服务器上改那一页 HTML，而是改源文件、重新构建，再用新的文件替换网站目录。替换如果直接覆盖，构建出错或页面有问题时，就没有完整的上一版可以立刻换回。&lt;/p&gt;
&lt;p&gt;把「源文件、构建结果、正在对外的目录、备份」分成四个位置，更新和回滚都只做复制，不在对外目录里临时改页面。&lt;/p&gt;
&lt;h2&gt;四个目录各放什么&lt;/h2&gt;
&lt;p&gt;以本站为例，可以按下面这样放：&lt;/p&gt;
&lt;p&gt;| 位置 | 内容 | 能不能当网站根目录 |
| --- | --- | --- |
| 源码目录 | 文章、配置、主题 | 不能，这里是工程 |
| &lt;code&gt;dist/&lt;/code&gt; | 本次构建出的 HTML、样式、脚本 | 可以，但是候选版本 |
| &lt;code&gt;/srv/aicore/current&lt;/code&gt; | 现在对外的文件 | 这是网站根目录 |
| &lt;code&gt;/srv/aicore/releases/时间戳&lt;/code&gt; | 一份完整的历史版本 | 回滚时整份换上 |&lt;/p&gt;
&lt;p&gt;源码目录里有依赖和中间文件，不能指给 Web 服务器。每次只发布 &lt;code&gt;dist/&lt;/code&gt; 里的结果。&lt;/p&gt;
&lt;h2&gt;发布前先留一份完整备份&lt;/h2&gt;
&lt;p&gt;在替换之前，把当前对外目录整份复制出来。目录名用时间，避免第二次发布把上一次备份盖掉：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stamp=$(date +%Y%m%dT%H%M%SZ)
mkdir -p /srv/aicore/releases
cp -a /srv/aicore/current &quot;/srv/aicore/releases/${stamp}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;cp -a&lt;/code&gt; 保留权限和时间。备份完成后看一下文件数量，不要只看命令没有报错。空目录复制成功，也仍然是空的。&lt;/p&gt;
&lt;p&gt;服务器上如果同时跑着别的网站，备份和替换都只发生在这个站点自己的目录里，不要打包整个 &lt;code&gt;/srv&lt;/code&gt; 或 Web 服务器的配置，除非这次确实改了配置。&lt;/p&gt;
&lt;h2&gt;用新目录替换，而不是在旧目录里覆盖&lt;/h2&gt;
&lt;p&gt;构建在源码目录完成，确认 &lt;code&gt;dist/&lt;/code&gt; 里有首页和文章后再上传。上传到一个新目录，例如 &lt;code&gt;/srv/aicore/releases/20261026T090000Z&lt;/code&gt;，不要直接传进 &lt;code&gt;current&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;传完后在服务器上检查三件事：首页文件存在，一篇文章的路径存在，样式和字库也在这个目录里。然后把对外目录切过去。目录本身如果是一个符号链接，切换就是一次改链接：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ln -sfn /srv/aicore/releases/20261026T090000Z /srv/aicore/current
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Web 服务器的根目录指着 &lt;code&gt;current&lt;/code&gt;，改链接后新请求就会读到新文件。已经打开的页面需要刷新。&lt;/p&gt;
&lt;p&gt;如果系统不允许用符号链接，就保持两个真实目录，在 Web 服务器配置里把根目录从旧版本改成新版本，再重载配置。重载前保留旧配置的副本，这样切回去时有据可依。&lt;/p&gt;
&lt;h2&gt;出错时整份回滚&lt;/h2&gt;
&lt;p&gt;新版本出现页面打不开、样式丢失或文章内容不对，不要在对外目录里手改 HTML。那些改动会在下一次发布时被盖掉，也和源文件对不上。&lt;/p&gt;
&lt;p&gt;回滚就是把 &lt;code&gt;current&lt;/code&gt; 指回上一份备份：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ln -sfn /srv/aicore/releases/上一次的时间戳 /srv/aicore/current
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;切回去以后，用浏览器打开首页、文章列表和刚才出错的那一篇，确认是旧内容。然后再回到源码里修，重新构建，作为再下一次发布。不要把服务器上改过的 HTML 反抄回源码，两条线会越差越远。&lt;/p&gt;
&lt;p&gt;备份保留最近几份即可。确认新版本可用之后，再删更早的副本，给磁盘留空间。正在被 &lt;code&gt;current&lt;/code&gt; 指着的那一份不能删。&lt;/p&gt;
&lt;h2&gt;常见错误&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;只备份了新改的那篇文章。样式、脚本和搜索索引是一起生成的，缺任何一件，旧文章页也可能显示不完整。回滚要整份目录。&lt;/li&gt;
&lt;li&gt;构建失败了仍把半成品传上去。应先在本地打开构建结果，至少看首页和一篇文章。&lt;/li&gt;
&lt;li&gt;新版本覆盖了旧版本的同一目录，没有时间戳。发现问题时已经没有完整旧文件。&lt;/li&gt;
&lt;li&gt;改了文章却忘了重新构建，服务器上还是上一次的 HTML。源文件新、网页旧，核对的是网页而不是编辑器。&lt;/li&gt;
&lt;li&gt;回滚只换了页面，没换同时改过的服务器配置。配置和文件要各自有上一份可用的副本。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每次发布把四件事记下来：源码位置、使用的版本、对外目录、这次备份的路径。下次接手的人不需要从历史记录里猜哪一份是能用的。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;静态网站的更新单位是一整份构建结果，不是单个 HTML 文件。先把正在对外的目录完整备份，再把新结果放到带时间的目录里，检查后再切换。出问题就把对外目录指回备份，然后回到源码里修。这样每一次发布都可以前进，也可以退回上一个已知可用的版本。&lt;/p&gt;</content:encoded></item></channel></rss>