1. 项目概述:一个复古游戏收藏家的数字工具箱
作为一个玩了十几年复古游戏,家里堆满了各种卡带和光盘的老玩家,我深知管理实体收藏的“痛”。从FC红白机到PS2,从《超级马里奥》到《最终幻想7》,每张游戏卡背后都是一段回忆。但问题来了:当你的收藏超过一百款,甚至几百款时,光靠记忆和Excel表格就完全不够用了。哪张卡带是“近乎全新”(Near Mint),哪张只是“良好”(Good Condition)?去年淘到的《塞尔达传说:时之笛》N64版具体是哪天入手的?想按平台筛选出所有世嘉MD的游戏,手动翻找简直是大海捞针。
这就是我动手打造Retro Games Collection Manager的初衷。它不是一个简单的清单,而是一个由命令行工具(CLI)和REST API双引擎驱动的完整管理系统,两者共享同一个轻量级的SQLite数据库。你可以把它想象成给你的游戏柜配了一个私人数字档案员和一个随时待命的自动化接口。CLI工具适合像我这样喜欢在终端里敲敲打打、快速完成批量操作的老派极客;而REST API则为未来可能开发的Web前端、移动App,甚至是与其他收藏管理平台的集成打开了大门。整个项目基于Python构建,核心依赖就是sqlite3和FastAPI,在开发过程中,我深度使用了Cursor编辑器搭配GitHub Copilot,体验了一把AI结对编程如何将想法快速落地的爽快感。接下来,我会带你从零开始,完整复现这个系统,并分享我在构建过程中踩过的坑和总结出的最佳实践。
2. 核心架构与设计思路拆解
2.1 为什么选择“CLI + API”的双模架构?
在项目初期,我面临几个选择:是只做一个带图形界面的桌面应用,还是只做一个Web服务?经过权衡,我决定采用CLI与API分离的架构,主要基于以下几点考量:
效率与灵活性的平衡:CLI工具在执行一次性、批量的管理任务时具有无可比拟的效率。比如,当我从线下集市淘回几十张游戏卡,想要快速录入系统时,在终端里运行一条导入命令远比在图形界面里一个个点击添加要快得多。CLI脚本化能力强,也便于集成到自动化流程中,例如定期备份数据库到云端。
开放性与可扩展性:REST API的加入,彻底打破了工具的使用边界。它意味着我的游戏库数据不再是一个信息孤岛。未来,我可以基于这个API轻松开发一个视觉更友好的Web界面给我的家人用;也可以写一个简单的手机App,让我在逛二手店时能随时查询库存;甚至可以通过API与其他游戏社区(如IGDB)的数据进行同步。这种设计遵循了“关注点分离”的原则,数据层、业务逻辑层和表现层清晰独立。
技术栈的统一与简化:无论是CLI还是API,它们都基于Python,并共享同一套数据模型和数据库操作逻辑。这极大地减少了代码重复和维护成本。SQLite作为数据库,无需安装和配置独立的数据库服务,一个.db文件搞定所有,非常适合个人项目和小型收藏管理。
2.2 数据库设计:如何为复古游戏建模?
数据库是整个系统的基石。一张games表是核心,但字段的设计需要仔细推敲,以准确描述一件实体游戏收藏品。
# 这是在CLI工具中初始化数据库时执行的SQL语句核心部分 CREATE TABLE IF NOT EXISTS games ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, release_year INTEGER, platform TEXT NOT NULL, date_acquired DATE DEFAULT (DATE('now')), condition TEXT CHECK(condition IN ('mint', 'vgc', 'gc', 'used')) );title和platform:这是识别一个游戏最基本的信息。title设为非空,platform我使用了文本字段,而不是枚举,是为了灵活性。比如,除了标准的“NES”、“SNES”,你可能会录入“Famicom”(日版红白机)或“Mega Drive”(世嘉MD的欧版名称)。release_year:年份对于复古游戏收藏尤为重要,它是划分时代、追溯历史的重要维度。这里用了INTEGER类型,查询时可以直接进行范围筛选。date_acquired:收藏的乐趣之一在于“遇见”的过程。记录入手日期,未来回顾时能串联起许多故事。默认值设为当前日期,方便快速录入。condition:成色是衡量实体收藏价值的关键。我采用了在复古游戏圈内比较通行的四级分类:mint:全新未拆封或近乎完美,无任何使用痕迹。vgc(Very Good Condition):品相很好,可能有极轻微的使用痕迹,包装齐全。gc(Good Condition):有正常使用痕迹,但功能完好,包装可能不全。used:有明显使用痕迹或磨损,但通常仍可正常运行。 使用CHECK约束确保录入的值只能是这四种之一,保证了数据的一致性。
实操心得:关于“成色”字段的思考最初我打算用数字1-10来评分,但很快发现这太主观了。不同人对“8分”的理解可能天差地别。而采用行业内共识的文本描述(mint/vgc/gc/used),虽然粗略,但沟通成本低,更容易在玩家间形成一致认知。如果你的收藏涉及更细分的领域(如卡带标签完好度、光盘划痕程度),可以考虑增加额外字段,但核心的
condition字段保持简单明了。
2.3 安全基石:管理员与密码哈希
系统允许通过CLI管理管理员用户,这些用户凭证将用于保护API的写操作(增删改)。安全无小事,明文存储密码是绝对禁忌。
# 在`retro-games-cli/utils/auth.py`中 import bcrypt def hash_password(password: str) -> str: """使用bcrypt生成密码的加盐哈希值。""" # 将字符串密码编码为字节 password_bytes = password.encode('utf-8') # 生成盐并哈希,默认rounds=12提供了良好的安全/性能平衡 hashed_bytes = bcrypt.hashpw(password_bytes, bcrypt.gensalt()) # 将字节哈希值解码为字符串存入数据库 return hashed_bytes.decode('utf-8') def verify_password(plain_password: str, hashed_password: str) -> bool: """验证明文密码是否与存储的哈希值匹配。""" try: plain_bytes = plain_password.encode('utf-8') hashed_bytes = hashed_password.encode('utf-8') return bcrypt.checkpw(plain_bytes, hashed_bytes) except Exception: return False我选择了bcrypt算法,它是目前公认的、用于密码存储的最佳实践之一。它内部自动处理了“加盐”(Salt)过程,即使两个用户密码相同,其哈希值也完全不同,能有效抵御彩虹表攻击。bcrypt.gensalt()的默认计算成本因子(rounds=12)在普通服务器上验证一次大约需要0.2-0.3秒,这个延迟对于防止暴力破解至关重要,但对合法用户登录体验影响微乎其微。
admins表的设计非常简单:
CREATE TABLE IF NOT EXISTS admins ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL );UNIQUE约束确保了用户名的唯一性。
3. CLI工具深度解析与实操指南
CLI工具是系统的管理核心,位于retro-games-cli/目录下。它使用Python的argparse库构建,结构清晰。
3.1 环境搭建与初始化
第一步永远是创建一个干净的Python环境,避免包依赖冲突。
# 进入CLI工具目录 cd retro-games-cli # 创建虚拟环境(强烈推荐,这是Python项目的标准做法) python3 -m venv .venv # 激活虚拟环境 # Linux/macOS: source .venv/bin/activate # Windows: # .venv\Scripts\activate # 安装依赖 # requirements.txt 内容通常包含:bcrypt, sqlite3 (内置) pip install -r requirements.txt激活虚拟环境后,你的命令行提示符通常会发生变化(前面出现(.venv)),这表明你正工作在一个独立的环境中。
接下来,初始化数据库。这个命令会创建retro_games.db文件(如果不存在),并建立games和admins表。
python main.py init执行成功后,你会在当前目录下看到新生成的retro_games.db文件。务必将这个文件加入你的.gitignore中,避免将包含敏感信息或测试数据的数据库提交到代码仓库。
3.2 游戏管理:从单条录入到批量处理
3.2.1 添加单款游戏
最基础的操作是添加一款游戏。CLI提供了交互式和参数式两种方法。
交互式添加(推荐新手):
python main.py add-game执行后,程序会依次提示你输入标题、发行年份、平台、入手日期和成色。入手日期默认为当天,直接按回车即可;成色需要输入mint/vgc/gc/used之一,程序会有提示。
命令行参数式添加(适合熟练后或脚本调用):
python main.py add-game --title "Chrono Trigger" --release-year 1995 --platform "SNES" --date-acquired "2023-10-01" --condition "vgc"这种方式效率更高,尤其适合你知道所有信息时。所有参数都是可选的,未提供的会有默认值或留空。
注意事项:日期格式无论是交互式还是参数式,
date-acquired的输入都必须遵循YYYY-MM-DD格式(例如2024-01-15)。这是SQLite的DATE类型识别的标准格式。如果你输入15/01/2024,会导致存储错误或查询异常。在代码内部,我们使用datetime.strptime()进行严格校验。
3.2.2 批量导入与导出:CSV的妙用
当你的收藏初具规模,或者想从其他系统迁移数据时,批量操作是救命稻草。CLI工具支持标准的CSV格式。
导出现有游戏到CSV:
python main.py export-games --output my_collection.csv这会生成一个包含所有游戏信息的CSV文件,你可以用Excel或Numbers打开查看和编辑。
从CSV文件批量导入:首先,你需要准备一个CSV文件,其表头必须与数据库字段对应。一个标准的模板如下(可以用导出的文件作为参考):
title,release_year,platform,date_acquired,condition Super Mario Bros.,1985,NES,2023-05-10,gc The Legend of Zelda: A Link to the Past,1991,SNES,2023-08-22,vgc Sonic the Hedgehog,1991,Genesis,2024-01-05,used注意,id字段不需要提供,数据库会自动生成。date_acquired和condition字段必须符合格式要求。
然后执行导入命令:
python main.py import-games --input new_games.csv程序会读取CSV的每一行,尝试插入数据库。这里有一个关键细节:导入过程是“原子性”的。这意味着,如果CSV中某一行数据格式错误(比如日期不对),整个导入操作会回滚,不会插入任何一条记录。这防止了数据库出现部分更新的混乱状态。你需要在修正CSV文件后重新执行导入。
3.2.3 查询与列表:多种视角浏览收藏
查看收藏有多种方式,满足不同场景需求。
列出所有游戏(默认按ID排序):
python main.py list-games按特定平台筛选:
python main.py list-games --platform "PS1"按成色筛选,并格式化输出为表格:
python main.py list-games --condition mint --format table--format参数支持table(美观的表格,适合终端查看)和csv(纯CSV格式,适合管道传递给其他工具)两种输出格式。
更复杂的组合查询(例如,找出所有1990年前发行的、成色在vgc以上的NES游戏):目前CLI工具不支持多条件高级筛选。但你可以通过导出CSV后,在电子表格软件中筛选,或者直接使用SQLite命令行工具查询数据库:
sqlite3 retro_games.db "SELECT * FROM games WHERE platform='NES' AND release_year < 1990 AND condition IN ('mint', 'vgc');"这揭示了CLI工具和底层数据库的互补性:CLI处理常见操作,复杂查询直达数据库。
3.3 管理员用户管理
在启动API的写操作保护前,你需要先创建至少一个管理员用户。
添加管理员:
python main.py add-admin --username luke --password mySecurePass123这个命令会在后台调用hash_password函数,将mySecurePass123转换为一个长长的、安全的哈希字符串(如$2b$12$...)存入数据库。请务必使用强密码。
列出所有管理员(仅显示用户名):
python main.py list-admins移除管理员:
python main.py remove-admin --username luke移除操作需要谨慎,因为一旦移除,该用户将无法再通过API进行认证。
踩坑实录:密码输入的安全与便利在早期版本中,
add-admin命令的--password参数是明文在命令行中传递的,这有安全风险,因为密码可能会留在shell历史记录中。改进方案是采用交互式提示输入密码,或者像ssh-keygen那样不显示输入字符。在当前的实现中,为了简化,我们仍然使用命令行参数,但在实际生产环境或个人使用中,更安全的做法是:1)通过环境变量传递密码;2)使用getpass库进行交互式隐藏输入。这是一个权衡,我为了演示的清晰性暂时保留了命令行参数方式,你在实际部署时务必修改此处。
4. REST API构建与接口详解
API部分位于retro-games-api/目录,基于FastAPI框架构建。FastAPI以其高性能、自动生成交互式文档(Swagger UI)和强大的数据验证而闻名。
4.1 快速启动API服务
首先,在另一个终端窗口(或标签页)中,设置API环境。
# 假设项目根目录是 retro-games cd retro-games-api # 创建并激活虚拟环境(同样重要,可与CLI共用,但独立更干净) python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # 安装依赖 # requirements.txt 通常包含:fastapi, uvicorn[standard], pydantic, sqlite3 pip install -r requirements.txt # 启动开发服务器 uvicorn app:app --reload--reload参数使得你在修改代码后服务器会自动重启,非常适合开发。启动后,你会看到类似Uvicorn running on http://127.0.0.1:8000的输出。
现在,打开浏览器,访问http://127.0.0.1:8000/docs。你会看到一个自动生成的、完整的API交互式文档(Swagger UI)。所有可用的端点、参数、请求体格式都一目了然,你甚至可以直接在页面上点击“Try it out”按钮来测试API调用,无需额外工具。这是FastAPI带来的巨大开发体验提升。
4.2 核心端点设计与使用
API遵循RESTful设计原则,对/games资源提供了完整的CRUD操作。
4.2.1 数据模型与验证
在app.py或models.py中,我们使用Pydantic模型来定义数据的结构和验证规则。
from pydantic import BaseModel, Field, validator from datetime import date from typing import Optional class GameBase(BaseModel): title: str = Field(..., min_length=1, description="游戏标题,不能为空") release_year: Optional[int] = Field(None, ge=1970, le=date.today().year, description="发行年份") platform: str = Field(..., min_length=1, description="游戏平台,如NES, SNES") date_acquired: date = Field(default_factory=date.today, description="入手日期,默认为今天") condition: str = Field(..., description="成色等级") @validator('condition') def validate_condition(cls, v): allowed_conditions = ['mint', 'vgc', 'gc', 'used'] if v not in allowed_conditions: raise ValueError(f'condition must be one of {allowed_conditions}') return v class GameCreate(GameBase): pass # 创建时使用基类即可 class Game(GameBase): id: int class Config: orm_mode = True # 允许从SQLAlchemy ORM对象转换GameBase模型定义了游戏数据的核心字段和验证逻辑:title和platform不能为空;release_year是可选的,但必须在合理范围内;condition通过自定义验证器确保是四个有效值之一。Game模型在GameBase基础上增加了id字段,用于响应数据。
4.2.2 查询所有游戏与条件筛选
端点:GET /games
这是最常用的端点。你可以通过查询参数进行筛选。
# 使用curl命令测试(或在Swagger UI中操作) # 获取所有游戏 curl -X 'GET' 'http://127.0.0.1:8000/games' -H 'accept: application/json' # 按平台筛选 curl -X 'GET' 'http://127.0.0.1:8000/games?platform=SNES' -H 'accept: application/json' # 组合筛选:平台为NES且成色为mint或vgc curl -X 'GET' 'http://127.0.0.1:8000/games?platform=NES&condition=mint&condition=vgc' -H 'accept: application/json'注意最后一个例子中,condition参数可以传递多次,以实现“或”逻辑(IN查询)。在FastAPI中,这通过List[str]类型的参数来实现:condition: Optional[List[str]] = Query(None)。
API的响应是JSON格式的数组,包含了所有匹配的游戏对象,每个对象都包含id,title,release_year等字段。
4.2.3 创建、更新与删除游戏(受保护端点)
根据项目TODO,创建、更新和删除端点计划使用JWT进行保护。目前,我们以实现基础功能为主。假设这些端点暂时开放(注意:在生产环境中,务必先实现认证!)。
创建游戏 (POST /games):
curl -X 'POST' 'http://127.0.0.1:8000/games' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "title": "Final Fantasy VI", "release_year": 1994, "platform": "SNES", "date_acquired": "2023-11-30", "condition": "gc" }'如果成功,API将返回创建的游戏对象(包含新生成的id),状态码为201。
更新游戏 (PUT /games/{id}):
curl -X 'PUT' 'http://127.0.0.1:8000/games/5' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "title": "Final Fantasy III (US)", # 更新标题 "condition": "vgc" # 更新成色,其他字段保持不变 }'PUT通常用于替换整个资源,但我们的实现可以设计为只更新提供的字段(部分更新)。这需要在后端逻辑中处理。
删除游戏 (DELETE /games/{id}):
curl -X 'DELETE' 'http://127.0.0.1:8000/games/5' -H 'accept: application/json'成功删除将返回204 No Content状态码。
4.3 数据库连接与依赖注入
FastAPI的依赖注入系统让数据库会话管理变得优雅。我们通常会在一个dependencies.py文件中创建数据库依赖。
# retro-games-api/dependencies.py import sqlite3 from fastapi import Depends, HTTPException, status DATABASE_URL = "sqlite:///./retro_games.db" # 注意路径,这里指向项目根目录的同级db文件 # 一个简单的依赖项,为每个请求提供数据库连接 def get_db_connection(): # SQLite连接需要绝对路径或正确相对路径 # 假设API和CLI共用数据库,且数据库在项目根目录 import os db_path = os.path.join(os.path.dirname(__file__), '..', 'retro_games.db') conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row # 这样fetchone()返回的是字典式对象,便于操作 try: yield conn finally: conn.close() # 在路由中使用 from fastapi import APIRouter, Depends import sqlite3 router = APIRouter() @router.get("/games") def read_games(platform: Optional[str] = None, conn: sqlite3.Connection = Depends(get_db_connection)): cursor = conn.cursor() if platform: cursor.execute("SELECT * FROM games WHERE platform = ?", (platform,)) else: cursor.execute("SELECT * FROM games") games = cursor.fetchall() # 将sqlite3.Row对象转换为字典列表 return [dict(game) for game in games]通过Depends(get_db_connection),每个请求都会获得一个独立的数据库连接,并在请求结束后自动关闭,避免了连接泄露。
5. 开发工具实战:Cursor与GitHub Copilot的化学反应
这个项目的开发过程,也是我深度体验现代AI编程助手的一次实践。我主要使用Cursor编辑器,并集成了GitHub Copilot。
5.1 从自然语言描述到代码生成
在构建数据模型和API端点时,Copilot的表现令人印象深刻。例如,当我输入注释:
# Define a Pydantic model for a Game with fields: id, title, release_year (optional), platform, date_acquired (default today), condition (must be one of mint, vgc, gc, used)然后按下Ctrl+I(Cursor的Copilot指令快捷键),它几乎能瞬间生成完整的GameBase和Game类,包括字段类型、默认值和验证器的大致框架。我只需要微调一下验证逻辑和描述文本。
对于重复性的CRUD路由代码,Copilot更是节省了大量时间。写完一个GET /games的端点后,在下面新建一个POST /games端点,刚打出@router.post,Copilot就能自动补全整个函数结构、参数解析和数据库插入语句的模板。
5.2 代码解释与安全审计
对于不熟悉的库(比如bcrypt的最佳实践),或者自己写的复杂逻辑,我可以直接选中代码块,在Cursor中通过Cmd+K(或右键菜单)打开AI指令面板,输入“解释这段代码”或“这段密码哈希代码安全吗?”。Copilot会给出清晰的解释,并指出潜在问题,比如提醒我“密码在命令行参数中可能不安全”,这直接促使我思考并添加了前面提到的安全注意事项。
5.3 错误排查与智能建议
在调试一个数据库查询错误时,错误信息显示“sqlite3.OperationalError: no such column: platform”。我将错误信息粘贴到Cursor的聊天框中,并附上相关的路由函数代码。Copilot很快分析出,可能是我在SELECT语句中拼错了字段名,或者数据库表结构没有更新。它建议我检查表结构,并提醒我如果修改了模型,可能需要运行数据库迁移(虽然SQLite的迁移比较手动)。这比单纯搜索错误信息要高效得多。
实操心得:AI辅助编程的定位Cursor和Copilot不是替代思考的“魔法棒”,而是强大的“副驾驶”。它们极大地提升了编写样板代码、探索新API和排查简单错误的效率。但核心的业务逻辑设计、架构决策、安全边界,仍然需要开发者自己牢牢把握。例如,关于JWT认证如何设计、令牌刷新机制、哪些端点需要保护,这些需要综合考量的部分,AI可以提供代码片段参考,但最终的方案必须由你自己决定并理解其原理。
6. 项目扩展与TODO实现思路
根据项目TODO列表,最大的待办事项是实现API的JWT认证。这里我分享一下我的实现思路,你可以在此基础上继续开发。
6.1 JWT认证流程设计
登录端点 (
POST /auth/login):- 接收
username和password。 - 查询
admins表,验证用户名和密码(使用verify_password函数)。 - 如果验证成功,使用一个密钥(
SECRET_KEY)和算法(如HS256)生成一个JWT令牌。令牌的有效载荷(Payload)应包含管理员ID和用户名等信息。 - 将令牌返回给客户端。通常放在响应体的
access_token字段中。
- 接收
受保护端点:
- 在需要保护的端点(
POST /games,PUT /games/{id},DELETE /games/{id})上添加一个依赖项,例如get_current_user。 - 这个依赖项会从请求头(通常是
Authorization: Bearer <token>)中提取JWT令牌。 - 验证令牌的签名和有效期。
- 如果有效,从令牌负载中解析出用户信息,并注入到路由函数中;如果无效,则抛出
HTTPException(status_code=401, detail="无效或过期的令牌")。
- 在需要保护的端点(
令牌刷新(可选但推荐):
- 可以设计一个
/auth/refresh端点,使用一个有效期更长的刷新令牌来获取新的访问令牌,提升用户体验。
- 可以设计一个
6.2 代码结构建议
在retro-games-api目录下创建新的模块:
auth.py:包含密码验证、JWT令牌创建与验证、生成哈希密码等函数。dependencies.py:扩展原有的依赖项,增加get_current_user等认证依赖。routers/auth.py:专门处理认证相关的路由(登录、刷新、登出)。routers/games.py:游戏相关的路由,从app.py中分离出来,使结构更清晰。
6.3 安全性增强建议
- 环境变量管理:将
SECRET_KEY、数据库路径等敏感信息从代码中移除,使用.env文件和环境变量管理(例如python-dotenv库)。 - HTTPS:在生产环境部署时,务必使用HTTPS来加密传输数据,防止令牌被窃听。
- 输入验证:FastAPI的Pydantic已经做了很好的基础验证。对于更复杂的业务逻辑验证(如“发行年份不能晚于入手日期”),可以在路由函数或服务层添加。
- API限流:为了防止滥用,可以考虑为公开的
GET /games端点添加简单的限流(例如使用slowapi库)。
7. 部署与日常使用建议
7.1 数据库备份
你的游戏收藏数据是无价的。定期备份retro_games.db文件至关重要。
简单手动备份:
# 在项目根目录 cp retro_games.db retro_games_backup_$(date +%Y%m%d).db可以将此命令加入cron(Linux/macOS)或计划任务(Windows),实现自动每日/每周备份。
版本控制考虑:虽然不建议将数据库文件本身加入git,但你可以将数据库的模式(Schema)单独保存为一个.sql文件。当数据库结构变更时,导出并提交这个文件。
sqlite3 retro_games.db .schema > schema.sql7.2 API服务部署
对于个人使用,在本地机器上运行uvicorn开发服务器完全足够。如果你希望从家庭网络的其他设备(如手机、平板)访问,可以:
uvicorn app:app --host 0.0.0.0 --port 8000然后通过你的电脑的局域网IP(如http://192.168.1.100:8000)访问。注意:这仅在受信任的局域网内安全。
对于真正的“云”部署,可以考虑:
- VPS:在云服务器上安装Python环境,使用
nohup或systemd服务让API在后台运行。 - PaaS平台:如Railway、Fly.io或PythonAnywhere,它们简化了部署流程,通常提供免费的入门套餐。
- 容器化:使用Docker将CLI和API打包成镜像,部署到任何支持容器的环境,确保环境一致性。
7.3 日常使用工作流
我的典型使用流程是这样的:
- 录入新游戏:在实体游戏到手后,打开终端,使用CLI的
add-game命令快速录入。 - 定期整理:每季度一次,使用
export-games导出CSV,在表格软件中整体浏览,修正可能录入错误的信息,然后再import-games覆盖更新(注意备份原数据库)。 - 查询与分享:当朋友问起我有没有某个游戏时,我直接用
list-games --title "关键词"快速查找。未来API前端做好后,可以直接分享链接给他们看我的收藏列表。 - 数据维护:偶尔通过CLI删除已出售或损坏的游戏记录。
这个由CLI和API组成的“数字档案员”,已经成了我复古游戏收藏 hobby 中不可或缺的一部分。它不仅仅是一个工具,更是将实体收藏与数字世界连接起来的桥梁。从一行命令开始,构建一个完全属于自己的管理系统,这个过程本身,就和玩复古游戏一样,充满了探索和创造的乐趣。