ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OSS+WebUI+Llms.py v4:构建企业级LLM应用平台的核心实践

OSS+WebUI+Llms.py v4:构建企业级LLM应用平台的核心实践 之前整理过不少 LLM 应用项目的落地经验最直观的感受是当应用从“单个对话 Demo”走向“多项目、多智能体、多文档协作”的阶段时最先暴露问题的往往不是模型本身而是基础设施层的三件事——文件往哪里放、智能体配置如何管理、结果怎么快速分享。这篇文章会围绕“OSS WebUI Llms.py v4”这套组合展开完整拆解一个可落地的 LLM 应用平台方案。它把对象存储、调度编排、前端界面、PDF 解析和分享链路串在一起重点覆盖 v4 版本带来的四个核心能力Projects项目管理、Agent Profiles智能体配置、PDF StudioPDF 工作室、1-Click Sharing一键分享。如果你正在做 LLM 应用平台或者想把本地脚本进化成一个带 WebUI 的完整产品这篇文章值得收藏。下面是完整的实现思路、代码示例和排错经验。1. 背景与核心概念1.1 这个项目到底解决什么问题大多数人刚开始接触 LLM 开发时流程很简单拿一个模型 API写一段 Prompt然后在 Notebook 里跑出结果。但一旦进入真实业务场景问题就复杂多了会有多个项目并行每个项目有不同的数据、不同的智能体配置。用户需要上传 PDF、图片、音频等文件这些文件不能只存在本地临时目录。团队其他成员需要一个 Web 界面而不是去命令行里敲 Python。结果要一键分享给同事或客户不能让对方也装一套环境。换句话说LLM 应用要走向工程化就必须有稳定的文件存储、项目隔离、智能体管理和分享机制。这正是“OSS WebUI Llms.py v4”这套体系要解决的核心问题。1.2 OSS、WebUI、Llms.py 分别是什么OSSObject Storage Service是对象存储服务以阿里云 OSS 为例它适合存放图片、PDF、视频、模型文件等非结构化数据。相比服务器本地磁盘OSS 的优势是容量大、成本低、支持 CDN 加速、权限控制灵活而且不占用应用服务器的磁盘空间。WebUI是 Web 图形界面。在 LLM 应用里WebUI 让用户可以通过浏览器完成对话、文件上传、配置管理等操作。常见的方案有 Gradio、Streamlit 或自研前端本文的架构可以兼容这些方案核心是把后端能力以 API 形式暴露出来。Llms.py是一个基于 Python 的 LLM 数据流编排框架。它把提示词、模型调用、输出解析组织成可复用的管道。与传统“一层一层调用 API”的方式相比Llms.py 更强调用 DataFrame 等结构化管理数据流适合批量处理、多步骤任务和结果沉淀。这里有一个关键点v4 版本所强调的 Projects、Agent Profiles、PDF Studio、1-Click Sharing本质上不是四个孤立的“按钮”而是一整套数据模型 API 前端交互的组合能力。后面会逐个拆解。1.3 整体架构思路为了便于理解可以把整个系统拆成四层层级组件职责存储层OSS 本地临时目录保存 PDF、图片、解析结果、分享快照业务层FastAPI Llms.py项目管理、智能体配置、解析任务调度能力层PDF Studio Agent ProfilesPDF 文本提取、向量化、LLM 调用参数管理交互层WebUI 分享链接用户操作入口、一键分享访问入口以下是一个简化的请求流程用户在 WebUI 上传 PDF 文件。后端把 PDF 转存到 OSS并以project_id作为目录前缀。PDF Studio 从 OSS 下载文件解析文本把结果写回 OSS。Agent 配置从 Agent Profiles 中读取 System Prompt、模型名、温度参数。用户生成分享链接后端返回短链接或带签名 URL。其他人点击分享链接通过浏览器直接查看结果。这套流程里OSS 是唯一的文件真相源Source of TruthWebUI 是交互入口Llms.py 负责模型调用和数据流编排。2. 环境准备与版本说明2.1 运行环境清单在开始之前先确认环境是否满足要求。不同版本的 Llms.py、OSS SDK 和 WebUI 方案会有一些差异本文以常见的稳定版本环境为例重点演示配置思路请根据实际项目情况调整版本。组件说明操作系统Ubuntu 22.04 / macOS / WindowsWSL2 推荐Python3.10 或 3.11 以上阿里云 OSS已开通 OSS 服务并创建 BucketDocker可选用于 WebUI 容器化部署包管理工具pip 或 uv需要安装的 Python 包包括pip install llms pip install oss2 pip install fastapi pip install uvicorn pip install pdfplumber pip install pandas pip install python-multipart版本需要根据你的项目实际情况调整不要盲从最新版本。特别是 Llms.py 的 API 在不同版本可能有微小变化下文示例以 v4 架构下的通用写法为主。2.2 OSS 账号与 Bucket 准备以阿里云 OSS 为例需要提前准备AccessKey ID 和 AccessKey Secret。一个 Bucket例如my-llm-app-bucket。Bucket 所属地域的 Endpoint例如oss-cn-hangzhou.aliyuncs.com。创建 Bucket 时建议先选择“私有读写”。原因是后续可以通过后端生成签名 URL 来控制访问权限而不是把文件直接暴露到公网。如果某些资源确实需要公开访问比如产品页的头像、分享封面再单独对指定目录设置公共读。为了安全不要把 AccessKey 硬编码在代码里。推荐用环境变量或本地.env文件并在.gitignore中排除。2.3 项目目录结构本文的示例项目整体结构如下oss-webui-llms/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 入口 │ ├── oss_client.py # OSS 客户端封装 │ ├── pdf_studio.py # PDF 解析与处理 │ ├── agent_profiles.py # 智能体配置管理 │ ├── share.py # 一键分享 │ └── models.py # Pydantic 数据模型 ├── data/ # 本地临时目录 ├── requirements.txt ├── .env.example ├── Dockerfile └── docker-compose.yml模块职责尽量单一这样后续维护和扩展都方便。下面逐模块实现。3. 核心功能设计与原理拆解3.1 Projects多项目隔离机制Projects 并非简单的“文件夹分类”而是一个数据隔离与权限管理单元。在 OSS 中建议使用目录前缀实现项目隔离projects/{project_id}/pdfs/xxx.pdf projects/{project_id}/parsed/xxx.txt projects/{project_id}/output/result.json这样做的好处不同项目的文件物理上分开便于设置 Bucket Policy 或 RAM 子账号权限。后续做生命周期管理时可以针对某个项目目录单独设置过期规则。清理项目时只需删除对应前缀下的所有 Object。在数据库中Projects 表的核心字段可以设计为字段类型说明idstring项目唯一 IDnamestring项目名称descriptiontext项目描述ownerstring项目创建者created_atdatetime创建时间agent_profile_idstring绑定的智能体配置 ID项目的创建、更新、删除都应该有对应的 API前端 WebUI 才能实现项目切换和管理。3.2 Agent Profiles智能体配置化很多团队在开发 LLM 应用时把 Prompt 直接写在代码里。这种方式在项目少的时候问题不大但一旦有多个机器人、多套 Prompt、多个模型参数组合代码就会变得难以维护。Agent Profiles 就是把“智能体”的配置抽出来形成一份可复用、可修改的配置。核心字段如下{ id: agent_001, name: 文档分析助手, system_prompt: 你是一个专业的文档分析助手擅长提取关键信息。, model: gpt-4o, temperature: 0.3, max_tokens: 2048, tools: [pdf_extract, oss_read] }这样前端 WebUI 可以通过表单修改智能体配置后端读取配置后动态构建 LLM 调用。如果某个项目要换成不同的模型只需要修改 profile而不需要改代码、重新部署。在 Llms.py 中我们可以把 Agent Profile 作为调用参数传入。这样既能保留 Llms.py 对数据流的编排能力又能把“人设”和“参数”拆出来集中管理。3.3 PDF Studio文档解析管线PDF Studio 不止是做 PDF 文本提取它是一条完整的解析管线文件上传用户通过 WebUI 上传 PDF后端保存到 OSS。文件下载Worker 从 OSS 下载 PDF 到本地临时目录。文本提取使用 pdfplumber 等工具提取文本保留页码信息。分段处理长文本按长度切块方便后续向量化和检索。结果回写解析结果保存为 JSON 或 TXT再上传回 OSS。后续扩展可以将文本块送入 Embedding 模型写入向量数据库。这种管线设计的好处是阶段之间解耦。任何一个环节失败都能定位到具体步骤并重试。3.4 1-Click Sharing分享链路设计一键分享的核心是“生成一个可访问的短链接”。但链接的安全性需要额外考虑。推荐方案生成一个随机 Token。把 Token 与项目 ID、过期时间绑定。后端保存 Token 到数据库或内存缓存。访问分享链接时先校验 Token 是否有效、是否过期再返回内容。如果分享的内容是 OSS 里的文件更安全的做法是后端生成 OSS 签名 URLSTS 或签名 URL浏览器跳转到签名地址。这样不会把 Bucket 的公网权限打开又能限制访问时效。4. 完整实战案例下面从零开始搭建一个可运行的“OSS WebUI Llms.py v4”示例。示例以 FastAPI 作为后端以最简单的 HTML 页面作为 WebUI 入口重点是理清全链路。4.1 创建项目结构与依赖先创建项目目录并进入mkdir oss-webui-llms cd oss-webui-llms创建requirements.txtllms oss2 fastapi uvicorn[standard] pdfplumber pandas python-multipart python-dotenv安装依赖pip install -r requirements.txt4.2 配置 OSS 客户端新建.env文件生产环境不要提交到仓库OSS_ACCESS_KEY_ID你的AccessKeyId OSS_ACCESS_KEY_SECRET你的AccessKeySecret OSS_ENDPOINToss-cn-hangzhou.aliyuncs.com OSS_BUCKET_NAMEmy-llm-app-bucket创建app/oss_client.pyimport os import oss2 from dotenv import load_dotenv load_dotenv() ACCESS_KEY_ID os.getenv(OSS_ACCESS_KEY_ID) ACCESS_KEY_SECRET os.getenv(OSS_ACCESS_KEY_SECRET) ENDPOINT os.getenv(OSS_ENDPOINT) BUCKET_NAME os.getenv(OSS_BUCKET_NAME) auth oss2.Auth(ACCESS_KEY_ID, ACCESS_KEY_SECRET) bucket oss2.Bucket(auth, ENDPOINT, BUCKET_NAME) def upload_file_to_oss(local_path: str, object_key: str) - str: 上传本地文件到 OSS返回 object_key。 这里不直接返回公网 URL由后端统一生成签名 URL。 bucket.put_object_from_file(object_key, local_path) return object_key def download_file_from_oss(object_key: str, local_path: str): bucket.get_object_to_file(object_key, local_path) def generate_signed_url(object_key: str, expire_seconds: int 3600) - str: 生成 OSS 签名 URL默认有效期 1 小时。 return bucket.sign_url(GET, object_key, expire_seconds)核心思路所有写操作走upload_file_to_oss。需要读文件时先生成签名 URL而不是直接把 Bucket 设为公共读。object_key是 OSS 中的完整路径例如projects/project_001/pdf/xx.pdf。4.3 创建 Pydantic 数据模型创建app/models.pyfrom pydantic import BaseModel from typing import Optional class Project(BaseModel): id: str name: str description: str owner: str admin agent_profile_id: Optional[str] None class AgentProfile(BaseModel): id: str name: str system_prompt: str model: str gpt-4o temperature: float 0.7 max_tokens: int 2048 class ShareLink(BaseModel): project_id: str expire_seconds: int 3600数据模型是前后端交互的契约。后续如果前端要增加字段先改这里再同步前端接口。4.4 编写智能体配置管理创建app/agent_profiles.pyimport json import os DATA_DIR os.getenv(DATA_DIR, ./data) PROFILES_FILE os.path.join(DATA_DIR, agent_profiles.json) def ensure_data_dir(): os.makedirs(DATA_DIR, exist_okTrue) def load_profiles() - dict: ensure_data_dir() if not os.path.exists(PROFILES_FILE): return {} with open(PROFILES_FILE, r, encodingutf-8) as f: return json.load(f) def save_profiles(profiles: dict): ensure_data_dir() with open(PROFILES_FILE, w, encodingutf-8) as f: json.dump(profiles, f, ensure_asciiFalse, indent2) def create_or_update_profile(profile: dict) - dict: profiles load_profiles() profiles[profile[id]] profile save_profiles(profiles) return profile def get_profile(profile_id: str) - dict: profiles load_profiles() return profiles.get(profile_id, {})为什么用 JSON 文件而不是数据库在中小型项目中JSON 文件已经够用且零运维成本。当项目规模变大后只需把load_profiles和save_profiles替换成数据库读写即可接口层不需要改动。4.5 编写 PDF Studio 解析模块创建app/pdf_studio.pyimport pdfplumber import os import json def extract_pdf_text(pdf_path: str) - str: 提取 PDF 全文保留页码分隔。 text_chunks [] with pdfplumber.open(pdf_path) as pdf: for page_index, page in enumerate(pdf.pages, start1): page_text page.extract_text() or text_chunks.append(f--- Page {page_index} ---\n{page_text}) return \n.join(text_chunks) def chunk_text(text: str, chunk_size: int 1500, overlap: int 200) - list: 将长文本分块避免后续向量化和 LLM 调用时超出上下文限制。 chunks [] start 0 text_len len(text) while start text_len: end start chunk_size chunks.append(text[start:end]) start end - overlap return chunks补充说明pdfplumber对“文本型 PDF”识别效果好对扫描版 PDF 需要接入 OCR 组件例如 PaddleOCR。chunk_text中overlap参数是为了避免文档被切在语义中间导致上下文断裂。4.6 编写一键分享模块创建app/share.pyimport time import uuid share_links {} def create_share_link(project_id: str, expire_seconds: int 3600) - str: 生成分享 token并绑定项目 ID 和过期时间。 token uuid.uuid4().hex share_links[token] { project_id: project_id, expire_at: time.time() expire_seconds } return token def resolve_share_token(token: str) - str | None: 校验分享 token 是否有效返回 project_id。 link_info share_links.get(token) if not link_info: return None if link_info[expire_at] time.time(): share_links.pop(token, None) return None return link_info[project_id]在真实项目中share_links应替换为 Redis 或数据库并增加最大访问次数限制。内存字典只适合演示。4.7 编写 FastAPI 入口创建app/main.pyimport os import uuid import tempfile from fastapi import FastAPI, UploadFile, File, HTTPException from fastapi.responses import HTMLResponse from models import Project, AgentProfile, ShareLink from agent_profiles import create_or_update_profile, get_profile from oss_client import upload_file_to_oss, generate_signed_url import pdf_studio import share as share_module app FastAPI(titleOSS WebUI Llms.py v4) # 简单内存存储生产环境建议使用数据库 projects_db {} app.get(/, response_classHTMLResponse) def index(): return html headtitleOSS WebUI Llms.py v4/title/head body h1OSS WebUI Llms.py v4/h1 p这是一个演示页面。你可以通过接口完成项目管理、PDF 解析、配置管理和分享链接生成。/p /body /html app.post(/projects) def create_project(project: Project): projects_db[project.id] project return {status: created, project: project} app.get(/projects) def list_projects(): return list(projects_db.values()) app.post(/agent-profiles) def create_agent_profile(profile: AgentProfile): saved create_or_update_profile(profile.dict()) return {status: saved, profile: saved} app.get(/agent-profiles/{profile_id}) def get_agent_profile(profile_id: str): profile get_profile(profile_id) if not profile: raise HTTPException(status_code404, detailAgent Profile not found) return profile app.post(/projects/{project_id}/pdf) async def upload_pdf(project_id: str, file: UploadFile File(...)): if project_id not in projects_db: raise HTTPException(status_code404, detailProject not found) # 本地临时文件 suffix os.path.splitext(file.filename)[-1] or .pdf with tempfile.NamedTemporaryFile(deleteFalse, suffixsuffix) as tmp: content await file.read() tmp.write(content) tmp_path tmp.name # 上传到 OSS object_key fprojects/{project_id}/pdfs/{uuid.uuid4().hex}_{file.filename} upload_file_to_oss(tmp_path, object_key) # 解析 PDF text pdf_studio.extract_pdf_text(tmp_path) chunks pdf_studio.chunk_text(text) # 解析结果写回 OSS result_key fprojects/{project_id}/parsed/{uuid.uuid4().hex}.json parsed_content { object_key: object_key, text_length: len(text), chunk_count: len(chunks), chunks: chunks } # 写入本地临时文件并上传 result_path tmp_path .json import json with open(result_path, w, encodingutf-8) as f: json.dump(parsed_content, f, ensure_asciiFalse, indent2) upload_file_to_oss(result_path, result_key) # 清理临时文件 os.unlink(tmp_path) os.unlink(result_path) return { status: success, pdf_object_key: object_key, result_object_key: result_key, text_length: len(text), chunk_count: len(chunks) } app.post(/share) def create_share(share_data: ShareLink): token share_module.create_share_link( share_data.project_id, expire_secondsshare_data.expire_seconds ) return {share_url: f/share/{token}} app.get(/share/{token}) def access_share(token: str): project_id share_module.resolve_share_token(token) if not project_id: raise HTTPException(status_code403, detail分享链接无效或已过期) project projects_db.get(project_id) if not project: raise HTTPException(status_code404, detailProject not found) return {project: project, message: 这是一个可公开访问的分享页面}这个示例把整个闭环跑通项目管理 - PDF 上传 - OSS 存储 - PDF 解析 - 结果回传 - 分享链接。4.8 运行与验证启动服务uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload打开浏览器访问http://localhost:8000可以看到最简单的 WebUI 页面。接下来用 curl 测试接口。创建项目curl -X POST http://localhost:8000/projects \ -H Content-Type: application/json \ -d {id:project_001,name:测试项目,description:第一个项目,owner:admin}预期返回{status:created,project:{id:project_001,name:测试项目,description:第一个项目,owner:admin,agent_profile_id:null}}创建 Agent Profilecurl -X POST http://localhost:8000/agent-profiles \ -H Content-Type: application/json \ -d {id:agent_001,name:文档助手,system_prompt:你是文档分析专家,model:gpt-4o,temperature:0.3,max_tokens:2048}上传 PDF 并解析curl -X POST http://localhost:8000/projects/project_001/pdf \ -F file./test.pdf预期返回包含text_length和chunk_count的 JSON。如果 test.pdf 是文字型 PDF解析结果会非常快如果是扫描版需要先接 OCR。生成分享链接curl -X POST http://localhost:8000/share \ -H Content-Type: application/json \ -d {project_id:project_001,expire_seconds:3600}返回{share_url:/share/7f6f1a1c5a234a0e8f7c0c2d1e3b4f5a}浏览器访问该链接可以拿到项目信息说明链路正常。4.9 检查 OSS 中的文件如果配置正确打开阿里云 OSS 控制台可以看到projects/ project_001/ pdfs/ 0a1b2c3d_test.pdf parsed/ 4e5f6a7b.json这验证了 OSS 目录隔离策略生效。5. 常见问题与排查思路5.1 curl 能访问 OSS 吗经常有人问“curl 能访问 OSS 吗”。答案是可以但要根据 Bucket 权限选择正确的方式。如果 Bucket 是公共读可以直接用 curl 拉取文件curl -O https://{bucket}.{endpoint}/{object_key}如果 Bucket 是私有读写必须使用签名 URL# 先在后端生成签名 URL再用 curl 访问 curl -O https://{bucket}.{endpoint}/{object_key}?OSSAccessKeyIdxxxExpiresxxxSignaturexxx后端已经封装了generate_signed_url不需要手动拼接签名参数。5.2 Docker 部署 open webui 下载镜像很慢如果在 Docker 部署 WebUI 时遇到镜像下载慢通常不是 OSS 的问题而是网络环境导致。可以配置 Docker 镜像加速源。修改 Docker 配置文件/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ] }然后重启 Dockersudo systemctl restart docker之后再拉取镜像速度会明显提升。5.3 OSS 跨域问题如果前端页面通过浏览器直接访问 OSS 文件控制台出现跨域错误需要配置 OSS 跨域规则进入 OSS Bucket 管理页面。找到“数据安全” - “跨域设置”。添加一条规则来源http://localhost:*或你的前端域名。允许 MethodsGET, POST, PUT。允许 Headers*。需要注意跨域规则只影响浏览器端请求不影响后端签名和访问。5.4 PDF 解析结果为空白如果pdfplumber解析 PDF 返回空字符串通常是因为 PDF 是扫描版或图片型 PDF没有可提取的文本层。解决方案接入 OCR 引擎如 PaddleOCR、Tesseract。使用专门的 PDF 解析服务。对扫描版 PDF 先做图像增强再 OCR。在实际项目中可以先用 PDF 元数据判断是否有文本层with pdfplumber.open(pdf_path) as pdf: first_page pdf.pages[0] text first_page.extract_text()如果 text 为空再决定是否调用 OCR。5.5 常见问题速查表问题现象常见原因解决思路curl 访问 OSS 返回 AccessDeniedBucket 为私有权限使用签名 URL 或 STS 临时凭证Docker 拉取镜像超时未配置镜像加速源配置 registry-mirrors 并重启 DockerPDF 解析文本为空PDF 为扫描版接入 OCR 组件OSS 上传大文件超时未使用分片上传使用 oss2.resumable_upload 断点续传分享链接无法访问Token 过期或服务重启持久化分享数据到 Redis 或数据库WebUI 跨域报错未配置 OSS 跨域规则在 OSS 控制台添加跨域规则6. 最佳实践与工程建议6.1 OSS 对象命名规范对象命名直接影响后续的权限管理和生命周期管理。建议统一采用以下格式{module}/{project_id}/{category}/{uuid}_{filename}例如projects/project_001/pdf/8f1c3a2e_report.pdf projects/project_001/parsed/9b2d4f6c_report.json不要使用中文名或带空格的 Object Key避免 URL 编码问题和跨平台兼容问题。6.2 权限与安全AccessKey 不要写在代码里使用环境变量或 KMS 托管。Bucket 默认设置为私有读写通过签名 URL 对外提供临时访问。分享链接加入过期时间必要时限制访问次数。对外 API 加认证鉴权避免任何人直接调用项目创建和文件上传接口。对用户上传的 PDF 做文件类型校验防止恶意文件伪装成 PDF。6.3 性能优化大文件上传使用 OSS 分片上传避免单请求超时。PDF 解析是 CPU 密集型任务建议放入任务队列如 Celery异步执行而不是在 API 请求中同步等待。文本分块时根据模型上下文长度设置合理的 chunk_size避免 token 浪费。高频读取的文件可以加 CDN 加速减少 OSS 直接带宽压力。6.4 日志与监控记录 OSS 上传、下载、签名 URL 生成的日志方便排查权限问题。对 PDF 解析失败的任务单独记录失败原因和文件路径。在 OSS 控制台开启访问日志定期分析异常请求。配置告警规则例如单日上传失败次数、OSS 流量突增等确保异常能被及时发现。6.5 数据生命周期管理OSS 支持生命周期规则可以设置临时文件 7 天后自动删除。归档文件 90 天后转为冷存储。项目删除后自动清理对应前缀下的全部 Object。这样可以有效控制存储成本避免高频更新文件不断堆积。7. 总结与学习路线从这篇文章的完整示例中你可以看到“OSS WebUI Llms.py v4”并不神秘。它本质上是把文件存储、数据流编排、前端交互和分享分发四件事串起来OSS 负责所有非结构化数据的持久化与权限控制。WebUI 负责用户操作入口。Llms.py v4 提供的 Projects、Agent Profiles、PDF Studio、1-Click Sharing 四个能力分别解决项目隔离、智能体配置、文档解析和分享链路的工程化问题。FastAPI 作为粘合层把上述能力以 API 形式暴露给前端。建议你按下面的路线继续学习先跑通本文的最小示例理解每个模块的职责。把内存存储替换为 PostgreSQL把分享 Token 替换为 Redis。接入真实的 LLM 调用在 PDF 解析完成后把文本块送入模型做摘要或问答。给 PDF Studio 增加 OCR 能力解决扫描版文档解析问题。为 WebUI 增加交互式对话窗口让 Agent Profile 可以动态切换模型和 Prompt。实际项目中最值得优先关注的三个风险是Bucket 权限配置错误导致数据泄露、PDF 解析任务阻塞 API 导致请求超时、分享 Token 持久化缺失导致服务重启后链接失效。把这三件事在开发阶段处理掉系统就能稳定运行很久。如果这篇文章对你有帮助可以收藏备用。动手搭一个最小版本跑通一次文件上传和 PDF 解析你会对这套架构的理解更加扎实。
返回列表