ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

手搓PS5游戏管理面板:SQLite数据建模与PWA部署实践

手搓PS5游戏管理面板:SQLite数据建模与PWA部署实践 打开PS5主界面看着满屏游戏封面的时候我突然有种“仓库管理员”的错觉。头一年入手PS5我的数字资产膨胀得相当夸张PS会员库塞了几百个游戏打折促销的购物车里又躺了十几款想把它们分类整理却发现主机自带的“游戏库”功能根本不够用。它只会按字母排序列出你拥有或领取过的游戏至于“我新买的卧龙到底占了多大空间”“上个月打折入手的双人成行到底钱花值不值”“那些被会员库覆盖但还没玩的游戏什么时候会下线”这类问题主机一概给不了答案。所以我花了两周做了一个自己用得上的小工具取名 AnyPS5。先声明一下它不是破解主机、不是模拟器、不是外挂只是一个跑在局域网里的个人游戏管理面板。它能帮你把游戏库、存储空间、购买价格、游玩时长都记下来还能在你想清理硬盘的时候告诉你“删哪个最不心疼”。这篇文章就是把整个项目从立项、设计、开发到实测三个月的完整过程拆给你看包括了数据表怎么建、为什么不用自动化采集、以及在真实使用里踩到的一堆坑。如果你也是那种游戏买得比玩得多、硬盘天天报警的人这篇应该对你有点用。1. 从一次“删游戏恐慌”说起AnyPS5想解决的不只是存储先把这个项目的动机交代清楚。很多人以为游戏管理工具的痛点是“存储不够”其实真正让人难受的是“存了却不记得删了又怕后悔”。1.1 那晚我对着SSD里只剩32GB发呆那是某个周五的晚上我刚通完一个段落准备开新坑系统提示可用空间不足32GB。当时我的PS5装了战地全系列、几个还没通关的RPG、一堆随时随地就想打开的格斗游戏赛博朋克2077的回滚大小一度占到100GB以上。我盯着主机界面翻来翻去不知道删哪个好——不是没空间而是删错了就意味着下次想玩的时候要用流量重新下载时间成本比空间成本更贵。我当时大可像很多人那样做个简单的计算装了多少就删多少留着最近玩的那批。但问题在于PS5自己的存储设置页面虽然能按游戏排序却不能告诉我某款游戏是我打折时39块放进来的还是PS会员白嫖的还是重要的双人派对备选项目。它也不会告诉我“这个游戏我已经通关了删掉毫无心理负担”。这些信息全在我脑子里而我的脑子被工作和生活塞得太满根本想不起来。1.2 官方界面的缺口就是这类工具的出发点如果你长期用PS5会知道系统里其实有一个“游戏库”入口能够筛选PS4/PS5、已安装/未安装但这套界面的定位是“让你找到游戏并启动”不是“让你管理整个数字资产账本”。它没有价格历史没有购买渠道标记也看不见DLC占掉的空间更别提把多区服账号买过的同一款游戏合并成一个条目。我也试过用Excel记第一条游戏记录写得极其认真第二周就开始漏后来整个表变成半死不活的状态。为什么因为Excel的问题是打开路径太深先解锁手机、找表格App、翻目录、点开文件、再滚动到目标行。步骤一多习惯就没了。当时我就想做一个小程序点开就是今天的“游戏财务摘要”大不了只维护三五个常用字段一分钟录完一条数据。1.3 项目定位一张可以装进口袋的个人游戏账本AnyPS5这个名字本意是“在任何设备上看清自己的PS5库存”。它可以部署在树莓派、旧笔记本或者NAS里通过浏览器访问。数据全部落在本地文件里备份就是拷走一个SQLite数据库文件和导出JSON。一开始我没有画产品原型只写了一个需求清单能记录游戏名称和别名、能记录数字/实体版本和区服、能记录占用空间大小、能记录购买来源和价格快照、能记录每次游玩的起止时间、能按“占位大小”和“最近游玩时间”排序、能在列表上直接进行“已通关/未通关/弃坑”标记。后来的开发过程证明这七条足够了正是从这七条里长出了AnyPS5的核心结构。2. 需求围栏AnyPS5只做四件事多余一概不做做个人项目最怕的是功能越加越多最后变成“全屋智能中控台”。AnyPS5从立项那天起就给自己划了四条业务线每条线对应一个具体的高频场景。2.1 空间账本主机上装了什么、如果全装还缺多少第一件事是回答“我的硬盘还能撑多久”。每次往游戏库里添加一条游戏记录我就会顺手填上它的安装空间单位心里默认为GB。随着条目增加AnyPS5会自动累加所有“已安装”游戏的占位总和并和主机总容量对账。如果一款游戏有PS4和PS5两个版本我会把它们作为两个独立条目毕竟它们的体积和是否需要外接硬盘完全不一样。这条业务线的补充场景是“重建场景”。比如你换了主机或者删空了机器想重新知道“如果要装回所有持有游戏需要多大硬盘”AnyPS5会把你打上“未安装”标记的那批也汇总出体积。这个数字往往很吓人但对决定是否加装M.2 SSD有非常直接的参考意义。2.2 价格时间线最低价、折扣价、入手价分开记第二件事是记录我到底“花没花冤枉钱”。我买过的游戏里有不少是从各个平台的低价信息里淘来的但人的记忆会美化和失真半年后可能连自己多少钱入手的都忘了。所以我加了一张价格表每个游戏可以有多个价格快照例如观察日期和价格折扣比例可选数据来源“区服商店”“网页下单页”“邮件收据”备注例如“黑五折后价”“首次打折”。之后首页会显示每个游戏最后一次记录的“到手价”和“历史最低价”如果最低价不是自己实际支付价格就会看到那个让人血压升高的差值。这个功能不是为了让你焦虑是为了在剁手下一个游戏前三秒钟想一下这个差值值不值。2.3 会话账本补上官方“游玩时间”统计的盲区PS5自己的主界面确实能看游玩时间但那个统计挺粗糙的有时候我挂着游戏去吃饭它也会把时间算进去。AnyPS5做的“会话账本”是另一个思路每一次打开游戏前记录“开始”结束游戏时记录“结束”系统算出当次时长累计成总时长。最开始我以为这会很麻烦实际用下来只需要每次玩完顺手点一下“结束”手感上来说比打一个“已通关”标记还快。这套数据还衍生出一个有趣的排序维度按“单次平均时长”排序。你会发现有些游戏经常开但每次就打十分钟有些游戏一次能沉浸两小时。这对“碎片时间选什么游戏玩”特别有用比单纯看总时长更符合现代玩家的真实作息。2.4 交易与订阅状态数字版、盘、DLC、PS 去留第四件事是把整个持有状态说清楚。实体盘和数字版要分开因为盘可以出二手数字版则永久绑定账号。DLC也不能被忽略有些游戏的DLC比本体还大比如某些历时几年的服务型游戏后续更新包动辄几十GB。AnyPS5在每一条游戏记录上加了“类型”字段本体/DLC/季票/会免领取还会标记当前是否可用。这个“可用状态”是应对订阅制游戏的PS会员库的游戏说下线就下线但账号库里的记录不能跟着消失至少要知道自己“曾经拥有并玩过”。这四件事做完之后AnyPS5的首页变成了一个能回答四个高密度问题的仪表盘硬盘还能装什么、我最常玩什么、我买贵了多少、哪些游戏即将从会员库里消失。3. 第一版的数据骨架三张表加一个文件就够任何管理类工具的核心都是数据模型。AnyPS5的数据模型一开始就没设计得很复杂因为我知道个人项目的维护成本必须压到最低。整个数据库就是一个SQLite文件三个核心表加一个配置项。3.1 从对象到表Game、PlaySession、PriceRecord 怎么划分我在设计表结构时没有搞什么复杂的三范式论证纯粹按照“一次操作对应一张表”的直觉来的。Game表负责描述一款游戏的基本信息PlaySession表负责记录每一次游玩PriceRecord表负责保存价格历史的每一笔观察。SQLite的建表语句大概是这样的CREATE TABLE games ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, alias TEXT, region TEXT DEFAULT CN, game_type TEXT DEFAULT base_game, -- base_game / dlc / season_pass / ps_plus platform TEXT DEFAULT PS5, storage_size_mb INTEGER DEFAULT 0, installed INTEGER DEFAULT 0, status TEXT DEFAULT backlog, -- playing / finished / abandoned / backlog purchase_channel TEXT, purchase_price_cents INTEGER DEFAULT 0, purchase_date TEXT, note TEXT ); CREATE TABLE play_sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER NOT NULL, started_at TEXT NOT NULL, ended_at TEXT, note TEXT ); CREATE TABLE price_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER NOT NULL, observed_on TEXT NOT NULL, price_cents INTEGER NOT NULL, region TEXT, source TEXT, note TEXT );每次打开任意页面我最关心的永远是查询“我到底有哪些游戏”所以games表里的大部分字段都会出现在列表查询里。PlaySession表只存起止时间不冗余游戏标题PriceRecord表只存价格观察值和来源防止之后想统计“黑五平均降价幅度”时没数据可用。3.2 为什么“别名”字段是中文区用户刚需这是我实际用了两周后才加的重要字段。之前我往库里加“最终幻想7重生”时用的标题是“FF7 Rebirth”后来在手机里写成了“最终幻想7重生”系统认定它们是两个游戏。吃过这个亏之后我把games表加了alias字段用来存放游戏在不同语言下的常见叫法。这样无论我下次输入的是“FF7RB”还是“重瓣版”搜索都能命中同一个条目。以后谁要做类似项目建议第一次建表就把alias放进去。游戏名在不同来源里的表达差异超出你的预期简体中文、繁体中文、英文名称、社区简称很容易让信息管理工具变成一串重复垃圾数据。3.3 录入流程设计一次完整录入不超过20秒光有表结构还不行得让数据能长期进得来。我把新增游戏的操作设计成一条流搜索框输入标题如果库里已有同名记录就弹出合并提示如果没有就直接跳到表单页表单字段经过裁剪只保留七样标题、平台、类型、区域、存储大小、购买渠道、入手价格。写得快的人十秒能完成慢一点也能控制在20秒内。对于已经安装到主机里的游戏我会先把系统的“存储”页面打开当作参照直接按页面上显示的数字填进去。输入的时候注意系统里的单位是GB录入后保存到数据库时统一换算成MB避免后面计算时出现十进制二进制混着用的尴尬。这个细节后面会再提因为它真的是个大坑。4. 前端和部署一台老旧设备也能跑起来的轻量方案这个项目一个人用没必要为了“技术时髦感”上一套微服务和容器编排。AnyPS5的目标是低门槛、耐折腾、恢复成本低。部署逻辑很简单一个Node.js进程提供静态文件和接口一个SQLite数据库文件负责落盘。4.1 技术栈选型的两个硬约束我在选型时的第一个硬约束是“老设备能跑”。当时手边有一台吃灰多年的旧笔记本双核CPU连风扇都滋滋响。让我把这套东西部署在Kubernetes集群上是完全不现实的想法。最终我选了Express better-sqlite3的组合因为better-sqlite3是同步操作不需要管理一堆异步回调代码量可以减掉一大截。第二个硬约束是“拆掉容易”。我不想在NAS和路由器上装任何需要常驻后台的大组件。直接把项目丢到任意一台有小容量存储的设备里安装Node.js后跑npm install npm start整个应用就能起来。我连数据库迁移工具都没用因为个人工具的表结构很少变动真改了直接改建表语句然后从旧库里导出JSON再导入就行。4.2 离线优先的PWA思路前端我最初做了一个纯粹的服务端渲染页面后来觉得手机上用起来还是不够顺畅就把页面改成响应式单页应用并加了一个很轻的manifest配置让它在手机上能“添加到主屏幕”。这样从主屏点开就是独立窗口看起来像一个原生App。同时我把页面静态资源完全缓存到浏览器后端接口不可用时列表依然能显示最近一次加载过的数据。这个过程中没有引入复杂的PWA框架毕竟核心只是离线缓存。对于一个家庭局域网里的工具这个可靠程度已经非常高哪怕NAS临时重启手机的快捷入口也不会白屏报错。4.3 数据安全边界文件备份和不联网原则从一开始我就确定了AnyPS5的数据不联网原则。整站不添加任何所谓的“云统计”、不接入第三方登录、不给公网开端口。你只需要让它跑在自己的局域网里或者绑定到自己的内网域名后面。备份方式也更简单粗暴直接把data目录里的数据库文件复制走。个人工具最忌讳的就是“数据被锁死在别人服务器上”。我见过很多好用的管理服务结果折腾半天发现导入导出只能走他们定义的格式甚至还有账号体系绑定。AnyPS5的做法是提供两个导出按钮一个导出现状套用的SQLite文件另一个把整个数据库的内容转成一个可读性很好的JSON。两个导出文件就是我全部数据的所有权凭证。5. 三个月实测踩过的七个坑跟你们逐一汇报工具上线不代表能用真正有价值的部分是在真实使用里被磨出来的。这三个月我几乎每个星期都会打开AnyPS5录入或回顾数据踩了不少坑下面这七个是典型的按照影响程度从轻到重排。5.1 同名游戏在不同语言下的重复录入这个坑在第三节里提过实际发生频率非常高。同一个《马力欧》游戏在不同地区商店里叫法都不一样更别提中文互联网社区里还流行各种简称。修复方案就是前面说的alias字段另外我在搜索接口里做了简单的“去掉空格和标点再比较”的逻辑不然“FF7 Rebirth”和“FF7Rebirth”还是会变成两条记录。5.2 容量单位换算的隐形地雷有一段时间我在首页统计“全部游戏占位总和”时发现数字比我主机上真实可用容量大了一圈排查半天才发现问题出来早期录入的时候有些游戏我填了GB数例如80GB直接存成了80没有乘以1024换算成MB。后来有两个游戏我填的是按M.2页面显示的GB却因为眼残写成了“8000MB”。底层单位必须统一这一点我交了一次学费。建议所有做存储记录的朋友在设计字段时就把它定义成最小统一单位比如MB。录入界面上可以给人看GB的小数点视图但数据库里永远只能存一个整数。5.3 不同区服的同款游戏价格被混算因为我会在港服、日服和某些地区的实体店之间横跳同一款游戏的价格来源跨越多个区。结果就是历史最低价计算时把不同区服的价格混在一起得出了“我竟然亏了这么多”的错误结论。后来我把PriceRecord表也加上region字段统计历史最低价时强制按当前游戏优化出来的region范围过滤。这个逻辑加得很早所以后续没再被误导过。5.4 官方统计时长和自记时差的迷思PS5会显示“游玩时间”但它的口径和我用会话账本算出来的完全是两回事。官方那边是把应用处于前台的时间都算进去哪怕是挂机看地图也算我的会话账本记录的是“一次主动开始和主动结束”之间的时间所以对那种“开游戏然后去回微信”场景的记录会更干净。如果你也想做类似工具建议别指望官方统计能给你完美的数据。自己记一次就玩一次的时长会有漏记但漏记的比例远比官方口径里的“假装在玩”低。5.5 DLC与主体游戏的边界模糊有的DLC和主体根本分不开比如《艾尔登法环》的黄金树幽影在我心里它就是个新游戏。可在存储统计时它又确实只作为主体游戏的一条扩展记录。我的解决方式是在DLC条目里新增一个base_game_id字段标注它挂在哪个主体下存储统计时DLC单独算体积但在查找时能通过主体游戏入口关联到。这个方案不完美但比把DLC硬塞进主体游戏的某个备注字段里要在数据上干净得多。5.6 订阅制游戏会“过期”但记录不该消失PS会员游戏的下架是很现实的事。有一款我很喜欢的游戏入库时没标记可用的到期时间结果某天想点开玩发现它已经不在会员库里了。如果我不做AnyPS5记录这段拥有经历和游玩记忆就彻底没了。现在我给games表预留了一个是否订阅领取的标记并在备注里写清楚它是哪个会免月的而不是简单地打勾“已领取”。至少未来回顾自己玩过什么时库是完整的。5.7 工具用着用着懒了怎么逼自己保持输入习惯最后一个坑不是技术问题是习惯养成。我再喜欢用表格和面板也没有动力每周定期去录入游玩时长。后来我给AnyPS5的首页加了一个“今天想开哪个小游戏”的随机按钮它会从标记为backlog、且安装过的游戏里挑一个出来。这种一点点游戏化的诱导反而很有效每天打开首页自带一种“抽卡”的趣味顺路就更新掉当天的游玩记录。6. 下一版规划不做更多功能只做月度过账AnyPS5目前版本用得很稳定我也在思考下一步往哪走。这里顺便聊聊我的取舍思路也给打算做同类个人工具的朋友一个参考坐标。6.1 玩家反馈里出现频率最高的需求陆续给几个朋友用过之后他们反馈最多的不是“存储统计”而是“月度花费报告”。很多人跟我一样看到折扣就手痒但根本不知道自己一个月在游戏上花了多少钱。AnyPS5有购买记录和价格快照完全能按月生成一张消费汇总表这个月买了几款游戏、花了多少、实际游玩时长多少、每小时的娱乐成本约合多少。这个需求比我想象的要有价值因为大脑在面对“每小时成本0.5元”这种数字时会产生一种非常清晰的心理反馈。而平时我们只会看到“打折-30%”的红色标签并不知道自己真正打开过几次。6.2 月度报告的交互设计思路我计划在月末生成的报告里只放三个区块本月新增游戏列表及总支出、本月游玩时间最长的五款游戏、上个月购入但至今未打开的游戏列表。第三块是重点它专门用来治疗“买过玩过”的幻觉。界面会做成一张长条卡片便于截图分享既能让自己的消费冲动被量化也能纳入朋友之间互相调侃的谈资。同时我准备给所有记录增加一个“手动修正”按钮因为月度汇总的对账周期很长难免有当月的记录后来才发现填错了。工具不应该拿数据逼人它应该始终允许数据被人类修正。6.3 我的维护原则能离线绝不联网能导出绝不锁死接下来无论AnyPS5怎么迭代有两根红线我不会碰。第一是不做账号系统就算以后有人在network环境里用也只是共用一份本地数据不能被单一账号绑架第二是不做自动后台采集游戏价格和游戏名称都不会爬别人的网站宁可留一个手动吸光按钮由用户自己决定什么时候把键盘里的内容放进库里。守住这两条工具就永远是工具不会变成另一种需要你去维护的负担。最后分享一点这三个月的体会。AnyPS5一开始的目标只是解决“硬盘不够”的烦躁真正坚持用下来它带来的最大变化是让我对“买游戏”这件事的认知清醒了不少。以前看到打折就想囤现在下单之前会下意识想一下它会不会成为月度报告里那个“至今未打开”的条目。数字不会替你做决定也不会阻止你消费但它能强迫你看见自己真正的游戏习惯。如果你也经常被数字版游戏塞满硬盘还舍不得删不妨也试着建一个属于自己的游戏账本——不用写代码用表格工具就行关键是开始记录并且在月底愿意回头看它一眼。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进