资讯动态

自建AnyPS5:PS5玩家的本地游戏库与开销管理工具

发布时间:2026/10/15 7:37:44 来源:尧图企业网站定制
如果你用PS5玩过四五个以上的游戏大概会有这种感觉数字版、实体盘塞在柜子里电子商店的购买记录和实体盘混不到一起通关没通关、还差几个奖杯全靠脑子记。实在记不住的时候只能翻系统自带的游戏库但那个库只告诉你“已安装”和“未安装”连买成多少钱、什么时候通关的都管不了。我做了AnyPS5这个自托管的本地Web工具把碎片信息统一收拢到一张张“游戏卡片”上。AnyPS5这个名字想表达的意思是不挑机器、不挑网络环境任何一台PS5玩家都可以把它跑起来。它不依赖任何官方接口不需要登录账号数据全留在你自己电脑里。运行起来就是一个浏览器能访问的小网站支持在手机、电脑上同时打开。核心功能包括游戏档案管理、游玩状态跟踪、奖杯进度记录、购入卖出价格统计以及一张简单的想玩清单。所有数据都存本地SQLite数据库备份就是复制一个文件。这个项目适合谁首先是手里已经攒了一堆游戏、想认真盘点库存的PS5玩家其次是喜欢折腾本地工具、对Web开发有点兴趣的人。如果你是刚拿到PS5的新玩家也能把它当成一个私人的游戏购物清单提前把想玩的作品和预算安排好。哪怕完全不懂代码使用打包好的一键启动版也问题不大只是我写这篇文章会把实现过程也展开讲一下方便你按需自己改。1. 项目概述从一团乱麻的游戏库开始1.1 为什么大部分家用方案都不够顺手最直接的办法是拿Excel表格记录游戏库。我也试过建了一列列字段名称、状态、价格、通关时间。但Excel有两个不好受的地方一是手机上看表格要装软件、要同步操作路径太长二是游戏封面、评分、奖杯进度这些东西放表格里既不直观也没法快速筛。每次想查“我有哪些还没通关的”都得重新拉筛选。另一个思路是使用在线游戏收藏网站。这类服务数据都在云端功能也确实全但对我来说有两个麻烦第一需要把个人信息交给第三方站点有些平台还必须公开你的收藏夹我比较介意这点第二大部分这类服务都面向全球玩家奖杯进度、二手交易记录这些本地化很强的功能要么没有要么需要订阅会员。我需要的只是一个小工具不需要社区、不需要推荐算法也不希望哪天站点停止运营数据就一起消失。所以最后确定了本地自托管的路线。AnyPS5整体是一个极轻量的Web应用程序跑在你自己电脑上手机和电脑通过浏览器访问同一个地址。不依赖云端账号也不需要公网服务器家里局域网内就能用。对“私人收藏夹”这件事来说这是一个足够简单、足够可控的方案也正好符合“AnyPS5”这个名字代表的那层意思——不挑平台随用随开。1.2 技术选型为什么是Vue Express SQLite技术栈是在设计阶段反复对比后定下来的。前端用的Vue 3脚手架走Vite后端是一个Node.js的Express服务数据层用better-sqlite3驱动SQLite。选择这三个只有一个理由个人项目维护成本最低。如果选一个全栈框架比如Next.js或者Nuxt倒也能做但AnyPS5的核心用法是“局域网内多人访问的本地数据库”需要动态接口、文件上传和静态托管。我用Vite构建前端用Express单独写几个JSON接口逻辑非常直观出问题时很快能定位。SQLite则是这一类项目的最佳选项它不需要单独安装数据库服务一个文件就是整个库备份迁移全都在几秒内完成。为什么不直接上Docker虽然容器化听着很专业但会让“普通玩家”的使用门槛陡增。我选择的做法是把整个项目做成一个文件夹写好一键启动脚本双击就能运行。Node.js环境问题用内置可执行文件解决而不是要求使用者先装一整套工具链。后面我会详细讲一键启动的实现方式实际上代码量不多但体验上的差别非常明显。2. 功能拆解游戏库到底需要记什么AnyPS5的功能不是越多越好而是围绕几个高频使用场景来做的找游戏、看进度、算开销。当初我列需求时差点控制不住差点加进了“社区评论”“好友对战”这些明显超出范围的东西后来全部砍掉。真正的核心功能只有四个模块游戏档案、状态追踪、奖杯账本、开销统计。四个模块共用一套游戏数据互相关联但都不复杂。2.1 游戏档案一张卡片装下所有关键信息每个游戏在AnyPS5里就是一张卡片对应的数据库里是一行记录。卡片上显示封面、标题、平台、类型、当前状态、买入价和简单评分。点进去以后可以维护更多信息我把字段控制在15个以内再多就容易懒得填了。字段具体包括游戏名称、平台PS5、PS4兼容、类型、发行年份、媒介实体盘或数字版、购入渠道、购入价格、卖出价格、发售价、状态未开始、进行中、已通关、全收集/白金、弃坑、开始日期、通关日期、我的评分、备注。这里没有游戏简介、没有攻略链接因为那些内容离“个人记录”太远加了反而干扰。字段设计里有一个很容易被忽视的点区分“购入渠道”和“媒介”。因为实体盘和数字版的交易逻辑完全不同实体盘会有二手买卖数字版则很难回血。如果只记一个价格后面做统计时会发现分不清钱花到了哪里。AnyPS5在数据库里用了两个独立字段并在统计时分开处理数字版只计入支出实体盘则按“买入价减卖出价”计算实际净支出。这个细节虽然小但用下来觉得非常关键。搜索和筛选也是档案模块的重点。列表页默认展示全部游戏卡片顶部有一个搜索框和一个状态筛选下拉框。搜索采用前端过滤的方式因为本地数据最多几百条一次性全量加载后在前端做名称模糊匹配性能完全够。筛选条件则支持组合状态选“未通关”平台选“PS5”媒介选“实体盘”这样周末想找一张还没通关的实体盘玩一秒就能定下来。2.2 状态与奖杯每天花十秒钟维护进度状态追踪是整个工具里使用频率最高的功能。它记录的是“这个游戏现在怎么样了”而不是“这个游戏是谁的”。AnyPS5允许每个游戏设置一个当前状态状态含义做了固定枚举方便后续统计。这里特别推荐大家把“弃坑”作为一个正式状态而不是删除记录。很多游戏玩到一半放下了它仍然占着你曾经的开销留着数据比删掉更有意义。奖杯进度是PS5玩家绕不开的刚需所以我单独做了一份小账本。每个游戏可以维护六个数字白金、金杯、银杯、铜杯、总奖杯数、当前完成数。在详情页的操作非常简单点一下“已完成奖杯数1”进度条自动刷新如果拿到了白金点一个“标记白金”按钮完成率直接变成100%。奖杯完成百分比是自动算的不需要玩家手动填写。这样做的目的只有一个降低记录的心理成本如果一次要填五个奖杯打勾我可能坚持不了两周。顺带记了“游玩投入”字段单位是小时。这个字段完全靠手动估误差很大但我还是保留了。原因是当你想知道“一台游戏机究竟消耗了我多少时间”时有个粗略数字总比没有好。AnyPS5不做精准统计反正游戏机自带的功能比我准我只需要在季度回顾时能列出一个量级就够了。2.3 开销统计游戏消费不该是一笔烂账游戏开销统计是我做这个项目时最有动力的一块。玩家很容易忽略一个事实游戏消费是真实的钱不能因为“游戏不是必需品”就不管。AnyPS5把每一笔游戏相关开销都拆到两个维度的表格里按购入渠道统计支出按状态统计回血。购入渠道主要区分数字商店、实体店、二手交易、朋友赠送统计时把赠送标为0元让平均数不被拉偏。二手交易记录是开销统计的亮点。实体版游戏买入时会填买入价卖出时会填卖价系统自动计算每张盘的实际成本。比如某张实体盘买入280元卖出200元那这个游戏的真实花费就是80元。如果还能以高于买入价卖出统计结果会显示负值相当于赚了钱。这种细账玩时间久了很有成就感也更清楚自己是不是应该停止买新游戏了。预算功能做得很轻只在侧边栏显示“本月游戏支出”和“已购未玩数量”。没有复杂的预算周期设置默认自然月。如果你和我一样对“游戏消费失控”敏感只要在月初看一眼这个数字就够了。项目后来还增加了一个“想玩清单”排序列表按“预计花费”和“期望指数”两个手动字段排序它集成在游戏档案里但不会影响现有统计。3. 开发全过程从零开始搭建AnyPS5这一章我把从创建文件夹到双击启动的真实过程拆开讲。虽然代码看起来不少但整体步骤其实很固定初始化前后端、定义数据库、写接口、写页面、打包托管。如果你是新手按顺序做一遍就能理解全貌如果你已经熟悉Web开发可以直接跳到3.4看一键打包的方案。3.1 环境准备和目录初始化开发AnyPS5需要本机已经安装Node.js我使用的是LTS版本。Node.js自带包管理器npm但我推荐用pnpm主要是装包速度快、占用磁盘小。具体差异这里不展开用npm其实也可以唯一需要保证的是前后端依赖都装在对应目录里。项目结构分成两个部分前端web和后端server。根目录只放一个说明文件和启动脚本。前端用Vite的官方脚手架创建命令是npm create vitelatest web -- --template vue然后进入web目录安装基础依赖。后端目录手动创建在server目录下执行npm init -y再安装Express和better-sqlite3。这两个库分别是Web框架和SQLite同步接口驱动。同时安一个concurrently用来在开发时同时启动前后端两个服务。初始化完成后目录大概是下面这样anyps5/ web/ # Vue3 前端项目 server/ index.js # 后端服务入口 db.js # 数据库初始化 data/ # SQLite 文件目录 uploads/ # 本地上传封面目录 start.bat # Windows 一键启动脚本 start.sh # macOS/Linux 一键启动脚本 README.md这一步遇到的常见坑是版本不一致。比如Node.js版本太老Vite会直接报错启动不了。遇到这种问题建议升级到当前LTS版本不要试图用老版本硬扛。另外better-sqlite3在安装时依赖预编译二进制有时会因网络问题下载失败这时需要重新安装或者设置本机镜像源这属于环境问题和项目本身无关。3.2 数据库设计和接口实现数据库设计是整个项目最核心的一步。AnyPS5只用了一张主表games外加一个简单的trophy_records表用来记录每个游戏奖杯完成情况。表结构经过三轮调整才稳定下来第一版字段太多第二版漏了软删除第三版才理顺。最终的主表结构如下CREATE TABLE IF NOT EXISTS games ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, platform TEXT DEFAULT PS5, genre TEXT DEFAULT , release_year INTEGER, medium TEXT DEFAULT digital, purchase_channel TEXT DEFAULT , purchase_price REAL DEFAULT 0, sell_price REAL DEFAULT 0, status TEXT DEFAULT backlog, start_date TEXT, finish_date TEXT, rating INTEGER, notes TEXT, cover_path TEXT, play_hours REAL DEFAULT 0, created_at TEXT DEFAULT (datetime(now)), updated_at TEXT DEFAULT (datetime(now)), deleted_at TEXT ); CREATE TABLE IF NOT EXISTS trophy_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER NOT NULL, platinum INTEGER DEFAULT 0, gold INTEGER DEFAULT 0, silver INTEGER DEFAULT 0, bronze INTEGER DEFAULT 0, completed INTEGER DEFAULT 0, total INTEGER DEFAULT 0, updated_at TEXT DEFAULT (datetime(now)) );软删除字段deleted_at是我后来加上的因为发现删掉一个有二手交易记录的游戏会让开销统计结果出现空洞。现在删除游戏只是标记删除时间默认查询会过滤掉它30天后才真正清理。这样即使误删也能快速恢复下文会专门讲这个操作。后端接口一共就七个获取列表、获取详情、创建、更新、删除、更新奖杯进度、上传封面。这里用一个小代码片段展示创建游戏的接口其他接口逻辑类似都是操作同一个games表const express require(express); const db require(./db); const app express(); app.use(express.json()); app.post(/api/games, (req, res) { const { title, platform, genre, release_year, medium, purchase_channel, purchase_price, sell_price, status, start_date, finish_date, rating, notes, play_hours } req.body; if (!title) { return res.status(400).json({ error: 游戏名称不能为空 }); } const result db.prepare( INSERT INTO games ( title, platform, genre, release_year, medium, purchase_channel, purchase_price, sell_price, status, start_date, finish_date, rating, notes, play_hours ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ).run( title, platform, genre, release_year, medium, purchase_channel, purchase_price, sell_price, status, start_date, finish_date, rating, notes, play_hours ); const game db.prepare(SELECT * FROM games WHERE id ?).get(result.lastInsertRowid); res.json(game); });这里的重点是输入校验不能只看数据库约束。数据库只能保证非空而业务上“价格不能为负数”“评分只能在0到10之间”这类规则在接口里就要拦住否则前端页面上会出现很反直觉的数据。我在所有写接口里都加了这一层手动校验虽然多写几行代码但后面省了很多排查时间。3.3 页面与交互把数据库变成能看的游戏墙前端页面设计成三大块顶部搜索栏、左侧筛选栏、中部游戏卡片墙。我没有使用现成的后台管理模板因为那种模板一眼看上去就像内部后台不符合个人项目的气质。卡片墙用CSS Grid布局每个卡片显示封面、标题、状态标签和买入价。状态标签用颜色区分未开始是灰色进行中是蓝色已通关是绿色弃坑是橙色白金是金色。配色选的是低饱和度的暗色系因为游戏墙图片色彩已经很杂背景尽量保持安静。交互上最重要的原则是“少跳转”。添加/编辑游戏用弹窗完成点卡片上的铅笔图标直接打开表单不需要单独跳一个详情页。奖杯进度也放在同一个弹窗的底部写游戏信息时顺手就能点一下完成数。页面中用到的核心组件代码量不大问题在于状态同步。前端拿到游戏列表后任何修改操作都要回到列表刷新我是通过一个极简的事件总线实现的没有引入完整的状态管理库避免小题大做。这里要说一下封面图的处理。我一开始允许直接填图片URL但很快发现很多网站的图片都有防外链填进去显示不出来。后来改成两个方案任选上传本地图片后端存到uploads/cover/目录前端用/uploads静态路径展示或者继续填URL但后端加一个图片代理接口先请求图片再转给前端这样就避开了浏览器的外链限制。如果只追求省事推荐第一种把图丢进文件夹最稳。3.4 构建打包与一键启动开发完成后把前端构建产物放到Express托管就能变成一个真正的本地应用。前端在web目录执行npm run buildVite会生成dist目录后端用一行静态托管代码让它生效app.use(express.static(path.join(__dirname, ../web/dist)));然后在后端入口的最后加一条兜底路由所有非API请求都返回index.html这样才能支持前端路由刷新不报404app.get(*, (req, res) { res.sendFile(path.join(__dirname, ../web/dist/index.html)); });一键启动脚本很简单先检查端口是否被占用然后直接启动后端服务。Windows下的start.bat大致长这样echo off cd /d %~dp0 set PORT4567 if exist server\data\app.db echo 数据库已存在 node server/index.js pause在真正给普通用户用时我推荐用打包工具把Node.js运行时打成单文件用户就不需要自己装Node。这个步骤可以把后端的入口文件和依赖压成一个可执行程序再配合前端dist目录和data目录一起发布。注意打包后路径要用process.cwd()而不是__dirname来定位数据目录否则用户双击启动的时候找不到数据库文件这也是我自己踩过的一个小坑。4. 实际部署本地运行和数据的长期保存开发完以后我第一时间把AnyPS5部署到日常环境下用了两周。这个过程和开发时完全不同会出现防火墙、局域网访问、路径大小写、中文编码等一堆细节问题。这一部分写的都是实测经历不是照着文档念出来的。4.1 让手机和电脑同时访问同一个游戏库AnyPS5设计为在家庭局域网内使用所以体验的核心是“手机扫码、电脑同屏”。启动服务后在电脑上通过命令行看到本机IP然后在浏览器里访问http://本机IP:4567。这里有一个很容易被忽略的点默认端口不要用3000或者8080因为太常见容易撞上其他开发程序。我用了4567冲突概率小很多。手机访问时如果页面打不开绝大多数原因不是程序问题而是系统防火墙拦截了端口。Windows系统第一次启动Node服务时会弹一个防火墙提示一定要选“允许”。如果当时点错了需要到防火墙设置里手动添加一个放行4567端口的入站规则。家里路由器的“AP隔离”功能也会让设备之间无法互通设置里把那个开关关掉就能解决。另外如果希望IP地址固定不变建议在路由器后台给这台电脑做一个DHCP保留。否则重启后IP变了手机上的收藏地址就失效了。AnyPS5没有做多服务注册这类复杂能力固定IP是最省心的维护方式。4.2 备份、迁移和一键换新机正是因为AnyPS5没有云端所以备份策略必须自己心里有数。我的方案非常简单把整个anyps5目录里除前端依赖外的文件压缩备份。其中真正有用的是两个东西server/data/app.db数据库文件和server/uploads文件夹。前者是全部游戏记录后者是本地封面图片。只要这两样东西在换到任何一台新电脑上都能恢复完整数据。我后来写了一个小批处理脚本每天凌晨把app.db复制到同一个硬盘的另一块分区保留最近30份。这个脚本算是半个备份方案因为它没有离开电脑硬盘坏了照样全丢。如果追求稳妥可以再定期把备份文件夹同步到网盘一类的存储上我在这里不推荐具体品牌但方法是一样的多存一份就多一份安心。迁移流程实测下来不超过五分钟。把旧机器的data和uploads复制到新机器的对应位置然后双击启动脚本数据就全部回来了。数据库文件不绑定机器也不记录绝对路径这是设计之初就坚持的一点给迁移省了不少麻烦。4.3 本地工具也要有好体验主题和快捷键论功能AnyPS5离商业化产品差得很远但我觉得个人工具更应该注重体验。首页卡片墙支持按标题排序、按购入时间排序、按评分排序还做了简单快捷键按N是新建游戏按/是聚焦搜索框。这些细节做起来不复杂却能大大减少每天维护库时的烦躁感。主题方面我用CSS变量做了两套主题默认深色和亮色。很多游戏玩家习惯深夜坐在屏幕前整理收藏默认用深色会舒服不少。亮色主题留给白天在办公室或者书房用。两套主题都不会影响游戏卡片封面的观感因为封面的亮度和饱和度已经很高再叠加一层颜色反而会脏。如果之后再加主题我大概率会加个“CRT扫描线”这种偏装饰性的效果但不会让它影响可读性。5. 常见问题排查与避坑记录工具跑起来以后最大的问题往往不是核心功能而是一些看起来特别基础的环境问题。我把这两周遇到的、以及朋友试用时碰到的典型问题整理成了一张表方便直接对照。现象可能原因解决办法双击启动后窗口一闪而过Node.js未安装或路径错误确认启动脚本里的Node路径使用内置运行时版页面打不开提示连接失败防火墙拦截端口/使用了错误IP防火墙放行4567检查IP和端口手机能访问电脑打不开浏览器缓存了旧地址清除浏览器缓存或换一个无痕窗口中文标题搜索不到输入法半角/全角混用前端过滤已包含大小写转换确认搜索关键词无多余空格封面图片全部裂图使用了外链图片被防盗链拦截改用本地上传或将图片代理开关打开误删了一款游戏列表里没了软删除机制生效用软删除恢复接口或直接改数据库deleted_at为空统计数据里的金额不对实体盘和数字版媒介标错了修改对应游戏媒介字段再刷新统计页更新页面后修改丢失浏览器没有触发刷新加载新JS强制刷新或缩短Vite静态资源的缓存时间5.1 端口被占用与启动脚本的容错端口被占用是个人Web项目最常见的启动失败原因。有时候你开着别的前端调试进程刚好占用了同一个端口。我的做法不是改代码而是在启动脚本里先检测端口占用情况被占用时自动提示并抛出一个可用的备用端口。检测命令在Windows和macOS上不一样所以脚本里分了系统判断。虽然代码量不大但对普通用户来说这比看到一串报错信息友好得多。如果数据库文件正在被另一个实例占用better-sqlite3会直接报错。这种情况发生在多次点击启动脚本、开了两个服务时。我在启动脚本里做了单实例检查存在锁文件就提示先关闭旧窗口避免数据损坏。SQLite对并发写的支持本来就有限个人工具就应该遵循“同一时间只有一个服务实例”的原则。5.2 中文数据和SQLite的过滤顺序中文搜索一开始我担心会有编码问题实测下来发现更麻烦的是输入关键词时混入了全角空格。SQLite本身存中文没有编码障碍关键是查询时要把输入关键词和字段内容都转成统一格式。AnyPS5的搜索用前端过滤实现先把关键词的trim()和toLowerCase()做掉再遍历已经加载的游戏数组用includes匹配标题和备注字段。这样处理中文没问题英文字母大小写也不会误伤。在数据量很小的情况下前端过滤是合理的。但如果你真有上千条游戏记录那建议在后端用WHERE title LIKE ?配合索引来查。做到那一步时要注意SQLite的LIKE对中文没有大小写概念但对英文大小写是不敏感的不用额外处理。我在文档里写清了两种方式的适用场景避免后来者一上来就做复杂搜索。5.3 误删恢复与软删除的细节软删除是我自己翻过车后加上的功能。最初删除游戏是直接DELETE FROM games结果一个月后统计年度游戏支出发现总花费比印象里少了一百多一查才发现其中包含一笔已经卖出回本的二手交易记录。因为删了记录那条“买入280元、卖出200元”的净支出80元也跟着没了。从那以后AnyPS5的所有删除操作都改成更新deleted_at字段普通列表默认隐藏后台保留完全数据。恢复操作有两个层级。程序里我做了“最近删除”列表误删后可以在设置页一次性恢复如果连这个列表都进不去就直接用SQLite客户端改一行数据把deleted_at置空即可。这个方法我在文档里写得很醒目因为个人工具不需要太严谨的多级权限设计但要给使用者留一条后路。5.4 图片防盗链与本地存储的选择图片一直是个人收藏工具里最容易被低估的麻烦。很多游戏封面来源网站限制了跨站引用直接填URL在本机浏览器里是可以看到但在手机浏览器访问时可能就裂了。花了一下午排查后我放弃依赖外链改成默认使用本地路径。操作上就是上传图片到uploads/cover数据库里只存一个相对路径。本地存储也有一个需要提前考虑的地方硬盘占用。封面图片通常几百KB存几百个游戏也就一两百MB问题不大。但如果将来想存截图甚至录像那就要重新设计存储方案至少把大文件单独放一个目录不和数据库紧密绑定。现在AnyPS5只存封面不碰截图最大程度保持数据文件夹清爽。最后的体会做个让自己舒服的工具AnyPS5做下来我个人最大的感受是工具的价值不在功能多而在它真的能被你每天用起来。从第一天起我就把常用的录入操作控制在两次点击以内这就是它能坚持用到现在的原因。任何功能只要让我产生“今天懒得记”的感觉要么重新设计交互要么直接砍掉。另外一个收获是不要把数据完全交给云端服务。自己动手做一个本地管理工具不仅能完全掌握隐私还能随时按自己的习惯调整。AnyPS5目前离完整产品还很远我接下来的计划是增加年度报告导出功能把一年内通关的游戏、真实花费、白金数量汇总成一张网页卡片方便留作纪念。但不会盲目加社区、加排行榜因为那些东西对这个工具没有意义。如果你也是游戏库一团乱麻的人完全可以照着这篇文章的思路做一个自己的本地管理工具。不一定叫AnyPS5也不一定用同一种技术栈关键是找到一套让自己愿意坚持的记录方式。数据是自己一点一点攒起来的那才是真正值钱的东西。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑