基于Next.js与Ollama构建本地大语言模型Web交互界面
1. 项目概述:一个开箱即用的本地大语言模型Web界面
最近在折腾本地部署大语言模型,发现了一个挺有意思的项目,叫 jakobhoeg/nextjs-ollama-llm-ui 。简单来说,这就是一个用 Next.js 框架搭建的、专门用来和本地运行的 Ollama 大模型进行交互的 Web 用户界面。如果你已经厌倦了在命令行里和模型对话,或者想给自己部署的本地模型一个更友好、更现代的前端,那这个项目绝对值得你花时间研究一下。
它的核心价值在于,把 Ollama 这个强大的本地模型运行工具,从一个“后台服务”变成了一个“可交互的应用”。Ollama 本身负责模型的拉取、加载和推理,但它默认只提供 API 接口。而这个 Next.js UI 项目,就是为这些 API 接口穿上了一件漂亮、易用的“外衣”。你可以通过浏览器,像使用 ChatGPT 官网一样,和你的本地模型进行对话、管理对话历史、切换不同的模型,整个过程完全在本地进行,数据不出你的机器,安全性和隐私性拉满。
这个项目非常适合几类人:一是对 AI 应用开发感兴趣的开发者,想学习如何将大模型 API 与现代化前端框架结合;二是注重隐私、希望完全掌控对话数据的个人用户;三是想在内部搭建 AI 助手原型或工具的小团队,需要一个快速上手的界面。接下来,我会带你从零开始,把这个项目跑起来,并深入聊聊其中的技术细节和我在部署过程中踩过的那些坑。
2. 核心架构与技术栈拆解
2.1 为什么是 Next.js + Ollama 这个组合?
要理解这个项目,得先拆解它的两大支柱:Next.js 和 Ollama。
Ollama 是一个让在本地运行大型语言模型变得极其简单的工具。它帮你处理了最头疼的部分:模型下载、环境配置、GPU/CPU 推理优化。你只需要一条命令,比如 ollama run llama3.2 ,就能把 Meta 最新的 Llama 3.2 模型拉下来并在本地跑起来。它本质上是一个后台服务,启动后会在本地(通常是 http://localhost:11434 )暴露出一套 RESTful API。你可以用 curl 命令或者任何 HTTP 客户端向它发送请求,它返回模型的回答。
但问题来了,整天对着命令行或者用 curl 发 JSON 数据来聊天,体验太差了。这就引出了 Next.js 。Next.js 是一个基于 React 的“全栈”框架。这里的“全栈”是关键。传统的 React 应用(Create React App)是纯前端的,需要另一个后端服务(比如用 Python Flask、Node.js Express)来代理请求给 Ollama。而 Next.js 允许你在同一个项目里,直接编写“后端”API 路由。这意味着,这个 UI 项目本身就可以作为 Ollama API 的“代理”和“包装器”。
前端页面(React组件)发送请求到 Next.js 自己的 API 路由(比如 /api/chat ),然后这个 API 路由内部再去调用本地的 Ollama 服务( localhost:11434 )。这样做有几个巨大优势:
- 隐藏后端细节 :前端完全不需要知道 Ollama 的地址和端口,所有配置都在 Next.js 服务端完成,更安全。
- 数据处理与转换 :可以在 Next.js API 层对发送给 Ollama 的请求和返回的结果进行预处理和后处理,比如格式化消息历史、添加系统提示词、处理流式响应等。
- 一体化部署 :整个应用(前端界面+后端代理)可以打包成一个整体,部署和运维更简单。
所以,这个组合可以理解为: Ollama 充当“模型引擎”,Next.js 充当“驾驶舱和控制系统” 。你通过 Next.js 这个漂亮的驾驶舱下达指令,它帮你把指令翻译成引擎能懂的语言,引擎工作后,再把结果通过驾驶舱的仪表盘展示给你。
2.2 项目结构深度解析
克隆项目后,看一下它的目录结构,就能对它的工作方式有个大致了解。一个典型的 nextjs-ollama-llm-ui 项目结构可能如下:
nextjs-ollama-llm-ui/
├── app/ # Next.js 13+ 的 App Router 核心目录
│ ├── api/ # API 路由目录
│ │ └── chat/ # 处理聊天对话的 API 端点
│ │ └── route.ts # 关键文件:与 Ollama 通信的后端逻辑
│ ├── globals.css # 全局样式
│ ├── layout.tsx # 根布局组件
│ └── page.tsx # 主页面组件(聊天界面)
├── components/ # 可复用的 React 组件
│ ├── Chat.tsx # 聊天对话主组件
│ ├── Message.tsx # 单条消息展示组件
│ └── ModelSelector.tsx # 模型选择下拉框组件
├── lib/ # 工具函数和配置
│ ├── ollama.ts # 封装的 Ollama 客户端函数
│ └── types.ts # TypeScript 类型定义
├── public/ # 静态资源
├── .env.local.example # 环境变量示例文件
├── next.config.js # Next.js 配置文件
├── package.json
└── README.md
核心文件解读:
-
app/api/chat/route.ts:这是整个应用的“中枢神经”。它定义了一个POST请求处理函数。当前端发送聊天消息时,请求会到达这里。这个函数会:- 从请求体中提取用户消息、对话历史、选中的模型等参数。
- 构造符合 Ollama API 格式的请求体(特别是
messages数组,需要包含role和content)。 - 使用
fetch或axios向http://localhost:11434/api/chat发起请求。这里 强烈建议 将 Ollama 的地址(如OLLAMA_API_BASE_URL)配置为环境变量,而不是硬编码,方便后续部署。 - 关键技巧:为了实现像 ChatGPT 那样的打字机效果,这个 API 路由通常会将 Ollama 的响应设置为 流式传输 。即,它自己接收到 Ollama 返回的流,然后再将这个流转发给前端浏览器。这样前端就可以一个字一个字地显示回复。
-
app/page.tsx和components/Chat.tsx:这是用户看到的界面。它们负责渲染聊天窗口、消息列表、输入框,并处理用户交互。当用户发送消息时,Chat.tsx组件会调用fetch(‘/api/chat’, …),将请求发送给上面提到的 Next.js API 路由。 -
lib/ollama.ts:这里通常会封装一些辅助函数,比如listModels()用于获取本地 Ollama 已下载的模型列表,generate()用于调用非聊天模式的生成 API。将 Ollama 的 API 调用封装起来,使得业务逻辑更清晰,也便于统一处理错误。
这种清晰的分层结构,使得项目易于理解和维护。前端组件只关心展示和交互,业务逻辑和与 Ollama 的通信都集中在 API 路由和工具库中。
3. 从零开始的完整部署与配置指南
3.1 前置条件与环境准备
在启动这个 UI 项目之前,你必须确保它的“引擎”——Ollama,已经就位。
第一步:安装并运行 Ollama 这是基础中的基础。请根据你的操作系统,前往 Ollama 官网下载安装包。安装过程非常简单,几乎是一路点击“下一步”。
安装完成后,打开终端(或命令提示符/PowerShell),运行以下命令来验证安装并拉取一个模型:
# 检查 Ollama 是否安装成功
ollama --version
# 拉取并运行一个模型,例如小巧高效的 Llama 3.2
ollama run llama3.2
第一次运行 ollama run 命令会自动下载模型。下载完成后,你会进入一个交互式命令行聊天界面。输入 /bye 退出。 请保持这个终端窗口打开,或者以后台服务方式运行 Ollama ,因为它是一个持续运行的服务。
注意 :模型下载需要良好的网络环境。如果遇到下载慢或失败,可以考虑配置镜像源。但请记住,Ollama 服务默认运行在
http://127.0.0.1:11434,这是我们后续配置 UI 时需要知道的地址。
第二步:准备 Node.js 开发环境 这个 Next.js 项目需要 Node.js 环境。建议安装 Node.js 18.17 或更高版本 ,同时搭配 npm 或 yarn 包管理器。你可以使用 nvm (Node Version Manager) 来管理多个 Node.js 版本,这对于前端开发非常方便。
# 检查 Node.js 和 npm 版本
node --version
npm --version
3.2 获取并初始化 Next.js Ollama UI 项目
现在,我们来部署“驾驶舱”。
# 1. 克隆项目代码到本地
git clone https://github.com/jakobhoeg/nextjs-ollama-llm-ui.git
cd nextjs-ollama-llm-ui
# 2. 安装项目依赖
npm install
# 或者如果你习惯用 yarn
yarn install
安装依赖时,可能会花费几分钟时间,这取决于你的网络速度。这个过程会下载 Next.js、React、Tailwind CSS(如果项目用了)以及其他必要的库。
第三步:关键配置——环境变量 项目根目录下通常有一个 .env.local.example 或 .env.example 文件。你需要复制一份,并重命名为 .env.local (Next.js 默认读取此文件作为本地环境变量)。
cp .env.local.example .env.local
然后,用文本编辑器打开 .env.local 文件。里面最关键的配置项就是 Ollama 服务的地址:
# .env.local 文件内容示例
OLLAMA_API_BASE_URL=http://localhost:11434
# 可选:如果 Ollama 运行在其他机器或端口,请修改此处
# OLLAMA_API_BASE_URL=http://192.168.1.100:11434
确保这个 URL 和你的 Ollama 服务地址完全一致。 如果 Ollama 就在本机默认运行,那么 http://localhost:11434 或 http://127.0.0.1:11434 都是正确的。
3.3 启动服务与首次对话
配置完成后,启动就非常简单了。
# 在项目根目录下,启动开发服务器
npm run dev
# 或
yarn dev
如果一切顺利,终端会输出类似下面的信息:
> nextjs-ollama-llm-ui@0.1.0 dev
> next dev
▲ Next.js 14.2.5
- Local: http://localhost:3000
- Environments: .env.local
现在,打开你的浏览器,访问 http://localhost:3000 。你应该能看到一个简洁、现代的聊天界面。
首次使用流程:
- 检查模型列表 :界面通常会有一个模型选择下拉框。如果它是从
/api/models这样的接口动态获取的,你需要确保你的 Next.js API 路由正确配置并连接到了 Ollama。有时首次加载可能为空,刷新一下或检查浏览器控制台有无报错。 - 开始对话 :在底部的输入框中键入消息,例如“你好,请介绍一下你自己”,然后按下回车或点击发送按钮。
- 观察响应 :你应该能看到消息出现在聊天区域,并且模型的回复会以流式(逐字打印)的方式显示出来,体验非常接近主流 AI 产品。
如果页面能打开但发送消息后报错(如“Connection refused”或“Failed to fetch”), 99%的问题都出在 Ollama 服务未运行或环境变量配置错误上 。请返回终端,确认:
- 有一个终端窗口正在运行
ollama run命令(或 Ollama 作为后台服务在运行)。 - 检查
.env.local文件中的OLLAMA_API_BASE_URL是否完全正确。 - 你可以在浏览器中直接访问
http://localhost:11434/api/tags来测试 Ollama API 是否正常(它应该返回你已下载的模型列表 JSON)。
4. 核心功能实现与定制化开发
4.1 流式响应与打字机效果实现剖析
这个 UI 最吸引人的特性之一就是流畅的打字机效果。这背后是 服务器发送事件 或 流式响应 技术的应用。我们来深入看一下 app/api/chat/route.ts 里可能的核心代码逻辑。
当 Next.js API 路由收到前端的聊天请求后,它不会等待 Ollama 生成完整回复再一次性返回,而是会“转发” Ollama 的流式响应。
// app/api/chat/route.ts 示例代码片段
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
try {
const { messages, model } = await request.json(); // 从前端获取消息历史和模型名
// 1. 构造请求体,发送给 Ollama
const ollamaResponse = await fetch(`${process.env.OLLAMA_API_BASE_URL}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: model || 'llama3.2', // 默认模型
messages: messages,
stream: true, // 关键参数:要求流式响应
}),
});
// 2. 检查 Ollama 响应是否正常
if (!ollamaResponse.ok) {
const error = await ollamaResponse.text();
throw new Error(`Ollama API error: ${error}`);
}
// 3. 创建一个新的 ReadableStream 来转发 Ollama 的流
const stream = new ReadableStream({
async start(controller) {
const reader = ollamaResponse.body?.getReader();
if (!reader) {
controller.close();
return;
}
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
// 将 Ollama 返回的数据块(通常是 JSON 字符串)直接推送给前端
controller.enqueue(value);
}
} catch (err) {
controller.error(err);
} finally {
reader.releaseLock();
}
},
});
// 4. 返回流式响应给前端
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream', // 或 'application/x-ndjson'
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
} catch (error) {
console.error('Chat API error:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
在前端, components/Chat.tsx 或相关组件中,会使用 fetch 并处理这种流式响应:
// 前端处理流式响应的简化示例
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: chatHistory, model: selectedModel }),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// 假设 Ollama 返回的是每行一个 JSON 对象
const lines = chunk.split('\n').filter(line => line.trim() !== '');
for (const line of lines) {
const parsed = JSON.parse(line);
// parsed.message.content 包含模型回复的片段
// 在这里更新 React 状态,将片段追加到当前回复中,即可实现打字机效果
setCurrentReply(prev => prev + parsed.message.content);
}
}
关键点 : stream: true 这个参数是告诉 Ollama 进行流式输出的关键。Ollama 会返回一系列 JSON 对象,每个对象包含回复的一小部分(如一个词或一个 token)。Next.js API 路由接收到这个流,并立即将其转发给浏览器,浏览器再逐步解析和渲染,从而实现了实时的打字机效果。
4.2 模型管理与系统提示词定制
一个实用的 AI 对话界面,不仅要能聊天,还要能方便地切换不同模型和定制对话行为。
动态模型列表 :好的 UI 会从 Ollama 服务动态获取已下载的模型列表,而不是写死在代码里。这通常通过调用 Ollama 的 GET /api/tags 接口实现。你可以在 Next.js 中创建一个新的 API 路由,例如 app/api/models/route.ts ,专门用来代理这个请求,然后前端在初始化时调用它来填充下拉选择框。
// app/api/models/route.ts
export async function GET() {
try {
const response = await fetch(`${process.env.OLLAMA_API_BASE_URL}/api/tags`);
const data = await response.json();
// 返回模型列表,通常格式是 { models: [ { name: "llama3.2", ... }, ... ] }
return Response.json(data);
} catch (error) {
return Response.json({ error: 'Failed to fetch models' }, { status: 500 });
}
}
系统提示词 :系统提示词是引导模型行为的重要工具。你可以在前端界面添加一个输入框或设置面板,让用户自定义系统提示词。当用户发送消息时,将系统提示词作为 messages 数组的第一条消息( role: ‘system’ )一并发送给后端 API。
// 发送给 Ollama API 的 messages 结构示例
{
"model": "llama3.2",
"messages": [
{
"role": "system",
"content": "你是一个乐于助人且简洁的助手。请用中文回答。"
},
{
"role": "user",
"content": "今天的天气怎么样?"
}
],
"stream": true
}
在 Next.js API 路由中,你可以从前端请求中接收这个 systemPrompt 参数,然后将其插入到 messages 数组的开头。这样,你就可以轻松地将助手设定为“翻译专家”、“代码助手”或“创意写手”等不同角色。
4.3 对话历史持久化与界面优化
基础的聊天功能实现后,我们可以考虑更进阶的特性来提升用户体验。
对话历史持久化 :默认情况下,刷新页面对话历史就消失了。为了实现持久化,你有几个选择:
- 浏览器本地存储 :使用
localStorage或IndexedDB。这是最简单的方法,数据保存在用户本地浏览器中。实现起来快,但无法跨设备同步。 - 服务端存储 :在 Next.js API 路由中连接一个数据库(如 SQLite、PostgreSQL、MongoDB)。为每个会话或用户创建记录,保存完整的
messages数组。这需要更多的后端开发工作,但功能更强大,可以支持用户登录、多设备同步等。- 实操心得 :对于个人使用的本地工具,
localStorage通常是首选。你可以在前端组件挂载时(useEffect)从localStorage读取历史,每次对话更新后同步写入。注意,messages数组可能很大,要小心localStorage的容量限制(通常约 5MB)。
- 实操心得 :对于个人使用的本地工具,
界面优化与功能添加 :
- Markdown 渲染 :大多数 LLM 的回复都支持 Markdown 格式。在前端使用如
react-markdown这样的库来渲染回复,可以让代码块、列表、加粗等格式更美观。 - 代码高亮 :结合
react-syntax-highlighter,为 Markdown 中的代码块添加语法高亮,这对程序员用户来说体验提升巨大。 - 停止生成按钮 :在流式响应过程中,提供一个“停止”按钮。其实现原理是使用
AbortController来中断前端的fetch请求。const controller = new AbortController(); fetch(‘/api/chat‘, { method: ‘POST‘, signal: controller.signal, // 传入信号 // ... 其他参数 }); // 点击停止按钮时调用 controller.abort(); - 对话重命名与管理 :如果实现了多对话历史,可以允许用户为每个对话线程命名,并提供删除、归档等管理功能。
5. 常见问题、故障排查与进阶部署
5.1 部署与访问问题排查清单
即使按照步骤操作,你也可能会遇到一些问题。下面是一个快速排查清单:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
访问 http://localhost:3000 页面无法打开 |
Next.js 开发服务器未启动或端口被占用 | 1. 确认在项目目录下运行了 npm run dev 。 2. 检查终端是否有错误输出。 3. 尝试更换端口: npm run dev -- -p 3001 。 |
页面打开,但模型列表为空或发送消息报错 “Failed to fetch” |
1. Ollama 服务未运行。 2. 环境变量 OLLAMA_API_BASE_URL 配置错误。 3. 网络策略或防火墙阻止了连接。 |
1. 新开一个终端,运行 ollama run llama3.2 并保持运行。 2. 检查 .env.local 文件,确认 URL 正确无误(注意是 http 不是 https )。 3. 直接在浏览器访问 http://localhost:11434/api/tags ,看是否能返回 JSON。如果不能,说明 Ollama 服务有问题。 |
| 发送消息后,长时间显示“正在思考”但无回复 | 1. 模型首次加载慢。 2. 硬件(CPU/内存)不足,推理时间极长。 3. API 路由或 Ollama 内部错误未正确返回。 |
1. 耐心等待,首次调用某模型需要加载到内存。 2. 打开浏览器开发者工具的“网络”选项卡,查看对 /api/chat 的请求,检查响应状态和返回数据。 3. 查看 Next.js 服务终端和 Ollama 服务终端的错误日志。 |
| 流式响应不流畅,一次性全部显示 | 前端处理流的代码可能有问题,或者 Ollama 未以流模式响应。 | 1. 确认 API 路由中调用 Ollama 时设置了 stream: true 。 2. 检查前端处理响应的代码是否正确使用了 ReadableStream 和 getReader() 。 |
| 跨域问题 (CORS) | 如果你将 Next.js 应用和 Ollama 部署在不同域名或端口,浏览器会阻止请求。 | 1. 本地开发通常同源,无此问题 。 2. 若部署后出现,需要在 Ollama 启动时配置 CORS(如设置 OLLAMA_ORIGINS 环境变量),或在 Next.js API 路由中添加 CORS 响应头(更推荐,因为 Next.js 是代理)。 |
一个非常常见的坑 : .env.local 文件修改后, Next.js 开发服务器不会自动重新加载环境变量 !你需要 停止并重新运行 npm run dev 才能使新的环境变量生效。这一点很容易被忽略。
5.2 生产环境部署考量
本地玩转之后,你可能想把它部署到服务器上,以便在局域网内或通过互联网访问。
方案一:同机部署(最简单) 将 Next.js 应用和 Ollama 都运行在同一台服务器上。部署 Next.js 应用时,使用 npm run build 构建生产版本,然后使用 npm run start 启动生产服务器。确保 Ollama 也作为系统服务在后台运行。此时,Next.js 应用中的 OLLAMA_API_BASE_URL 可以配置为 http://localhost:11434 (因为它们在同一个机器上)。
方案二:分离部署 将 Next.js 应用和 Ollama 部署在不同的服务器或容器中。这时,你需要:
- 修改 Next.js 项目的环境变量
OLLAMA_API_BASE_URL,指向 Ollama 服务器的 实际 IP 地址和端口 (如http://192.168.1.100:11434)。 - 重要 :在运行 Ollama 的服务器上,你需要修改 Ollama 的配置,允许来自 Next.js 服务器 IP 的连接。默认情况下,Ollama 只监听本地环回地址(
127.0.0.1)。可以通过设置环境变量OLLAMA_HOST来改变监听地址。
警告 :将# 在运行 Ollama 的服务器上 export OLLAMA_HOST=0.0.0.0:11434 # 监听所有网络接口 ollama serveOLLAMA_HOST设置为0.0.0.0会使 Ollama 服务暴露在网络上,请确保你的服务器防火墙(如ufw、iptables)配置了规则,只允许受信任的 IP(你的 Next.js 服务器 IP)访问 11434 端口,否则可能存在安全风险。
使用 Docker 容器化部署 : 这是更优雅和可复现的部署方式。项目可能会提供 Dockerfile 或 docker-compose.yml 。
Dockerfile用于构建包含 Next.js 应用的镜像。docker-compose.yml可以定义两个服务:ollama服务和nextjs-ui服务,并配置它们之间的网络连接。
# docker-compose.yml 示例
version: '3.8'
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama # 持久化模型数据
environment:
- OLLAMA_HOST=0.0.0.0 # 允许容器外访问
networks:
- ollama-network
nextjs-ui:
build: . # 使用当前目录的 Dockerfile 构建
container_name: nextjs-ollama-ui
ports:
- "3000:3000"
environment:
- OLLAMA_API_BASE_URL=http://ollama:11434 # 注意这里,使用服务名“ollama”
depends_on:
- ollama
networks:
- ollama-network
volumes:
ollama_data:
networks:
ollama-network:
driver: bridge
在这种配置下,Next.js 容器内可以通过服务名 http://ollama:11434 访问 Ollama 容器,无需暴露 Ollama 端口到宿主机外部,更安全。你只需要访问宿主机的 3000 端口即可使用 UI。
5.3 性能优化与模型选择建议
本地运行 LLM,性能是关键。这里有一些优化思路:
-
模型选择 :不是所有模型都适合你的硬件。如果你的机器内存有限(比如只有 8GB 或 16GB),务必选择参数量较小的模型。例如:
llama3.2:1b、llama3.2:3b:参数量小,对内存要求低,响应速度快,适合轻量级任务和入门。qwen2.5:7b、llama3.2:7b:7B 参数模型是目前在消费级硬件(16GB+ 内存)上性价比和性能比较平衡的选择。qwen2.5:14b、llama3.2:11b:需要更大的内存(建议 32GB+),能力更强。 使用ollama list查看已下载模型,使用ollama run <模型名>进行切换。
-
量化版本 :在拉取模型时,优先选择带量化后缀的版本,如
:7b-q4_K_M。量化能显著减少模型对显存和内存的占用,同时性能损失相对较小。q4_K_M是比较常用的一个平衡了精度和速度的量化等级。 -
Next.js 构建优化 :对于生产部署,确保使用
npm run build进行优化构建。可以考虑将 Next.js 配置为 Standalone Output ,以生成更精简的、不依赖node_modules的部署包。 -
利用 GPU 加速 :如果你的机器有 NVIDIA GPU,确保安装了正确的 CUDA 驱动和 cuDNN 库。Ollama 会自动检测并使用 GPU 进行推理,这会带来数倍甚至数十倍的速度提升。运行
ollama run时观察终端输出,如果看到类似“Using GPU 0”的日志,说明 GPU 加速已启用。
这个项目提供了一个绝佳的起点,让你能快速拥有一个私有的、可定制的 AI 对话界面。它的价值不仅在于即拿即用,更在于其清晰的开源结构,让你可以以此为蓝本,深入理解前后端如何协同与大型语言模型交互,并按照自己的需求进行任意功能的添加和修改,比如集成知识库检索、实现函数调用、或者添加语音输入输出等。从“会用”到“会改”,再到“会创”,这才是开源项目带给我们的最大乐趣。
更多推荐
所有评论(0)