news 2026/7/8 2:09:38

LLM:用一条命令统一所有大语言模型的 CLI 工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLM:用一条命令统一所有大语言模型的 CLI 工具

深度解析 simonw/llm 项目——12.2k Star、63 个版本迭代、50+ 插件生态,如何成为终端 AI 工具的事实标准。

项目概览

LLM 是由 Django 联合创始人、知名开源开发者 Simon Willison 创建的命令行工具和 Python 库,用于统一访问 OpenAI、Anthropic Claude、Google Gemini、Meta Llama 等数十种大语言模型。它不仅支持远程 API 调用,还支持通过插件在本地运行模型。

关键数据:

指标数值
GitHub Stars12,200+
最新版本0.31(2026-04-24)
累计发布版本63
官方+社区插件50+
支持模型数量100+(含插件)
代码语言Python 99.8%
许可证Apache 2.0

核心设计理念

Unix 哲学在 AI 时代的回归

LLM 的设计根植于 Unix 哲学——做一件事并做好。它将"与语言模型交互"这个行为,抽象为一条可以在终端执行的命令:

llm"Ten fun names for a pet pelican"

这行代码的背后是一个精心设计的统一抽象层。用户无需关心底层是 GPT-4o 还是 Claude 4 Opus,无需记忆不同的 API 签名和认证方式,只需要知道模型的名称。

微内核 + 插件架构

LLM 采用了类似操作系统微内核的设计——核心只负责命令行解析、密钥管理、SQLite 日志和插件加载,所有模型的具体实现都通过插件完成:

┌─────────────────────────────────────┐ │ LLM Core │ │ ┌──────────┐ ┌──────────────────┐ │ │ │ CLI 解析 │ │ 密钥管理 │ │ │ └──────────┘ └──────────────────┘ │ │ ┌──────────┐ ┌──────────────────┐ │ │ │SQLite 日志│ │ 插件加载器 │ │ │ └──────────┘ └──────────────────┘ │ └─────────────┬───────────────────────┘ │ ┌─────────┼─────────┐ ▼ ▼ ▼ ┌────────┐┌────────┐┌────────┐ │llm- ││llm- ││llm- │ │openai ││anthropic││ollama │ └────────┘└────────┘└────────┘

这种架构带来了三个关键优势:

  1. 核心精简:基础安装只包含 OpenAI 模型支持,不引入不必要的依赖
  2. 按需扩展:需要 Claude?llm install llm-anthropic。需要本地模型?llm install llm-ollama
  3. 社区驱动:任何人都可以编写插件来支持新的模型提供商

安装与快速上手

安装

支持多种安装方式,覆盖不同用户偏好:

# pippipinstallllm# Homebrew(macOS/Linux)brewinstallllm# pipx(隔离环境安装)pipxinstallllm# uv(Rust 实现的快速包管理器)uv toolinstallllm

30 秒上手

# 1. 设置 OpenAI API Keyllm keyssetopenai# 粘贴你的 API Key# 2. 执行一个提示词(默认使用 gpt-4o-mini)llm"Ten fun names for a pet pelican"# 3. 从图像中提取文本llm"extract text"-ascanned-document.jpg# 4. 用系统提示分析代码catmyfile.py|llm-s"Explain this code"# 5. 启动交互式聊天llm chat-mgpt-4.1

接入其他模型

# Google Geminillminstallllm-gemini llm keyssetgemini llm-mgemini-2.0-flash'Tell me fun facts about Mountain View'# Anthropic Claudellminstallllm-anthropic llm keyssetanthropic llm-mclaude-4-opus'Impress me with wild facts about turnips'# 本地模型(Ollama)llminstallllm-ollama ollama pull llama3.2:latest llm-mllama3.2:latest'What is the capital of France?'

核心功能深度解析

1. 提示词执行与管道

LLM 最基础的能力是在命令行执行提示词,但它远不止于此。它天然适配 Unix 管道:

# 将文件内容作为提示词输入catREADME.md|llm-s"Summarize this in 3 bullet points"# 提取代码块llm-x'Python function to reverse a string'# -x 参数自动提取响应中第一个 fenced code block# 继续上一次对话llm-c"Now do it in JavaScript"# 使用模糊搜索指定模型(不需要记完整 ID)llm-qgpt-q4o"your prompt"

-x--extract)和--xl--extract-last)参数是 LLM 的一个巧妙设计——在终端场景下,你往往只关心代码本身而非模型的解释文字,这两个参数直接提取响应中的代码块,省去了手动复制。

2. 模板系统:可复用的提示词

模板允许你保存和复用提示词配置,包括系统提示、模型选择、参数和工具:

# 创建一个模板llm-s"Translate to French"-mgpt-4.1-mini--savetranslate-fr# 使用模板llm-ttranslate-fr"Hello, how are you?"# 模板支持变量llm-s"Translate to$language"--savetranslate llm-ttranslate-planguage Spanish"Hello"

模板以 YAML 文件存储,结构清晰:

system:Translate to{$language}model:gpt-4.1-minioptions:temperature:0.3

更强大的是,模板可以绑定工具和 Schema(下文详述),这意味着你可以创建"一键执行"的复杂工作流。

GitHub 模板共享:通过llm-templates-github插件,你可以直接使用他人在 GitHub 上分享的模板:

llminstallllm-templates-github llm-tgh:simonw/pelican-svg-mo3-mini

3. Fragments:长上下文组装器

Fragments 是 0.24 版本引入的关键特性,解决了长上下文场景下的提示词组装问题。它允许你从多个来源(URL、文件路径、数据库记录)组装一个长提示词,并且这些内容会在 SQLite 中去重存储:

# 从 URL 加载内容作为上下文llm-fhttps://llm.datasette.io/robots.txt'explain this file'# 组合多个来源llm-fpaper.pdf-fnotes.md'Compare these documents'# 在聊天中使用llm chat-fcodebase/'Ask questions about this code'

Fragments 的真正威力在于插件生态。通过register_fragment_loaders()插件钩子,社区已经构建了丰富的片段加载器:

插件用途示例
llm-video-frames视频转帧序列llm -f video-frames:video.mp4
llm-fragments-github加载整个 GitHub 仓库llm -f github:simonw/files-to-prompt
llm-hacker-news导入 HN 对话llm -f hn:43615912
llm-fragments-pypi加载 PyPI 包信息llm -f pypi:ruff
llm-fragments-pdfPDF 转 Markdownllm -f pdf:paper.pdf

4. Schemas:结构化数据提取

0.23 版本引入的 Schema 功能是 LLM 最具实用价值的特性之一。它让大语言模型输出符合 JSON Schema 的结构化数据,而不是自由文本:

# 使用简洁的 Schema DSLllm--schema'name, age int, bio''Describe a nice dog'# 从新闻中提取人物llm--schema'name, title, organization, role''Extract people from this article'\-fnews-article.txt# 生成数组llm--schema'name, age int, bio'--schema-multi'Describe 3 nice dogs'

在 Python API 中,Schema 可以使用 Pydantic 模型:

importllmfrompydanticimportBaseModelclassDog(BaseModel):name:strage:intmodel=llm.get_model("gpt-4o-mini")response=model.prompt("Describe a nice dog",schema=Dog)# 输出:{"name": "Buddy", "age": 3}

这把 LLM 从"聊天工具"提升为"数据处理管道"——你可以用它从非结构化文本中批量提取结构化数据,然后存入数据库、生成报表或触发下游工作流。

5. Tools:让模型调用外部工具

0.26 版本引入的 Tool 支持是 LLM 的一次重大进化。模型现在可以调用 Python 函数来完成它自身无法完成的任务:

命令行使用:

# 内联定义工具llm--functions' def multiply(x: int, y: int) -> int: """Multiply two numbers.""" return x * y ''what is 34234 * 213345'# 使用插件注册的工具llm-Tsimple_eval'What is 2^20?'

Python API——更强大的用法:

importllmdefmultiply(x:int,y:int)->int:"""Multiply two numbers."""returnx*y model=llm.get_model("gpt-4.1-mini")# chain() 方法自动处理多轮工具调用response=model.chain("What is 34234 * 213345?",tools=[multiply],)print(response.text())

Toolbox 类——有状态的工具集合:

importllmclassMemory(llm.Toolbox):_memory=Nonedef_get_memory(self):ifself._memoryisNone:self._memory={}returnself._memorydefset(self,key:str,value:str):"Set something as a key"self._get_memory()[key]=valuedefget(self,key:str):"Get something from a key"returnself._get_memory().get(key)or""model=llm.get_model("gpt-4.1-mini")memory=Memory()conversation=model.conversation(tools=[memory])# 模型可以在多轮对话中使用 Memory 工具print(conversation.chain("Set name to Simon").text())print(conversation.chain("What is my name?").text())

工具调试钩子——对开发者至关重要的功能:

defbefore_call(tool,tool_call):print(f"About to call{tool.name}with{tool_call.arguments}")defafter_call(tool,tool_call,tool_result):print(f"{tool.name}returned{tool_result.output}")response=model.chain("Complex question requiring tools",tools=[multiply,search],before_call=before_call,after_call=after_call,)

社区已构建了丰富的工具插件:

插件功能
llm-tools-simpleeval数学表达式计算
llm-tools-quickjs沙箱化 JavaScript 执行
llm-tools-sqlite只读 SQL 查询
llm-tools-datasette查询远程 Datasette
llm-tools-exa网络搜索
llm-tools-rag嵌入集合检索

6. Embeddings:向量嵌入与语义搜索

LLM 不仅是语言模型的客户端,还是一个完整的嵌入管理工具:

# 生成单个嵌入llm embed-mtext-embedding-3-small-c"Hello world"# 批量嵌入文件内容llm embed-multi docs-mtext-embedding-3-small\--filesdocs/'*.md'--store# 语义相似度搜索llm similar docs-c"machine learning"

Python API 同等强大:

importllm model=llm.get_embedding_model("text-embedding-3-small")# 生成嵌入向量vector=model.embed("Hello world")# 使用 Collection 进行批量管理collection=llm.Collection("docs",model)collection.embed("doc1","Content of document 1")collection.embed("doc2","Content of document 2")# 语义搜索results=collection.similar("machine learning")

嵌入数据存储在 SQLite 中,配合llm-sentence-transformersllm-clipllm-embed-jina等插件,可以在本地构建完整的语义搜索系统。

7. SQLite 日志:完整的对话审计

LLM 默认将所有提示词和响应记录到本地 SQLite 数据库,这是一个被低估但极为重要的设计决策:

# 查看最近的日志llm logs# 按模型过滤llm logs-mgpt-4o# 搜索日志llm logs-q"pelican"# 查看特定对话llm logs--cidCONVERSATION_ID# 导出 Schema 收集的结构化数据llm logs--data--data-key name# 查看 Token 使用量llm logs-u

这个设计带来了几个重要的好处:

  • 可审计性:每次 AI 调用都有记录,方便回顾和追溯
  • 可分析性:可以统计不同模型的使用频率和 Token 消耗
  • 可复现性:对话历史完整保存,可以随时回溯
  • 与 Datasette 集成:通过 Datasette 可以在浏览器中可视化浏览所有日志

8. 多模态支持

从 0.17 版本开始,LLM 支持图片、音频、视频等多模态输入:

# 图片(URL 或文件)llm-mgpt-4o"describe this image"\-ahttps://example.com/photo.jpg llm-mgpt-4o-mini"extract text"-ascanned-document.jpg# 多个附件llm-mgpt-4o"compare these images"-aimg1.jpg-aimg2.jpg# PDFllm-mgpt-4o"summarize this document"-areport.pdf

Python API 同样支持:

model=llm.get_model("gpt-4o")response=model.prompt("Describe these images",attachments=[llm.Attachment(path="pelican.jpg"),llm.Attachment(url="https://example.com/photo.jpg"),])

插件系统详解

插件钩子(Plugin Hooks)

LLM 的插件系统提供了六个核心钩子,覆盖了所有扩展场景:

钩子用途引入版本
register_commands(cli)添加新的 CLI 命令早期
register_models(register)注册新的语言模型早期
register_embedding_models(register)注册嵌入模型早期
register_tools(register)注册工具函数0.26
register_template_loaders(register)注册模板加载器0.24
register_fragment_loaders(register)注册片段加载器0.24

插件生态全景

本地模型插件:

插件运行框架平台
llm-ggufllama.cpp跨平台
llm-mlxApple MLXmacOS
llm-ollamaOllama跨平台
llm-llamafilellamafile跨平台
llm-gpt4allGPT4All跨平台

远程 API 插件:

覆盖了几乎所有主流模型提供商:Mistral、Gemini、Anthropic、Cohere、Perplexity、Groq、Fireworks、Together AI、DeepSeek、OpenRouter、Bedrock、xAI Grok 等。

如果某个 API 提供商兼容 OpenAI 接口格式,甚至不需要安装插件——只需在extra-openai-models.yaml中配置即可。

编写自己的插件:

LLM 的插件开发体验经过了精心打磨。以一个 Markov 链模型插件为例:

fromllmimportModel,registerclassMarkovModel(Model):model_id="markov"defexecute(self,prompt,stream,response,conversation):# 实现 execute 方法text=build_markov_chain(prompt.prompt)yieldtextdefregister_models(register):register(MarkovModel())

安装和测试:

llminstall-e.llm-mmarkov"some input text"

Python API:超越 CLI 的编程接口

LLM 不仅是 CLI 工具,还是一个完整的 Python 库。这意味着你可以将其集成到任何 Python 应用中:

基础用法

importllm# 获取模型model=llm.get_model("gpt-4o-mini")# 执行提示词(懒加载,直到调用 .text() 才真正执行)response=model.prompt("Five surprising names for a pet pelican")print(response.text())# 流式输出response=model.prompt("A poem about coding")forchunkinresponse:print(chunk,end="",flush=True)

异步支持

importllm model=llm.get_async_model("gpt-4o")# 异步执行print(awaitmodel.prompt("Hello").text())# 异步流式输出asyncforchunkinmodel.prompt("Tell me a story"):print(chunk,end="",flush=True)

对话管理

importllm model=llm.get_model("gpt-4.1-mini")conversation=model.conversation()response1=conversation.prompt("My name is Alice")print(response1.text())response2=conversation.prompt("What is my name?")# 模型知道你叫 Aliceprint(response2.text())

Token 追踪与回调

importllm model=llm.get_model("gpt-4o-mini")response=model.prompt("A poem about a hippo")# 注册完成回调(可以用于追踪 Token 使用量)response.on_done(lambdar:print(r.usage()))print(response.text())# 输出:# Usage(input=20, output=494, details={})

版本演进与关键里程碑

LLM 的版本迭代非常活跃,每个版本都引入了实质性功能:

版本日期核心特性
0.312026-04-24GPT-5.5 支持,文本详细度控制
0.302026-03-31插件钩子增强,文档改进
0.292026-03-17GPT-5.4 系列模型
0.282025-12-12GPT-5.1/5.2,Python 3.10+ 要求
0.272025-08-11GPT-5 系列支持,模板可绑定工具
0.262025-05-27Tool 支持——里程碑版本
0.252025-05-04Fragment loader 支持附件,gpt-4.1/o3/o4-mini
0.242025-04-07Fragments 与模板加载器——长上下文支持
0.232025-02-28Schema 支持——结构化数据提取
0.222025-02-16KeyModel/AsyncKeyModel,llm-mlx 插件
0.202025-01-22o1 模型,-x代码块提取
0.192024-12-01Token 使用量追踪与日志
0.182024-11-17异步模型支持
0.172024-10-29多模态附件——图片/音频/视频
0.162024-09-12OpenAI 模型使用内部 key 机制
0.152024-07-18gpt-4o-mini 成为默认模型
0.142024-05-13GPT-4o 支持

从演进路线可以看出,LLM 的功能拓展遵循了一条清晰的路径:基础调用 → 模型覆盖 → 数据持久化 → 多模态 → 异步 → 结构化输出 → 工具调用 → 长上下文。每一步都建立在之前的基础上,形成了有机的功能体系。

设计亮点与工程思考

1. SQLite 作为一等公民

LLM 对 SQLite 的使用是一个值得学习的工程决策。SQLite 不需要服务器进程,数据就在用户本地的logs.db文件中。这意味着:

  • 零运维成本——不需要安装和配置数据库
  • 便携性——整个对话历史就是一个文件
  • 可查询——用 SQL 查询你的 AI 使用记录
  • 与 Datasette 天然集成——在浏览器中浏览和分析

2. 懒加载的响应设计

Python API 中的Response对象采用懒加载设计——调用model.prompt()不会立即执行请求,只有在调用response.text()时才真正触发。这个设计让流式输出和回调注册变得自然:

response=model.prompt("A poem")response.on_done(lambdar:save_to_db(r))# 注册回调print(response.text())# 执行请求 + 流式输出 + 触发回调

3. API Key 的多层管理

LLM 提供了三种 API Key 管理方式,覆盖不同安全需求:

  1. llm keys set openai——加密存储到keys.json(文件权限 600)
  2. 环境变量OPENAI_API_KEY——CI/CD 和容器化场景
  3. key=参数——Python API 编程场景

4. OpenAI 兼容模型的零插件接入

很多模型提供商(如 DeepSeek、vLLM、LocalAI)提供 OpenAI 兼容的 API。LLM 允许通过extra-openai-models.yaml配置这些模型,无需编写任何代码:

-model_id:deepseek-chatmodel_name:deepseek-chatapi_base:"https://api.deepseek.com/v1"api_key_name:deepseekcan_stream:truevision:truesupports_schema:true

5. 对 Prompt Injection 的坦诚

与许多 AI 工具不同,LLM 在文档中明确警告了 Tool 使用场景下的 Prompt Injection 风险。这种坦诚的态度对于建立用户信任至关重要。

实战场景

场景一:代码审查管道

# 在 CI/CD 中自动审查代码变更gitdiffmain|llm-s"Review this code change. Focus on: 1. Security vulnerabilities 2. Performance issues 3. Code style problems Output as bullet points."-mgpt-4.1-mini

场景二:文档批量提取结构化数据

importllm,json,glob model=llm.get_model("gpt-4.1-mini")forfileinglob.glob("contracts/*.txt"):withopen(file)asf:content=f.read()response=model.prompt(f"Extract contract details from:\n{content}",schema=llm.schema_dsl("party_a, party_b, value float, start_date, end_date, contract_type"))data=json.loads(response.text())print(json.dumps(data,indent=2))

场景三:构建语义搜索系统

# 1. 批量嵌入文档llm embed-multi knowledge-base-mtext-embedding-3-small\--filesdocs/'*.md'--store# 2. 语义搜索llm similar knowledge-base-c"how to configure authentication"# 3. 结合 RAG 工具llminstallllm-tools-rag llm-Trag"What does the documentation say about API keys?"

场景四:带工具的智能助手

importllmdefcalculate(expression:str)->str:"""Evaluate a mathematical expression safely."""importasttry:result=eval(compile(ast.parse(expression,mode='eval'),'','eval'))returnstr(result)exceptExceptionase:returnf"Error:{e}"defsearch_docs(query:str)->str:"""Search the documentation for relevant information."""# 自定义搜索逻辑returnsearch_internal_docs(query)model=llm.get_model("gpt-4.1")conversation=model.conversation(tools=[calculate,search_docs])whileTrue:user_input=input("> ")ifuser_input.lower()in('exit','quit'):breakresponse=conversation.chain(user_input)print(response.text())

与同类工具对比

特性simonw/llmLangChainOllama CLIOpenAI CLI
定位通用 CLI + Python 库应用开发框架本地模型运行OpenAI 专属
多模型支持100+(含插件)通过集成仅本地模型仅 OpenAI
插件系统成熟(6 个钩子)无(库级别扩展)Modelfile
对话日志SQLite 内置需自行实现
结构化输出Schema DSL + Pydantic需 OutputParserJSON Mode
工具调用内置Agent 框架Function Calling
嵌入管理内置需 VectorStore
本地模型通过插件通过集成核心功能
学习曲线

LLM 的独特定位在于:它不是开发框架,而是开发者工具。你不需要学习复杂的 Agent 抽象、Chain 组装或者 Memory 管理——一条命令就能完成大部分工作,需要编程时也有简洁的 Python API。

局限与权衡

  1. 同步模型的阻塞问题:虽然 0.18 版本引入了异步支持,但并非所有插件都实现了AsyncModel,在异步上下文中使用同步模型仍会阻塞事件循环
  2. Token 计费追踪粒度有限:虽然有response.usage()和 SQLite 日志,但缺少按项目、按标签维度的成本聚合能力
  3. 没有内置的 Agent 编排:LLM 提供了model.chain()Toolbox,但没有 LangChain 那样的 Agent 编排框架(如 ReAct、Plan-and-Execute)
  4. Windows 体验弱于 Unix:管道、文件路径、终端交互在 Windows 上偶尔会遇到兼容性问题
  5. Schema 支持依赖模型能力:结构化输出需要模型本身支持,并非所有模型都能可靠地遵循 Schema

总结

simonw/llm 是一个在 AI 工具生态中占据独特位置的项目。它不是另一个 LangChain,也不是另一个 Ollama——它是连接终端和大语言模型之间最直接的那座桥梁。

三个层面的价值:

  • 工具层面:一条命令访问任何模型,管道友好的输入输出,SQLite 完整审计
  • 平台层面:成熟的插件系统、模板共享、Schema/Tool/Fragment 三大扩展机制
  • 哲学层面:Unix 哲学在 AI 时代的实践——简单、可组合、可编程

如果你是经常在终端工作的开发者,如果你厌倦了在不同 AI 网页之间切换,如果你想用脚本自动化 AI 工作流——LLM 值得一试。

pipinstallllm&&llm keyssetopenai&&llm"Why should developers use CLI tools for AI?"

参考链接:

  • 项目仓库:https://github.com/simonw/llm
  • 官方文档:https://llm.datasette.io/
  • 插件目录:https://llm.datasette.io/en/stable/plugins/directory.html
  • 作者博客:https://simonwillison.net/tags/llm/
  • 演示视频:https://www.youtube.com/watch?v=QUXQNi6jQ30
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/8 2:08:46

飞书 CLI 实战:让 AI Agent 帮你操作飞书

事情是这样的 前阵子在排查公司内网一个 Java 服务的问题,日志刷了一屏幕。问题定位到了,方案也理清了,于是习惯性对 AI 说:"帮我把分析结果整理一下,写成一个飞书文档。" AI 很快就生成了一段漂亮的文字分…

作者头像 李华
网站建设 2026/7/8 2:08:33

【claude code实践】让 Claude Code 写单元测试:覆盖核心逻辑的第一步

让 Claude Code 写单元测试:覆盖核心逻辑的第一步 引言:为什么现在需要理解它 几乎所有开发者都同意“单元测试是代码质量的底线”,但真到了项目紧、需求多变的时候,测试往往是第一个被推迟的环节。即便想补,面对一套陌…

作者头像 李华
网站建设 2026/7/8 2:07:17

ComfyUI幻术工作流:图像信息隐藏技术与AI视觉编码实践

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 那天下午,我正对着电脑屏幕上一张看似普通的风景图发呆。一位朋友发来这张图,神秘兮兮地说:“仔细…

作者头像 李华
网站建设 2026/7/8 2:07:02

Jensen Hughes收购HiLT,进一步扩大东南亚业务版图

消防与生命安全、安保及基于风险的工程与咨询领域的全球领导者Jensen Hughes今日宣布,已收购新加坡领先的消防工程公司HiLT Pte. Ltd. (HiLT)。此次收购进一步巩固了该公司在亚太地区的业务布局,并提升了其为交通、储能系统以及大型商业和工业开发等高要…

作者头像 李华
网站建设 2026/7/8 2:06:26

为知笔记导出到本地 Markdown,老笔记也能重新用起来

摘要: 很多人的早期资料都存在为知笔记里,但迁移、备份和 AI 阅读都不太方便。万能导支持将为知笔记导出为本地 Markdown,尽量保留正文、图片、附件和目录层级,让旧笔记重新变成可迁移、可搜索、可交给 AI 分析的知识资产。 万能导…

作者头像 李华
网站建设 2026/7/8 2:05:53

用 Codex 给论文排版,体验如何?一次真实测评

最近尝试用 Codex 来做学术论文的 Word 排版,跑完一轮下来——有惊喜,也有不少坑。下面完整记录一下过程和结论。 一、先说优点 1. 基础排版基本成型 如果格式要求不算太复杂,Codex 生成的文档乍一看还是像模像样的: 2. 动态页码…

作者头像 李华