# 晨风书舍 — 项目完整规格文档

> 版本：V2.0 | 整理日期：2026-06-28 | 云端迁移版

---

## 一、项目概述

**晨风书舍**是一个家庭图书管理系统，部署为 `chenningbo.com/library`。核心工作流：

```
扫描 ISBN → 自动查询书目 + CLC 分类 → 5 秒倒计时确认 → 自动上架虚拟书架（云端持久化）
```

- **云端存储**：书籍数据存于服务器 SQLite，多设备实时同步
- **角色分离**：访客只读，管理员输密码后解锁写操作（密码仅当前标签页有效）
- **CLC 三级推断**：出版社映射（最准）→ 离线关键词 → DeepSeek AI 兜底
- **5 秒自动录入**：ISBN 扫描识别完成后倒计时自动保存，可提前确认或暂停修改

访问地址：`https://chenningbo.com/library`

---

## 二、技术栈

| 层 | 技术 | 说明 |
|:---|:---|:---|
| UI | HTML5 + CSS3 | 暗色主题，CSS Grid 书架，无框架 |
| 逻辑 | Vanilla JavaScript (ES2020+) | 无构建工具，单文件 |
| 存储 | 服务端 SQLite | 通过 REST API 访问，本地无状态 |
| 后端 | Python Flask (Blueprint) | `home_library_api.py` 挂载到 `chenningbo_api.py` |
| 运行 | systemd `chenningbo-api.service` | 端口 5099，开机自启 |
| 反代 | Nginx | `/library` → 静态 HTML；`/api/home-library/` → Flask |
| 分类 | 出版社映射 + 关键词引擎 + DeepSeek API | 三级推断，无 Key 时离线推断 |
| 封面 | OpenLibrary + 豆瓣 CORS | 两源回退，含占位图过滤 |
| 书目 API | 国图 OPAC 代理 + 豆瓣搜索代理 | 均在 chenningbo.com 同一 Flask 服务 |

---

## 三、文件结构

```
P22_HomeLibrary/
├── src/
│   └── 家庭图书馆.html          # 主程序（前端单文件，~2200+ 行）
├── release/
│   ├── 晨风书舍_V1.12.15.html
│   ├── 晨风书舍_V1.12.15_大改版前备份.html  # 迁移前安全备份
│   └── ... (历史版本)
├── home_library_api.py          # Flask Blueprint：图书 CRUD API
├── chenningbo_api.py            # Flask 主入口：组合 nlc_api + home_library_bp
├── PROJECT_SPEC.md              # 本文档
├── DEPLOY.md                    # 服务器部署操作记录
├── CHANGELOG.md                 # 版本更新日志
└── 工程记录.md                  # 早期架构记录
```

**服务器文件位置：**

| 文件 | 服务器路径 |
|:---|:---|
| 前端 HTML | `/var/www/chenningbo/library/index.html` |
| Flask 主入口 | `/var/www/chenningbo/api/chenningbo_api.py` |
| 图书 API Blueprint | `/var/www/chenningbo/api/home_library_api.py` |
| 国图/豆瓣代理 | `/var/www/chenningbo/api/nlc_api.py` |
| SQLite 数据库 | `/var/www/data/home_library.db` |
| systemd 服务 | `/etc/systemd/system/chenningbo-api.service` |
| Nginx 配置 | `/etc/nginx/sites-enabled/chenningbo.conf` |

---

## 四、服务端架构

### 4.1 Flask 服务组织

```
chenningbo_api.py (主入口，port 5099)
├── nlc_api.py 的路由（直接 import app 对象）
│   ├── GET  /api/isbn/<isbn>       国图 OPAC 代理，返回书目 + CLC
│   ├── GET  /api/douban/<isbn>     豆瓣搜索代理，返回书名/作者/出版社
│   └── GET  /api/search?q=书名    豆瓣书名搜索，用于无 ISBN 补全
└── home_library_bp（Blueprint）
    ├── GET  /api/home-library/books          全量获取书籍（公开）
    ├── POST /api/home-library/books          新增（管理员）
    ├── PUT  /api/home-library/books/<id>     更新（管理员）
    ├── DELETE /api/home-library/books/<id>   删除（管理员）
    ├── POST /api/home-library/books/bulk     批量覆盖写入，用于重排（管理员）
    ├── POST /api/home-library/import         全量导入，会清空现有数据（管理员）
    └── GET  /api/home-library/ping           健康检查 + 书籍总数（公开）
```

### 4.2 Nginx 路由

```nginx
location /library          → alias /var/www/chenningbo/library/  (静态 HTML)
location ^~ /api/home-library/  → proxy_pass http://127.0.0.1:5099
location ^~ /api/isbn/          → proxy_pass http://127.0.0.1:5099
location ^~ /api/douban/        → proxy_pass http://127.0.0.1:5099
location ^~ /api/search         → proxy_pass http://127.0.0.1:5099
```

### 4.3 systemd 服务

```ini
# /etc/systemd/system/chenningbo-api.service
ExecStart=/usr/bin/python3 /var/www/chenningbo/api/chenningbo_api.py
Environment=HOME_LIBRARY_TOKEN=<管理员密码>
Environment=HOME_LIBRARY_DB=/var/www/data/home_library.db
```

常用命令：
```bash
systemctl status chenningbo-api     # 查看状态
systemctl restart chenningbo-api    # 重启
journalctl -u chenningbo-api -f     # 实时日志
```

---

## 五、管理员权限设计

| 身份 | 操作 | 实现方式 |
|:---|:---|:---|
| 访客（默认） | 浏览、搜索、查看统计、导出 JSON | 无需任何操作 |
| 管理员 | 录入、编辑、删除、重排、修正、补封面、导入 | 点 🔒 → 输密码 → 变 🔓 |

**技术实现：**
- 密码存于 `sessionStorage`（key: `hl_admin_token`），关标签页自动清除
- 所有写操作请求头携带 `X-Admin-Token: <密码>`
- 服务端 `require_admin` 装饰器校验，失败返回 401
- 管理员按钮初始 `class="hidden"`，调用 `applyAdminUI()` 根据登录状态切换显示
- 修改密码：编辑 `/etc/systemd/system/chenningbo-api.service` 中 `HOME_LIBRARY_TOKEN`，重启服务

---

## 六、书架规则

### 6.1 物理结构

```
15 列（G1-G15）× 9 层（L1-L9）= 135 个格子

列分工：
  G1-G13   CLC A→Z 自动分配区（主书区）
  G14      儿童读物（固定列）
  G15      相册（固定列）

层分工：
  L1-L2    保留层（虚线显示）
  L3-L8    活跃层（6 层 × 13 列 = 78 格）
  L9       底层收藏区（标签可编辑）

每格容量：15 本
活跃容量：78 格 × 15 本 = 1170 本
```

### 6.2 位置编号格式

```
G{列}-L{层}-{位置}
例：G03-L05-12 = 第 3 列 · 第 5 层 · 第 12 本
```

---

## 七、CLC 分类系统

### 7.1 三级推断策略（V2.0 新增）

```
优先级 1（最准）：出版社精确映射
  人民文学出版社 → I，机械工业出版社 → T，法律出版社 → D ...
  覆盖 50+ 家主要出版社，命中率接近 100%

优先级 2：离线关键词匹配
  14 个分类各有 30-40 个关键词，书名/作者综合打分
  准确率约 75-85%

优先级 3：DeepSeek AI
  需要用户在页面设置 API Key（🤖 按钮）
  准确率约 90%，用于前两级无法匹配的书
```

### 7.2 22 大类

| 代码 | 类别 | 颜色 |
|:---|:---|:---|
| A | 马列/毛邓 | `#D32F2F` |
| B | 哲学/宗教 | `#7B1FA2` |
| C | 社科总论 | `#E64A19` |
| D | 政治/法律 | `#455A64` |
| E | 军事 | `#5D4037` |
| F | 经济/管理 | `#F9A825` |
| G | 文化/教育 | `#388E3C` |
| H | 语言/文字 | `#0288D1` |
| I | 文学 | `#C62828` |
| J | 艺术 | `#E91E63` |
| K | 历史/地理 | `#795548` |
| N | 自然科学 | `#00897B` |
| O | 数理化 | `#1565C0` |
| P | 天文/地球 | `#00ACC1` |
| Q | 生物科学 | `#43A047` |
| R | 医药/卫生 | `#EC407A` |
| S | 农业科学 | `#827717` |
| T | 工业技术 | `#1E88E5` |
| U | 交通运输 | `#6D4C41` |
| V | 航空/航天 | `#3949AB` |
| X | 环境科学 | `#2E7D32` |
| Z | 综合图书 | `#757575` |
| 儿童 | 儿童读物（扩展） | `#FF9800` |
| 相册 | 相册（扩展） | `#9C27B0` |

---

## 八、数据模型

### 8.1 书籍对象（前端 JS）

```javascript
{
  id:         Number,   // 服务端自增主键
  isbn:       String,   // ISBN（可为空）
  title:      String,   // 书名 *必填
  author:     String,
  publisher:  String,
  clcMain:    String,   // CLC 大类代码
  clcCode:    String,   // 同 clcMain（历史冗余字段）
  grid:       Number,   // 列号 1-15
  layer:      Number,   // 层号 1-9
  position:   Number,   // 格内序号 1-15
  location:   String,   // "G03-L05-12"
  addedAt:    String,   // "YYYY-MM-DD"
  cover:      String,   // 封面图 URL
  note:       String,
  corrected:  Boolean,  // 批量修正标记
}
```

### 8.2 SQLite 表结构

```sql
CREATE TABLE books (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    isbn        TEXT,
    title       TEXT NOT NULL,
    author      TEXT,
    publisher   TEXT,
    clc_main    TEXT,
    clc_code    TEXT,
    grid        INTEGER,
    layer       INTEGER,
    position    INTEGER,
    location    TEXT,
    added_at    TEXT,
    cover       TEXT,
    note        TEXT,
    corrected   INTEGER DEFAULT 0
);
```

字段名映射：DB 用下划线（`clc_main`、`added_at`），前端用驼峰（`clcMain`、`addedAt`），`home_library_api.py` 的 `_row_to_dict()` / `_dict_to_row()` 负责转换。

### 8.3 localStorage 使用

| Key | 内容 |
|:---|:---|
| `deepseek_api_key` | DeepSeek API Key |
| `cangshuge_custom` | L9 各格自定义标签 JSON |

---

## 九、核心函数速查

| 函数 | 作用 |
|:---|:---|
| `init()` | 启动：填充 CLC 下拉 → apiGet → renderShelf → applyAdminUI → checkConnection |
| `apiGet()` | GET /books → allBooks[] |
| `apiAdd(book)` | POST /books → {id, ...book} |
| `apiUpdate(book)` | PUT /books/:id |
| `apiDelete(id)` | DELETE /books/:id |
| `apiBulk(books)` | POST /books/bulk（重排专用） |
| `apiImport(books)` | POST /import（全量替换） |
| `refreshData()` | 重新拉取 allBooks → 重建索引 → renderShelf |
| `renderShelf()` | 全量渲染书架 HTML |
| `buildCellPlan()` | 按当前书量运行比例分配 |
| `proportionalAlloc(classes, total)` | 两步比例分配核心算法 |
| `rebuildLayout()` | 重排：重新分配所有书位置 → apiBulk |
| `fetchBookByISBN(isbn)` | 5 源串行查询，填表单，触发倒计时 |
| `recommendCLC(title, author, text, publisher)` | 三级推断：出版社映射 → 关键词 → null |
| `autoRecommendCLC(doubanText)` | 读取表单值 → recommendCLC → 填推荐徽章 |
| `classifyWithDeepSeek(title, author, desc)` | DeepSeek API 推断 CLC |
| `startAutoSaveCountdown(secs)` | 启动倒计时，5s 后自动 saveBook() |
| `clearAutoSaveCountdown()` | 取消倒计时，按钮恢复「确认录入」 |
| `scheduleAutoSave()` | 书名+CLC 齐全时调用 startAutoSaveCountdown(5) |
| `saveBook()` | 写入/更新书籍（apiAdd/apiUpdate），新书分配位置 |
| `searchByTitle(explicit)` | 书名搜索自动补全（无 ISBN 时用），触发倒计时 |
| `applyAdminUI()` | 根据 isAdmin() 切换管理员按钮显示/隐藏 |
| `toggleAdmin()` | prompt 输密码 → sessionStorage → applyAdminUI |
| `checkConnection()` | ping 服务器，更新 connStatus 指示器 |
| `batchCorrect()` | 批量用国图 API 重新核对 CLC |
| `batchFetchCovers()` | 批量补封面（8本一批并行） |
| `exportData()` | 导出 JSON 备份（无需管理员） |
| `importData(file)` | 导入 JSON → apiImport（需管理员） |
| `openStats()` | 统计面板（甜甜圈+排行榜+KPI） |
| `showToast(msg, type)` | 底部 toast（2.5s） |

---

## 十、ISBN 查询链路

```
扫描 ISBN
 │
 ├─ [1] 国图 OPAC 代理  /api/isbn/{isbn}        5s（常被阿里云屏蔽，快速失败）
 │      ✅ 返回准确 CLC + 书名/作者/出版社
 │
 ├─ [1.5] 豆瓣服务端代理  /api/douban/{isbn}    5s  ← 主力来源
 │      ✅ 返回书名/作者/出版社 → 出版社映射推断 CLC → 或 DeepSeek
 │
 ├─ [2] 豆瓣 CORS 代理（cors.zme.ink 等）       8s
 │      ✅ 书名/作者/封面 → 推断 CLC
 │
 ├─ [3] OpenLibrary                             4s
 │
 ├─ [4] Google Books                            3s（国内不通）
 │
 └─ [全失败] 提示手动填写，可用 🔍 书名搜索
```

**注意**：国图 OPAC（opac.nlc.cn）从阿里云服务器无法访问（被屏蔽），超时设为 5 秒快速跳过。豆瓣代理实际上是主要书目来源。

---

## 十一、书名搜索（无 ISBN）

点 🔍 或 Tab 离开书名栏后触发：

```
1. GET /api/search?q=书名  → 豆瓣搜索，返回书名/作者/出版社
2. 豆瓣 CORS 代理兜底
3. 填入作者、出版社
4. 出版社映射 → CLC；失败则 DeepSeek
5. 信息齐全 → 触发 5 秒倒计时
```

---

## 十二、书架分配算法

### 12.1 两步比例分配（`proportionalAlloc`）

```
步骤一（保底）：每个有书的 CLC 类 → ceil(count / 15) 格
步骤二（缓冲）：剩余格数按各类录入量占比分配，小数部分最大者优先
```

### 12.2 重排算法（`rebuildLayout`）

```
1. 统计各 CLC 类当前书数
2. 两步比例分配 → classes[].allocCells
3. A→Z 顺序，逐格独占分配
4. 儿童 → G14，相册 → G15（固定）
5. 同类书：中文作者优先，再按拼音排序
6. 批量写入服务端（apiBulk）→ 刷新书架
```

---

## 十三、UI 组件

### 13.1 顶部工具栏

| 控件 | 可见性 | 功能 |
|:---|:---|:---|
| SVG Logo（开卷+晨曦）| 始终 | 品牌标识 |
| 连接状态指示器 | 始终 | 显示服务器连接状态 |
| 统计数字 | 始终 | 总藏书/已用格 |
| 搜索框 | 始终 | 实时下拉搜索 |
| + 录入 | 管理员 | 打开录入弹窗 |
| 重排 | 管理员 | 重排预览弹窗 |
| 修正 | 管理员 | 批量国图校对 CLC |
| 补封面 | 管理员 | 批量抓取封面 |
| 导入 | 管理员 | 导入 JSON 全量替换 |
| 🔒/🔓 | 始终 | 管理员登录/退出 |
| 导出 | 始终 | 下载 JSON 备份 |
| 统计 | 始终 | 统计面板 |
| 🤖 | 始终 | DeepSeek Key 设置 |

### 13.2 录入弹窗倒计时

ISBN 扫描成功且书名/CLC 都填写后：
- `btnSave` 变为 `✓ 录入 (5s)`，底部有收缩进度条
- 5 秒后自动调用 `saveBook()`
- 点「✓ 录入」→ 立即保存
- 点「取消」→ 停止倒计时，弹窗保留供修改，再手动点确认

---

## 十四、已知限制

| # | 问题 | 影响 | 状态 |
|:---|:---|:---|:---|
| 1 | 国图 OPAC 从阿里云被屏蔽 | CLC 最准来源不可用，回落出版社映射+AI | 已知，改为 5s 快速失败 |
| 2 | 国图 API 单 IP 约 12 次/日 | 批量修正受限 | 已知，低优先级 |
| 3 | 全量 DOM 重建（1000+本约 200ms） | 大库略卡 | 待优化 |
| 4 | Google Books 国内不通 | 第 5 备用源失效 | 低优先级 |
| 5 | `clcCode` 字段冗余 | 与 `clcMain` 相同，历史遗留 | 待清理 |

---

## 十五、版本历史

| 版本 | 日期 | 关键里程碑 |
|:---|:---|:---|
| **V2.0** | 2026-06-28 | 🏗️ **云端迁移**：IndexedDB → 服务端 SQLite；REST API；角色权限；新 Logo；5s 倒计时；出版社→CLC 映射 |
| V1.12.15 | 2026-06-02 | OpenLibrary `?default=false` 根治占位图 |
| V1.12.7 | 2026-06-01 | 稳定版：比例分配完整实现 |
| V1.12.0 | 2026-06-01 | 统计面板重设计 |
| V1.11.6 | 2026-06-01 | 层规则修正（L1-L2 保留，L9 杂志区） |
| V1.9.x | 2026-05-31 | 早期功能探索 |

---

## 十六、待办（下次迭代候选）

- [ ] 搜索结果显示书架位置（G03-L05）
- [ ] 删书二次确认弹窗
- [ ] 操作日志（最近 20 条，localStorage）
- [ ] 增量 DOM 更新（大库性能优化）
- [ ] 分类筛选（按 CLC 大类过滤书架）
- [ ] `clcCode` 字段合并到 `clcMain`（清理冗余）
- [ ] 旧数据从另一台电脑导入（需导出 JSON 后通过导入功能上传）
