构建与定制 one-api 的 air 前端主题:从 npm 构建到 Go 二进制内嵌
web/air 是 one-api 内置的第三套前端主题(React 应用)。本文基于 air 主题说明文档 展开,覆盖本地开发、生产构建、REACT_APP_SERVER 环境变量配置等文档中的全部实操内容,并结合 package.json、main.go 与 router/web.go 等源码,讲清 air 主题的构建产物如何被 Go 后端以 embed 方式打进单一可执行文件,帮助你在本地独立调试前端、理解主题机制并参与主题定制。
air 主题在 one-api 中的定位
one-api 的前端采用"多主题"组织方式:web 目录下的每个文件夹都是一套独立的前端主题,web/README.md 明确说明"每个文件夹代表一个主题,欢迎提交你的主题",并指出 default 与 berry、air 等主题由不同开发者维护。从 common/config/config.go 可以确认主题的注册机制:
var Theme = env.String("THEME", "default")
var ValidThemes = map[string]bool{
"default": true,
"berry": true,
"air": true,
}
THEME环境变量控制启动时选用哪套主题,默认为default;ValidThemes是合法主题白名单,air已注册其中;- 启动时 main.go 会打印
using theme %s日志,便于确认当前生效主题。
air 主题本身是一个 React 应用,README 将其定位为 "React Template",说明它脱胎于一套可复用的 React 模板工程。
本地开发:npm start
文档中的 Basic Usages 给出的第一条命令是在 web/air 目录下运行开发服务:
# Runs the app in the development mode
npm start
npm start 对应 package.json 中的 "start": "react-scripts start",即标准的 Create React App 开发服务器,支持热更新。
从 package.json 的依赖清单可以看出该主题的技术栈:
| 依赖 | 版本 | 用途 |
|---|---|---|
| react / react-dom | ^18.2.0 | UI 框架 |
| react-scripts | 5.0.1 | CRA 构建体系 |
| @douyinfe/semi-ui / semi-icons | ^2.46.1 | 主 UI 组件库(布局、表格等) |
| @visactor/react-vchart | ~1.8.8 | 统计图表(用量、收益统计页) |
| axios | ^0.27.2 | HTTP 客户端 |
| react-router-dom / history | ^6.3.0 / ^5.3.0 | 前端路由与浏览器 history 管理 |
| react-toastify | ^9.0.8 | 消息提示 |
| react-turnstile | ^1.0.5 | Cloudflare Turnstile 人机验证 |
应用入口 src/index.js 中通过 initVChartSemiTheme 初始化图表主题,并用 SiderBar、HeaderBar 组装整体布局;src/App.js 通过 react-router-dom 的 Routes 挂载 Channel、Token、User、Log、Setting、TopUp 等页面,并对 Home、About 等页面做了 lazy 懒加载拆分。
关于开发模式下的请求代理:package.json 末尾声明了 "proxy": "http://localhost:3000"。结合 CRA 的 proxy 字段语义可以推断,开发时前端将 API 请求转发到本地 3000 端口的 one-api 后端实例,从而在不修改后端的情况下完成前后端联调。
生产构建与 REACT_APP_SERVER
文档中的第二条命令用于生产构建:
# Builds the app for production to the `build` folder
npm run build
注意 package.json 中的实际构建脚本比文档示例多一步产物归位:
"build": "react-scripts build && mv -f build ../build/air"
react-scripts build 在 web/air/build 产出静态文件后,将其强制移动到仓库根的 web/build/air 目录。这是 one-api 多主题共存的约定:所有主题的构建产物统一汇聚到 web/build/<主题名>/ 下(web/README.md 也要求新主题的 build 命令指向 ../build/<主题名>)。
通过 REACT_APP_SERVER 指定后端地址
文档 强调的关键配置是构建前设置 REACT_APP_SERVER 环境变量:
# 例如
REACT_APP_SERVER=http://your.domain.com npm run build
该变量在 src/helpers/api.js 中被消费:
export const API = axios.create({
baseURL: process.env.REACT_APP_SERVER ? process.env.REACT_APP_SERVER : '',
});
- 构建时设置了
REACT_APP_SERVER:所有请求的baseURL指向该地址,适合前后端分离部署(前端托管在独立域名/CDN,API 在后端域名); - 未设置时
baseURL为空字符串,即默认同源请求,适合前端静态资源与 API 同域(one-api 单二进制内嵌前端的典型部署形态)。
由于 CRA 只在构建期把 process.env.REACT_APP_* 静态替换进产物,该变量必须在 npm run build 之前生效,运行时修改无效。此外 api.js 还通过 axios 响应拦截器统一调用 showError 处理接口错误弹窗。
代码风格约定:Prettier 与 Actions on Save
文档 要求在开始编辑前,确认编辑器的 Actions on Save 开启了 Optimize imports 与 Run Prettier。这一点与 package.json 中的 Prettier 配置相呼应:
"prettier": {
"singleQuote": true,
"jsxSingleQuote": true
}
即项目统一使用单引号(JSX 同样单引号),devDependencies 中锁定 prettier 2.8.8。遵循该约定可避免提交大量格式抖动,保持与现有代码风格一致;Actions on Save 中的 Optimize imports 则配合 App.js 这类存在大量按需导入的入口文件,防止无用 import 累积。
构建产物如何进入 Go 后端
air 主题的产物之所以能随 one-api 单二进制分发,源于 Go 的 embed 机制,整条链路如下:
- main.go 声明
//go:embed web/build/*,将web/build下所有主题产物编译进二进制; - 启动时执行 router.SetRouter(server, buildFS),把内嵌文件系统交给路由层;
- router/web.go 按当前主题读取首页并挂载静态资源:
func SetWebRouter(router *gin.Engine, buildFS embed.FS) {
indexPageData, _ := buildFS.ReadFile(fmt.Sprintf("web/build/%s/index.html", config.Theme))
// ...
router.Use(static.Serve("/", common.EmbedFolder(buildFS, fmt.Sprintf("web/build/%s", config.Theme))))
}
也就是说,设置 THEME=air 启动 one-api 后,路由只暴露 web/build/air/ 下的内容作为站点首页与静态资源。而 common/embed-file-system.go 中的 EmbedFolder 通过 fs.Sub 截取子目录并实现 Exists 判断,目的是让 gin-contrib/static 支持 SPA 的 fallback 路由(未命中的路径回落到 index.html,由前端路由接管)。
这也解释了构建脚本必须把产物移动到 web/build/air:只有落位于该目录,go:embed web/build/* 才会在编译 Go 程序时把 air 主题一并打包。
参考与延伸阅读
文档的 Reference 部分 列出了 air 主题(模板)所借鉴的两个开源参考工程:
- OIerDb——一个基于 React 的管理后台项目,air 的整体页面结构与数据管理交互可追溯至该模板;
- 一个 React Hooks + Redux 的注册/登录示例工程,对应本主题 src/context/User 与 src/context/Status 等 Context 状态管理思路。
如需了解主题整体规范或贡献新主题(包括修改 build 命令、注册 ValidThemes、同步 web/THEMES 文件),请参阅 web/README.md;air 主题的进一步开发说明可参考 web/berry/README.md 的通用约定。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00