用 golang 写了,一套面向个人音乐资产的本地优先音乐系统

7 小时 7 分钟前
 vincentchyu

SonicLens:一套面向个人音乐资产的本地优先音乐系统

很多开发者都做过“记录自己在听什么”的小工具,但真正把这件事做成一个可以长期运行、可持续扩展、还能支撑多端体验的完整工程,其实并不容易。

我做 SonicLens,并不是为了再造一个播放器,也不是为了做一个简单的 scrobble 脚本,而是想回答一个更具体的问题:

如果一个人多年分散在 Apple Music 、Roon 、Audirvana 、Last.fm 里的听歌行为,最终都要沉淀为属于自己的音乐资产,那么这套系统应该长什么样?

项目: https://github.com/vincentchyu/sonic-lens.git

这就是 SonicLens 的起点。

它的目标很明确:

如果用一句话概括这个项目,我会这样描述:

SonicLens 是一套以“个人音乐资产”为中心构建的本地优先音乐基础设施,它把听歌记录、资料整理、AI 洞察和多端消费体验连接成一个完整闭环。

一、这个项目真正想解决的,不是“听了什么”,而是“如何拥有自己的音乐历史”

大多数音乐平台都能告诉你“最近听了什么”,但它们很少真正为用户提供一套稳定、可迁移、可扩展的数据资产模型。

一旦平台策略变化、账号迁移、播放器更换,过去的听歌行为往往就会被切碎,最终只剩一堆零散记录。对我来说,这件事最大的问题不是统计缺失,而是:

你的听歌历史没有真正属于你。

SonicLens 的设计从一开始就不是围绕某个单独播放器展开,而是围绕“个人音乐资产沉淀”展开。也正因为如此,它在架构上天然不是一个脚本型项目,而更像一个围绕领域模型搭建的系统:

这意味着它的工程重点不在“把接口调通”,而在于让多个异步链路、多个数据来源和多个客户端之间保持一致性。

二、从工程视角看,SonicLens 其实是四层系统

从当前仓库实现看,SonicLens 可以拆成四个核心层次。

1. 持续运行的 Go 后端

后端入口在 main.go。应用启动后会初始化配置、日志、OpenTelemetry 、Redis 、数据库、MusicBrainz 、对象存储,然后启动:

这类启动方式的重点,是把系统当作一个长期运行的服务,而不是一组临时执行的命令。

2. 明确分层的业务结构

这个项目后端不是把所有逻辑塞进 handler ,而是做了比较清晰的职责分层:

这种结构的价值不是“看起来规范”,而是当项目开始变复杂之后,数据访问边界、事务边界和业务边界依然可控。

GEMINI.md 里对这一点有非常明确的约束:数据库 CRUD 必须收口在 internal/model/,Logic 层只负责编排,不允许把原始 SQL 和 GORM 细节散落到业务代码里。

这是一个非常重要的工程判断,因为它直接决定了系统后期是否还能继续演化。

3. 产品化的原生三端客户端

很多个人项目做到后端和网页就结束了,但 SonicLens 还继续往前走了一层:我为它单独做了一套原生 Bridge 客户端。

soniclens-bridge 目前包含四个 target:

也就是说,这不是一个“顺带做了个移动端壳”的项目,而是一套有明确模块边界的多端产品:

project.yml 可以看到,整个 Xcode 工程本身也是生成式管理的,target 、scheme 和 extension 嵌入关系统一由配置驱动,而不是手工在 .xcodeproj 里维护。这一点在多人协作和长期迭代时非常关键。

4. 以 AI 为能力层,而不是以 AI 为项目本体

SonicLens 里我最看重的一点,是 AI 在这里是增强层,不是伪需求的中心

项目里的“音眸”能力并不是简单给歌曲丢一个 prompt ,而是围绕真实业务对象构建的:

这意味着 AI 不是一个“调用成功就结束”的黑盒,而是被纳入了工程系统本身。

三、这个项目最有技术含量的部分,不是接口数量,而是几个闭环

如果只看 API 数量,SonicLens 当前已经有几十个接口;但真正体现专业性的,不是接口多,而是几个关键闭环是否成立。

1. 播放监听闭环:从播放器状态到统一播放事实

SonicLens 当前支持接入 Apple Music 、Audirvana 、Roon 等来源。播放器监听不是单纯轮询标题字符串,而是围绕统一的播放事实在工作:

这部分实现里还有几个我自己非常在意的细节:

这些处理看起来不“炫技”,但恰恰决定了系统在长期运行时是不是稳定。

2. 资料库同步闭环:不是简单拉接口,而是本地索引系统

SonicLens 的客户端资料库不是“每次打开页面重新请求远端分页”这种常见方案,而是采用了一套更像本地应用的设计:

这套设计有两个明显好处。

第一,列表浏览和搜索体验不会严重依赖网络往返,客户端更接近原生 app 的使用感受。

第二,服务端和客户端之间的边界非常清楚:服务端提供变更事实,客户端维护查询性能。

这也是为什么 GEMINI.md 里会专门把“本地 SQLite 轻量索引 + FTS5 + 增量同步 + WebSocket 推送 + 详情页懒加载”列为长期红线。因为这不是一个实现细节,而是整个三端体验成立的基础。

3. 音眸异步任务闭环:把 AI 长任务做成产品能力

很多项目接入 AI 时最容易忽略的一点,是长耗时任务的状态管理。

SonicLens 里我专门为 AI 解析设计了 insight_job 这条链路,用它承载:

对应的接口链路也很完整:

这背后体现的是一个产品判断:

AI 解析不是“点一下等结果”,而是要适配真实客户端环境,包括前后台切换、网络中断、长耗时等待和终态回流。

如果未来我把这个项目继续做大,这条链路依然能继续承载更复杂的模型能力,而不是推倒重来。

4. 数据治理闭环:系统不是只会“记”,还要会“修”

这是我很喜欢 SonicLens 的一个点。

音乐数据不是天然干净的。专辑名不一致、版本名混杂、曲目归属错误、第三方元信息缺失,这些问题只要你真的做过音乐资料系统,就一定会遇到。

所以我没有把系统停留在“记录下来”,而是继续做了治理侧能力:

这意味着 SonicLens 不是一个被动收集器,而是一个可以持续整理自己数据的系统。

对我来说,这类能力的价值非常高,因为它说明项目已经开始从“功能实现”走向“数据运营”。

四、我在这个项目里特别重视的工程质量点

如果把 SonicLens 当成一份作品来看,我最希望别人看到的不是“功能很多”,而是我对工程质量的判断标准。

1. 不是只写功能,而是写长期可维护的结构

GEMINI.md 里保留了大量长期架构约束,这件事本身就说明这个项目不是靠短期记忆在推进,而是在持续沉淀工程规则。

例如:

这些约束看起来“麻烦”,但正是这些约束让系统不会在迭代三个月后失控。

2. 对可观测性有明确投入

这个项目不是出了问题靠猜。

从实现上看,Telemetry 已经接入到:

而且不仅有 trace ,还有 meter 、db stats metrics 和启动自检逻辑。这对一个个人项目来说其实投入不小,但我认为非常值得,因为系统一旦有多个后台任务、多个外部依赖和多条异步链路,没有观测能力几乎不可能稳定演进。

3. 对客户端性能边界有明确设计

在三端客户端这块,我没有把所有状态都堆进一个全局 store ,而是明确区分了:

这一套拆分说明我在做的不是“能显示出来就行”的 SwiftUI 页面,而是在认真处理高频状态更新对列表、详情和播放条的影响范围。

4. 把文档当成系统的一部分

SonicLens 不是写完代码才补 README 的项目。

除了 README ,本仓库还维护了:

我越来越认同一件事:真正复杂的个人项目,必须有自己的“工程记忆系统”。

因为当系统开始跨越后端、客户端、异步任务、数据同步和 AI 能力时,光靠代码本身已经不足以承载完整上下文。

五、为什么我觉得 SonicLens 是一份值得拿出来展示的作品

如果从面试或者技术交流的角度看,我认为 SonicLens 最有说服力的地方,不是它“用了多少技术栈”,而是它体现了一种完整的工程能力组合。

它至少覆盖了下面这些维度:

更重要的是,这些能力不是彼此孤立的,而是组成了一个完整系统。

换句话说,SonicLens 不是“我会做后端”“我也会写一点 SwiftUI”“我还能接个大模型”的拼盘式展示,而是一份能体现系统设计意识、产品意识和工程落地能力的综合作品。

六、接下来它还会继续往前走

SonicLens 现在已经不是一个原型,但我也不把它视作完成态。

接下来我仍然会继续打磨几个方向:

对我来说,做 SonicLens 的意义从来不只是“做一个能用的项目”。

它更像是一块长期打磨的工程试验田:我把自己对后端架构、客户端设计、数据治理、AI 工程化和产品实现的理解,都持续沉淀在这里。

如果未来有人问我,什么项目最能代表我真正的技术表达,我想 SonicLens 一定会是其中之一。


如果你也对“个人音乐资产”“本地优先产品”或者“AI 与传统软件系统的结合方式”感兴趣,欢迎交流。

对我来说,SonicLens 不是结束,而是一个还会继续生长的开始。

1003 次点击
所在节点    分享创造
16 条回复
chochox
6 小时 57 分钟前
有想法,给你点个赞
YanSeven
6 小时 46 分钟前
感觉这种“集成”类的软件,很大的风险是不是在于“统一接入多个播放来源,持续监听和记录播放行为”。

那些平台有官方可靠的接口不
innocent245
6 小时 32 分钟前
不错的想法, 已 star
vincentchyu
6 小时 0 分钟前
@YanSeven 依靠 apple script 和 medianowing (这个是私有库)全部都不依赖音乐软件的 api 属于系统支持的采集
vincentchyu
5 小时 58 分钟前
@chochox 感谢
crime1024
5 小时 54 分钟前
一键三连
zdf3
5 小时 52 分钟前
为什么总要重复造轮子 就觉得自己与众不同?
JYii
5 小时 49 分钟前
“这个项目最有技术含量的部分,不是接口数量,而是几个闭环”

AI 润色完能不能自己再看看
xinjiawei
5 小时 49 分钟前
@YanSeven 太对了,就算是官方的 api ,有时候也会失效
ZenOfAI
5 小时 42 分钟前
用 GPT 写的文章,真的很难看懂
vincentchyu
5 小时 13 分钟前
@zdf3 不爱请尊重,不要随便评价别人与众不同,这样显得你很 Low 。另外重复造什么轮子?
wait9yan
3 小时 57 分钟前
问,这个文章里有几个“不是...而是...”
zdf3
1 小时 56 分钟前
@vincentchyu #11 你是真的下头 你发帖不就是让人评价的 咋了 只能说好听的 说你重复造轮子就是 low?
actopas
1 小时 48 分钟前
感觉心智负担超重,从此听歌像是做任务
vincentchyu
1 小时 31 分钟前
@zdf3 发帖反馈没问题,但评价的前提是‘看过内容’。你连对方造了什么轮子都没讲,直接贴标签,这不叫指出问题,叫情绪输出。你说‘只能说好听的’,“随便评价别人要与众不同”,那你现在这段算好听的吗?接受不了反问却要求别人闭嘴,这自由确实挺双向的。不展开争论了,祝你下次发帖能收到你期待的反馈。
vincentchyu
12 分钟前
@JYii #7
@ZenOfAI #9
@wait9yan #11

感谢提出文章的建议,发之前我看下来没觉得有啥你们一说确实很机械化,我的 prompt 是基于 README.md 让他自动写文章的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://study.congcong.us/t/1207910

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX