- 后端
- 文件存储
【免费下载链接】alist
🗂️A file list/WebDAV program that supports multiple storages, powered by Gin and Solidjs. / 一个支持多存储的文件列表/WebDAV程序,使用 Gin 和 Solidjs。
导读:alist 是一个基于 Go 与前端框架构建的多存储文件列表/WebDAV 程序(仓库根目录命令描述见 cmd/root.go)。本文以仓库根目录的 CONTRIBUTING.md 为主线,完整梳理从开发环境搭建、克隆源码、本地前后端预览,到基于
drivers/template模板新增一个存储驱动、编写符合规范的提交信息并提交 Pull Request 的端到端流程。读完本文,你将掌握 alist 开发者视角的完整工作流,并理解驱动注册与加载的底层机制,能够独立为该项目贡献新存储后端。
一、环境准备:搭建 alist 开发机
1.1 前置依赖
alist 由 Go 编写后端、前端框架编写 Web 界面(CONTRIBUTING.md 原文表述为 React,仓库命令描述则写作 Go/Solid.js,见 cmd/root.go)。按照 CONTRIBUTING.md 的要求,开发机器需要安装以下工具:
| 依赖 | 用途 | 说明 |
|---|---|---|
| git | 版本控制 | 克隆源码、管理分支与提交 |
| Go 1.20+ | 后端编译运行 | 文档要求 1.20+;当前仓库 go.mod 中声明的版本为go 1.25.0,实际编译时建议使用较新的 Go 工具链 |
| gcc | 编译 CGO 依赖 | 部分第三方库(如本地缩略图、加密相关依赖)依赖 CGO |
| nodejs | 前端开发 | 配合 pnpm 运行前端开发服务器 |
1.2 克隆源码
按文档要求,将后端仓库alist与前端仓库alist-web克隆到任意位置:
$ git clone https://github.com/alist-org/alist.git $ git clone --recurse-submodules https://github.com/alist-org/alist-web.git其中前端仓库必须使用--recurse-submodules以同时拉取子模块。开发时应切换到main分支,与上游主线保持一致。
克隆得到的alist后端仓库核心目录结构如下(以当前仓库实际内容为准):
- main.go:程序入口,仅调用
cmd.Execute() - cmd/:基于 Cobra 的命令行实现(
root.go、server.go、storage.go、user.go等) - drivers/:全部存储驱动,每个子目录是一个独立驱动,另含
template模板与 all.go 注册聚合文件 - internal/:核心业务逻辑,其中 internal/driver 定义了驱动接口
- server/:HTTP/FTP/SFTP/WebDAV 等服务端路由
- pkg/:通用工具库
二、预览你的改动:前后端开发服务器
2.1 后端:go run main.go
在alist仓库根目录直接运行:
$ go run main.go该命令会编译并启动开发服务器。从源码看,main.go 只做一件事:调用cmd.Execute(),进而执行 cmd/root.go 中的 Cobra 命令分发。真正拉起服务的是 cmd/server.go 中的ServerCmd:它会依次完成配置初始化、加载存储(bootstrap.LoadStorages())、初始化任务管理器与 FRP 内网穿透,然后启动 HTTP/HTTPS/Unix Socket、S3、FTP、SFTP 与 MCP 等监听服务。
因此,go run main.go默认行为等同于执行alist server,监听地址等由配置文件决定(相关实现见 cmd/server.go)。
2.2 前端:pnpm dev
进入alist-web目录运行:
$ pnpm dev即可启动前端开发服务器。前后端联调时,前端开发服务器通常需要配置代理指向本机后端端口;开发期间修改 Go 代码后重启go run main.go,修改前端代码则由 Vite 热更新自动生效。
三、新增一个存储驱动(Driver)
这是 alist 贡献中最核心的扩展点。CONTRIBUTING.md 给出的方法极为简洁——复制drivers/template文件夹并重命名,然后按照其中的注释实现。下面结合源码把这条指引展开成可操作的完整步骤。
3.1 模板文件夹结构
drivers/template共包含 4 个文件(见 drivers/template):
| 文件 | 职责 |
|---|---|
| meta.go | 定义驱动注册信息config、用户可配置字段Addition,并在init()中调用op.RegisterDriver完成注册 |
| driver.go | 定义驱动主体结构Template并实现全部接口方法(大部分为 TODO 占位) |
| types.go | 类型定义占位文件 |
| util.go | 工具函数占位文件,注释提示“可在此实现 Driver 接口之外的其他逻辑” |
3.2 注册信息与配置字段(meta.go)
drivers/template/meta.go 揭示了每个驱动的两个核心组成部分:
Addition结构体:通过 struct tag 声明用户在前端页面填写的配置项。模板默认嵌入driver.RootPath或driver.RootID二者之一(通常按驱动是按路径还是按 ID 标识文件来选择),并示范了一个自定义字段:
type Addition struct { // Usually one of two driver.RootPath driver.RootID // define other Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"` }这里的 tag 约定(json、type、required、options、default、help)会被管理界面自动解析为表单控件。以本地驱动 drivers/local/meta.go 为真实范例,可以看到更丰富的字段,例如thumbnail(是否启用缩略图)、use_ffmpeg(是否用 ffmpeg 生成视频缩略图)、thumb_pixel(缩略图目标宽度像素)、video_thumb_pos(视频缩略图时间点,支持秒数或百分比)、mkdir_perm(新建目录权限)、recycle_bin_path(回收站路径)等,每个字段都带有help说明,非常值得模仿。
config变量:类型为driver.Config,用于描述驱动的能力与行为:
var config = driver.Config{ Name: "Template", LocalSort: false, OnlyLocal: false, OnlyProxy: false, NoCache: false, NoUpload: false, NeedMs: false, DefaultRoot: "root, / or other", CheckStatus: false, Alert: "", NoOverwriteUpload: false, }字段含义可从命名推断:OnlyLocal表示仅限本机文件系统(本地驱动 drivers/local/meta.go 将其置为true并开启LocalSort、NoCache);NoUpload表示不支持上传;DefaultRoot是默认根路径提示;Alert可在界面上展示注意事项。
注册:文件末尾的init()调用op.RegisterDriver:
func init() { op.RegisterDriver(func() driver.Driver { return &Template{} }) }所有驱动正是通过各包的init()副作用完成注册,而 drivers/all.go 以空导入方式统一聚合了全部驱动(注释写明“All do nothing, just for import”)。
3.3 驱动主体与接口方法(driver.go)
drivers/template/driver.go 定义的驱动结构体嵌入model.Storage与自定义Addition,从而自动获得存储基础属性与配置字段:
type Template struct { model.Storage Addition }必实现方法(标注required):
| 方法 | 签名 | 职责 |
|---|---|---|
Config | Config() driver.Config | 返回驱动能力配置 |
GetAddition | GetAddition() driver.Additional | 返回配置字段指针,供 JSON 反序列化使用(见 internal/driver/driver.go 中Meta接口注释) |
Init | Init(ctx) error | 初始化,如登录/刷新 Token;注释提示可调用op.MustSaveDriverStorage(d)保存存储 |
Drop | Drop(ctx) error | 销毁时清理资源 |
List | List(ctx, dir, args) ([]model.Obj, error) | 列出目录文件,必实现 |
Link | Link(ctx, file, args) (*model.Link, error) | 返回文件下载链接/本地路径/读取流,必实现 |
可选方法(标注optional):MakeDir(建目录)、Move(移动)、Rename(重命名)、Copy(复制)、Remove(删除)、Put(上传)、GetArchiveMeta/ListArchive/Extract/ArchiveDecompress(压缩包相关能力)。未实现的可选方法统一返回errs.NotImplement,框架会自动降级为内置工具处理或提示不支持。
3.4 接口层:驱动的契约由谁定义
模板中每个方法签名都来自 internal/driver/driver.go 定义的Driver接口体系:
type Driver interface { Meta Reader }Meta:Config、GetStorage/SetStorage、GetAddition、Init/Drop(internal/driver/driver.go)Reader:List与Link两个只读核心方法(internal/driver/driver.go),这就是“必实现”的来源- 写操作通过
Mkdir、Move、Rename、Copy、Remove、Put等可选接口以能力组合方式扩展(internal/driver/driver.go);Put接口的注释还给出了上传取消、进度上报、限速的最佳实践
模板末尾有一行编译期断言var _ driver.Driver = (*Template)(nil)(drivers/template/driver.go),确保实现始终满足接口契约。
3.5 参考真实实现
drivers/local是最适合对照学习的完整示例:其 driver.go 中List通过readDir读取真实目录并过滤隐藏文件;Link支持普通文件与缩略图两种请求(drivers/local/driver.go);Put用utils.CopyWithCtx实现可取消、带进度上报的写入(drivers/local/driver.go)。新增驱动时,若目标存储 API 与某个已有驱动相近,直接在 drivers/ 下寻找同名实现做参照会事半功倍。
四、创建符合规范的提交(Commit Message)
CONTRIBUTING.md 要求提交信息必须格式化、标准化,采用业界常见的 Conventional Commits 风格。
4.1 总体格式
<type>(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer>- header必填,其中
scope可选 - 任何一行不得超过 100 个字符,以便在 GitHub 与各类 git 工具中清晰阅读
4.2 Type:提交类型
必须为下表之一(原文定义见 CONTRIBUTING.md):
| Type | 含义 |
|---|---|
feat | 新功能 |
fix | 修复缺陷 |
docs | 仅文档变更 |
style | 不影响代码语义的格式调整(空白、缺失分号等) |
refactor | 既不修复缺陷也不增加功能的代码重构 |
perf | 性能改进 |
test | 补充缺失或修正已有测试 |
build | 影响构建或依赖变更 |
revert | 还原某次提交 |
ci | 持续集成相关文件修改 |
chore | 构建流程、辅助工具与库的变更(如文档生成) |
release | 发布新版本 |
4.3 Scope:作用域
scope指明变更所在位置,例如$location、$browser、$compile、$rootScope、ngHref、ngClick、ngView等。结合 alist 的实际目录结构,实践中可自然使用如drivers/local、server/webdav、cmd等作为作用域;当变更影响多个作用域时使用*。
4.4 Subject:主题行
主题行是对变更的简洁描述,必须遵守三条规则:
- 使用祈使句、一般现在时:写
change,不写changed或changes - 首字母不大写
- 末尾不加句号(
.)
4.5 Body:正文
与 Subject 相同,正文同样使用祈使句、一般现在时。正文应包含变更动机,并与变更前的行为形成对比,让审阅者理解“为什么要改、怎么改的”。
4.6 Footer:页脚
页脚承载两类信息:
- 破坏性变更:必须以
BREAKING CHANGE:开头,后跟一个空格或两个换行,其余部分作为详细说明 - 关闭 Issue 的引用:可在此引用本次提交所关闭的 GitHub Issue
4.7 Revert:还原提交
若提交用于还原之前的某次提交,必须以revert:开头并紧跟被还原提交的 header;正文中需写明This reverts commit <hash>.,其中<hash>为被还原提交的 SHA。
一个符合规范的提交示例(综合上述规则):
feat(drivers/local): support recycle bin path for remove move deleted files into configured recycle bin instead of permanently deleting them, so users can recover accidental removals. BREAKING CHANGE: `remove` behavior changes when recycle_bin_path is configured. This reverts nothing.五、提交 Pull Request
完成开发与本地验证后,按以下流程提交贡献(CONTRIBUTING.md):
- 将本地分支推送到你的
alistfork 仓库 - 在 GitHub 上向上游
alist仓库的main分支发起 Pull Request - 在 PR 描述中说明变更动机、实现方式与验证结果,配合符合上一节规范的提交信息,便于维护者高效审阅
建议在提交 PR 前先在本地完整跑通一次后端go run main.go与前端pnpm dev,确认新驱动能正常列出、预览与操作文件,再进入提交流程。
小结
本文围绕 CONTRIBUTING.md 展开,从环境搭建、源码克隆、前后端预览,到基于 drivers/template 新增存储驱动、编写规范提交信息、提交 PR,给出了 alist 贡献者的完整工作流。其中“新增驱动”是 alist 扩展能力的核心路径:理解 internal/driver/driver.go 的接口契约、参考 drivers/local 的真实实现,再配合 drivers/all.go 的注册机制,你就能把任意一个云存储/网盘后端接入 alist。更多细节可继续阅读仓库内各驱动的实现与测试文件。
- 后端
- 文件存储
【免费下载链接】alist
🗂️A file list/WebDAV program that supports multiple storages, powered by Gin and Solidjs. / 一个支持多存储的文件列表/WebDAV程序,使用 Gin 和 Solidjs。
相关推荐
OmniRoute 贡献指南:从本地开发环境搭建到新增 Provider 的完整工程工作流
OmniRoute 贡献指南:从本地开发环境搭建到新增 Provider 的完整工程工作流 本文以 docs/i18n/id/CONTRIBUTING.md h
后端API网关LLM 网关人工智能大模型MCP 服务桌面应用OmniRoute 贡献指南:从本地开发环境搭建到新增 AI Provider 的完整实战流程
OmniRoute 贡献指南:从本地开发环境搭建到新增 AI Provider 的完整实战流程 OmniRoute 是一个开源的统一 AI 网关(MIT Lic
后端API网关LLM 网关人工智能大模型MCP 服务桌面应用Element Plus 贡献指南:从环境搭建、本地开发到提交 PR 的完整流程
Element Plus 贡献指南:从环境搭建、本地开发到提交 PR 的完整流程 Element Plus 是一个使用 TypeScript 编写的 Vue 3
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考