Halo 前端 API 客户端 @halo-dev/api-client 完全指南:从插件接入到外部项目集成
【免费下载链接】haloHalo 是一款强大易用的开源建站工具,从个人博客、知识库,到企业官网、在线商城,Halo 都能助您轻松实现,一站式满足您的多样化建站需求。项目地址: https://gitcode.com/GitHub_Trending/ha/halo
本指南以 Halo 开源仓库中的 api-client 官方文档 为主体,结合 api-client 包源码、生成配置 与 Console 侧的拦截器实现,系统讲解@halo-dev/api-client的四个预置客户端、四个工厂函数、默认 Axios 实例的底层行为,以及它在 Halo 插件开发与外部第三方项目中的完整接入方案。读完本文,你将掌握如何用一行pnpm install加几个 import 语句,在插件或独立应用中直接调用 Halo 的 CRUD、Console、UC 与公开 API。
一、什么是 @halo-dev/api-client
@halo-dev/api-client是 Halo 2.x 的官方 JavaScript API 客户端请求库,位于仓库 ui/packages/api-client 目录。它本身不是手写的封装,而是基于 OpenAPI 规范、使用 OpenAPI Generator 以typescript-axios模板自动生成的一层类型安全的 HTTP 客户端。
从 package.json 可以看到它的完整元信息:
- 包名:
@halo-dev/api-client,当前仓库版本为2.26.0; - 许可证:
GPL-3.0; - 模块格式:
"type": "module"(ESM),入口为./dist/index.js,类型声明为./dist/index.d.ts; - 运行时依赖仅
qs(用于查询参数序列化),axios被声明为peerDependencies(要求^1.16.0),意味着 axios 由使用方(Console、UC 或插件宿主)统一提供,避免多实例冲突; sideEffects: false,可安全地被摇树优化(tree-shaking)。
1.1 生成链路:从 OpenAPI 到 TypeScript
客户端代码不是手工维护的,其生成命令定义在 package.json 的gen脚本中:
rimraf --glob './src/**' && openapi-generator-cli generate \ -i ../../../api-docs/openapi/v3_0/aggregated.json \ -g typescript-axios \ -c ./.openapi_config.yaml \ -o ./src \ --type-mappings='set=Array' \ --global-property apiDocs=false,modelDocs=false其中:
-i ../../../api-docs/openapi/v3_0/aggregated.json:输入文件是仓库 api-docs/openapi/v3_0/aggregated.json,即 Halo 后端所有分组(apis_console、apis_extension、apis_public、apis_uc)聚合后的 OpenAPI 描述文件;-g typescript-axios:生成基于 Axios 的 TypeScript 客户端;--type-mappings='set=Array':将 OpenAPI 中的set类型映射为Array;--global-property apiDocs=false,modelDocs=false:跳过 API 与模型文档的生成,只产出代码。
生成器版本由 openapitools.json 锁定为7.17.0。生成结果落在src/目录:src/api下是按资源划分的 API 类(如 post-v1alpha1-api.ts、user-v1alpha1-api.ts),src/models下是请求/响应模型,src/index.ts统一导出api、configuration与models:
export * from "./api"; export * from "./configuration"; export * from "./models";1.2 构建产物
构建由 vite.config.ts 驱动(vp pack),关键配置:
- 入口为
./entry/index.ts; neverBundle: ["axios"]:axios 不打入产物,交给宿主提供;alwaysBundle: ["qs"]:qs序列化逻辑直接打进产物;- 同时产出
esm与压缩后的iife两种格式,IIFE 全局名为HaloApiClient,方便 CDN 场景直接以<script>引入。
二、九大导出:预置客户端与工厂函数
包的主入口(entry/index.ts)最终导出以下内容:
import { coreApiClient, consoleApiClient, ucApiClient, publicApiClient, createCoreApiClient, createConsoleApiClient, createUcApiClient, createPublicApiClient, axiosInstance, } from "@halo-dev/api-client";| 导出 | 说明 |
|---|---|
coreApiClient | 为 Halo 所有自定义模型(extension)自动生成的 CRUD 接口封装的 api client |
consoleApiClient | 为 Halo 针对 Console(管理端)提供的接口封装的 api client |
ucApiClient | 为 Halo 针对 UC(用户中心)提供的接口封装的 api client |
publicApiClient | 为 Halo 所有公开访问的接口封装的 api client |
createCoreApiClient | 创建自定义模型 CRUD 接口的 api client,需要传入 axios 实例 |
createConsoleApiClient | 创建 Console 接口的 api client,需要传入 axios 实例 |
createUcApiClient | 创建 UC 接口的 api client,需要传入 axios 实例 |
createPublicApiClient | 创建公开访问接口的 api client,需要传入 axios 实例 |
axiosInstance | 包内部默认创建的 axios 实例 |
其中coreApiClient、consoleApiClient、ucApiClient、publicApiClient四个预置实例,本质上是createXxxApiClient(defaultAxiosInstance)的结果(见 entry/api-client.ts),它们共享同一个默认 Axios 实例,开箱即用。
2.1 默认 Axios 实例的底层配置
默认实例在 entry/api-client.ts 中创建,理解它对排查请求问题至关重要:
const defaultAxiosInstance = axios.create({ baseURL: "", withCredentials: true, paramsSerializer: (params) => { return QueryString.stringify(params, { arrayFormat: "repeat" }); }, }); defaultAxiosInstance.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";baseURL: "":默认实例不写死后端地址。在 Console / UC 中,请求走同源路径,由部署环境决定实际地址;withCredentials: true:跨域请求携带 Cookie,这是登录态(Session)能够生效的关键;paramsSerializer使用qs且arrayFormat: "repeat":数组参数会序列化为?a=1&a=2&a=3而不是?a[]=1&a[]=2&a[]=3,与 Halo 后端对列表参数(如分类、标签 ID 集合)的解析方式对齐;- 统一附带
X-Requested-With: XMLHttpRequest请求头,用于区分 Ajax 请求。
注意:
entry/index.ts中还通过export * from "./patch/thumbnail"兼容性地导出了ThumbnailSpecSizeEnum。该枚举已被标记@deprecated(参见 entry/patch/thumbnail.ts),请改用生成产物中的GetThumbnailByUriSizeEnum。
三、四类客户端的职责边界与组织结构
四类客户端对应 Halo 后端四组 OpenAPI 定义(可对照 api-docs/openapi/v3_0 下的apis_console、apis_extension、apis_public、apis_uc四个文件),覆盖四种完全不同的访问场景。
3.1 coreApiClient:自定义模型的 CRUD
createCoreApiClient的实现见 entry/api-client.ts,它按领域分组组织:
content:category、comment、post、reply、singlePage、snapshot、tag;auth:authProvider、userConnection;storage:attachment、group、policy、policyTemplate;plugin:extensionDefinition、extensionPointDefinition、plugin、reverseProxy;metrics:counter;theme:theme;notification:notification、notificationTemplate、notifierDescriptor、reason、reasonType、subscription;migration:backup;security:personalAccessToken;- 顶层另有
annotationSetting、menu、menuItem、setting、configMap、secret、user、role、roleBinding。
从源码结构看,content、auth、storage、plugin、metrics、theme、notification、migration、security这些命名对应 Halo 的 API 分组前缀(content.halo.run、auth.halo.run、storage.halo.run、plugin.halo.run等),每个字段对应的类均以V1alpha1Api结尾(例如文章对应PostV1alpha1Api)。这类接口通常面向插件开发者与内部模块,用于对自定义模型做增删改查。
3.2 consoleApiClient:管理端专用接口
createConsoleApiClient见 entry/api-client.ts,它封装了 Console 管理后台独有的能力,例如:
system、migration、uiPlugin;storage.attachment、storage.policy(注意 Console 侧策略类是PolicyAlpha1ConsoleApi);content下的category、comment、reply、indices、post、singlePage、tag;notification.notifier、plugin.plugin、theme.theme、configMap.system等。
这些接口通常依赖管理员权限,返回带权限过滤的数据形态(如列表分页、发布状态管理、系统配置读写)。
3.3 ucApiClient:用户中心专用接口
createUcApiClient见 entry/api-client.ts,服务登录用户本人的内容管理,例如:
content.post(如listMyPosts)、content.snapshot;security.twoFactor、security.personalAccessToken、security.device;notification.notification;user.preference、user.currentUser;storage.attachment。
3.4 publicApiClient:免认证公开接口
createPublicApiClient见 entry/api-client.ts,无需登录即可访问,典型用于门户站点、主题渲染与 SEO 场景:
menu、stats(SystemV1alpha1PublicApi);content.category、content.tag、content.singlePage、content.post;metrics.metrics、notification、index;- 顶层
comment(原先content.comment已被标记@deprecated,统一改用顶层comment)。
四、在 Halo 插件中使用(推荐方式)
插件是@halo-dev/api-client最主要的使用场景。安装依赖:
pnpm install @halo-dev/api-client axios由于 Halo 的 Console 与 UC 项目已经引入该包并设置好了 Axios 拦截器(详见下文"拦截器链路"),插件内直接使用预置客户端即可,无需自行创建实例:
import { coreApiClient } from "@halo-dev/api-client"; coreApiClient.content.post.listPost().then((response) => { // handle response });listPost()对应PostV1alpha1Api的列表方法,response.data即分页列表数据(含items与hasNext等字段)。
4.1 产物体积:依赖自动外置
README 特别强调:在@halo-dev/ui-plugin-bundler-kit@2.17.0及以上版本中,打包器已把@halo-dev/api-client与axios列入排除名单,插件最终产物中的这两项依赖会自动复用 Halo 宿主本身提供的版本,插件作者无需关心产物大小,也不存在多份 axios 实例导致的拦截器失效问题。这与 vite.config.ts 中neverBundle: ["axios"]的设计一脉相承。
五、在外部项目中使用(自定义实例)
如果你在 Halo 之外的独立应用(如自建的管理后台、数据看板、移动端配套服务)中调用 Halo API,需要手动创建 axios 实例并指定后端地址:
pnpm install @halo-dev/api-client axiosimport axios from "axios"; const axiosInstance = axios.create({ baseURL: "http://localhost:8090", }); const coreApiClient = createCoreApiClient(axiosInstance); coreApiClient.content.post.listPost().then((response) => { // handle response });几个关键点:
createCoreApiClient(以及其他三个工厂函数)会从传入的axiosInstance.defaults.baseURL中读取地址(见 entry/api-client.ts),因此务必在创建实例时配置baseURL;- 外部项目通常需要自己处理认证:可通过
axiosInstance.interceptors.request.use(...)附加 Token(如 Personal Access Token),或配合withCredentials: true走 Halo 的 Session 认证; - 推荐结合拦截器统一处理 401、网络错误与后端
ProblemDetail错误体(Halo 后端错误响应遵循 RFC 7807 格式,包含title、detail、status等字段)。
5.1 分页工具 paginate
包内还提供了一个实用的分页聚合工具,导出自 entry/utils/paginate.ts(由 entry/index.ts 一并导出):
export async function paginate<TParams extends { page?: number }, TItem>( listFn: (params: TParams) => Promise<AxiosResponse<ListResponse<TItem>>>, params?: Omit<TParams, "page"> ): Promise<TItem[]> { const result: TItem[] = []; let page = 1; let hasNext = true; while (hasNext) { const { data } = await listFn({ ...params, page } as TParams); result.push(...data.items); page += 1; hasNext = data.hasNext; } return result; }它基于 Halo 列表接口统一的响应结构(items+hasNext)逐页拉取全部数据,适合导出、统计等需要全量数据的场景:
import { paginate } from "@halo-dev/api-client"; import { coreApiClient } from "@halo-dev/api-client"; const allPosts = await paginate((params) => coreApiClient.content.post.listPost(params) );六、拦截器链路:Console/UC 已内置的错误处理
README 提到"已经在 Console 和 UC 项目中引入并设置好了 Axios 拦截器",其真实实现位于 ui/src/setup/setupApiClient.ts。该文件通过setupApiClient()在应用启动时注册响应拦截器,处理策略包括:
- 请求取消:
error.code === "ERR_CANCELED"直接透传,不弹任何提示; - 网络错误:匹配
Network Error或没有error.response时,弹出国际化后的网络错误 Toast; - 静默请求:若请求配置了
mute标记(errorResponse.config.mute),直接 reject 而不提示,用于后台静默刷新等场景; - 401 未授权:弹出"登录已过期"对话框,确认后跳转
/login?redirect_uri=...并在登录后回跳原路径; - HTML 响应:当响应头
content-type为text/html(典型来自反向代理或 WAF 的拦截页)时,用 iframe 弹窗展示原始内容,避免误解析; - ProblemDetail 错误体:从
errorResponse.data读取title/detail并以 Toast 展示; - 兜底:以上都不命中时,展示
status: statusText或通用未知错误提示。
因此,插件在 Console/UC 环境内调用coreApiClient等预置客户端时,认证失效、网络异常、后端错误都会得到统一且友好的 UI 反馈,这是"直接使用即可"的底层保障。在外部项目中,建议参照该实现自行注册拦截器,以获得一致的错误体验。
七、常见问题与最佳实践
- 何时用预置客户端,何时用工厂函数?在 Console/UC 插件内一律使用
coreApiClient、consoleApiClient等预置实例;在外部独立项目中,用createXxxApiClient(你的axios实例)绑定自己的baseURL与认证逻辑。 - 为什么数组参数要用
arrayFormat: "repeat"?因为 Halo 后端对多值查询参数(如?category=1&category=2)按重复键解析,默认实例已内置该序列化行为;自定义实例时建议保持同样的paramsSerializer配置,避免传参被序列化成category[]=1导致后端取不到值。 - 升级后
ThumbnailSpecSizeEnum失效?它是为兼容旧版本保留的废弃导出(见 entry/patch/thumbnail.ts),请改用GetThumbnailByUriSizeEnum。 - 保持 axios 单例:由于包将 axios 声明为 peerDependency 且打包时排除,项目中不要重复安装不同版本的 axios,否则拦截器可能注册在"另一份"实例上而失效。
- 跟进 API 变更:客户端代码由 OpenAPI 聚合文件生成,后端接口变更后需重新执行
pnpm gen(生成命令见 package.json),并同步升级包版本。
八、深入阅读指引
- 官方使用文档:ui/packages/api-client/README.md
- 客户端工厂函数与默认实例实现:ui/packages/api-client/entry/api-client.ts
- 包入口导出(含兼容性导出):ui/packages/api-client/entry/index.ts
- 分页聚合工具:ui/packages/api-client/entry/utils/paginate.ts
- 生成配置与构建配置:package.json、openapitools.json、vite.config.ts
- OpenAPI 聚合描述文件:api-docs/openapi/v3_0/aggregated.json(以及同目录下
apis_console、apis_extension、apis_public、apis_uc分组文件) - Console 侧拦截器实现(错误处理参考):ui/src/setup/setupApiClient.ts
- 插件 UI 资源与打包(依赖外置的配套机制):openspec/specs/ui-plugin-bundler-provider/spec.md
从插件内的一行import,到外部项目的自定义实例,@halo-dev/api-client通过"生成代码 + 预置实例 + 工厂函数"的组合,把 Halo 全部后端能力以类型安全、开箱即用的方式暴露给前端生态。掌握它的导出体系与默认实例行为,是高效开发 Halo 插件与周边应用的第一步。
【免费下载链接】haloHalo 是一款强大易用的开源建站工具,从个人博客、知识库,到企业官网、在线商城,Halo 都能助您轻松实现,一站式满足您的多样化建站需求。项目地址: https://gitcode.com/GitHub_Trending/ha/halo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考