# donki **Repository Path**: MineLX/donki ## Basic Information - **Project Name**: donki - **Description**: Anki's best friend. - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-02 - **Last Updated**: 2026-09-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Donki | FSRS 间隔重复(Spaced Repetition) [ts-fsrs](https://github.com/open-spaced-repetition/ts-fsrs) 驱动的卡片复习系统,由两部分组成: - `server/` —— Node.js + ts-fsrs + MongoDB 的后端服务(提供 RPC 方法) - `android/` —— Kotlin + Jetpack Compose 的 Android 客户端(今日复习、牌组查看/创建/组织,可配置服务器地址) ## 数据流 ``` Android App ── HTTP POST /rpc (JSON) ──> donki server (Express) │ 业务方法 (handler.js) │ ts-fsrs 调度 (srs.js) ▼ MongoDB (或内存存储) ``` 协议统一为: ```json 请求 { "action": "addCard", "version": 1, "params": { ... } } 响应 { "result": { ... }, "error": null | "错误信息" } ``` ## 服务器 ### 运行 ```bash cd server npm install # 方式一:使用 MongoDB(默认读取 .env.example 中的连接串) node index.js # 或: DONKI_MONGODB_URI=<连接串> node index.js # 方式二:无数据库,内存存储(本地调试) npm run memory # DONKI_USE_MEMORY=1 node index.js npm run smoke # 跑一遍业务方法冒烟测试 ``` 环境变量:`DONKI_MONGODB_URI`、`DONKI_MONGO_DB`、`DONKI_PORT`(默认 3000)、`DONKI_USE_MEMORY`。 ### RPC 方法一览 | action | params | 说明 | |---|---|---| | `addCard` | `{ card: { deckName, fields, tags } }` | 新增卡片(FSRS 初始状态),自动建牌组 | | `createDeck` | `{ name, parentName? }` | 新建牌组(支持 `parentName::name` 嵌套) | | `getDeckNames` | - | 牌组列表 `{id,name}[]` | | `getDeckTree` | - | 牌组树(含各自/汇总的 total、new、due 计数),用于组织 | | `getCard` | `{ cardId }` | 卡片详情(含记忆率 retrievability) | | `getCardsForDeck` | `{ deckId 或 deckName }` | 某牌组内卡片列表 | | `deleteCard` / `deleteDeck` | `{ cardId }` / `{ deckId }` | 删除 | | `updateCard` | `{ cardId, fields?, tags?, cardType?, deckName? }` | 修改卡片 | | `getToday` | `{ deckName?, limit? }` | **今日复习**:到期卡 `review[]` + 新卡 `newCards[]` | | `reviewCard` | `{ cardId, rating }` | 提交打分(1 Again/2 Hard/3 Good/4 Easy),FSRS 重排 | | `getRetrievability` | `{ cardId }` | 当前记忆率 0~1 | | `forgetCard` | `{ cardId }` | 重置为“新卡” | | `getStats` | - | 全局统计 | ### 卡片类型(cardType) `addCard` 的 `card.cardType` 控制展示方式(默认 `"basic"`): - `"basic"`(正反面):`fields.Front` 为问题,`fields.Back` 为答案,复习时先翻面确认再打分。 - `"single"`(单面):只有一面内容,`fields.Front` 即为要记忆的内容(可不填 Back),复习时**直接显示内容并打分**,不再翻面。 例如新增一张单面卡片: ```json { "action": "addCard", "version": 1, "params": { "card": { "cardType": "single", "deckName": "口语", "fields": { "Front": "把这个句子记住就够了。" }, "tags": ["single"] } } } ``` 所有与 `getCard`/`getToday`/`reviewCard` 相关的响应都会返回 `cardType` 字段,客户端据此决定复习流程。 ### 调用示例 ```json curl -s -X POST localhost:3000/rpc -H 'Content-Type: application/json' -d '{ "action": "addCard", "version": 1, "params": { "card": { "deckName": "编程::HTTP", "fields": { "Front": "HTTP 请求是什么?", "Back": "一种客户端和服务器之间进行通信的协议。" }, "tags": ["api", "automation"] } } }' ``` 字段值既可以是**纯文本**,也可以是媒体的 `{ "mime": "image/png", "data": "" }`(Android 端会将其渲染为图片)。 ## Android 端 - 编译好的 APK 位于:`/workspace/apk/donki-debug.apk`(包名 `com.donki.app`,可直接安装)。 - 若需重新构建:打开 `android/` 目录,或用以下命令(HTTP 代理已在 `android/gradle.properties` 中配置): ```bash cd android export JAVA_HOME= # 必须 JDK 17 gradle assembleDebug # 产物: app/build/outputs/apk/debug/app-debug.apk ``` - 首次启动后在右上角 **设置** 中填写服务器地址前缀(**无需 `/rpc` 结尾**),例如: - Android 模拟器:`http://10.0.2.2:3000` - 真机(同一局域网):`http://<电脑IP>:3000` - 首页展示今日到期/新卡数量与牌组树;点牌组进入查看/添加卡片、新建子牌组;点“开始复习”进入闪卡流程(正面→显示答案→重来/困难/良好/简单)。 ## FSRS 集成说明 `server/src/srs.js` 封装 ts-fsrs: - 新卡片:`createEmptyCard(now)`,state=New,due=now。 - 复习:`fsrs().next(card, now, grade)` 返回新的 `{card, log}`,据此更新 due、stability、difficulty、state 等信息。 - 到期判定:非 New 且 `due <= now`(Learning/Review/Relearning)。 - 记忆率:`get_retrievability(card, now)`。 `server/tools/smoke.js` 覆盖上述完整流程,可作为回归测试。