mirror of
https://github.com/KingHWJ/WeReadBoard.git
synced 2026-11-03 18:24:58 +08:00
merge: merge dev into master - MySQL switch, Chinese comments, health check, docs
This commit is contained in:
commit
5e4de7a336
8 files changed
+1805
-436
No files matched your search
@@ -7,3 +7,21 @@
|
|||||||
- 前端页面:ECharts 图表(年度趋势柱状图、24h时段折线图、分类饼图、作者排行条形图)、书架网格(分页+筛选)、笔记概览
|
- 前端页面:ECharts 图表(年度趋势柱状图、24h时段折线图、分类饼图、作者排行条形图)、书架网格(分页+筛选)、笔记概览
|
||||||
- 数据规模:累计阅读 339 小时 57 分钟、304 天阅读、404 本书架藏书、1709 条笔记
|
- 数据规模:累计阅读 339 小时 57 分钟、304 天阅读、404 本书架藏书、1709 条笔记
|
||||||
- 技术栈:Python Flask + SQLite + ECharts + 原生 HTML/CSS/JS
|
- 技术栈:Python Flask + SQLite + ECharts + 原生 HTML/CSS/JS
|
||||||
|
|
||||||
|
## 功能迭代(第二批)
|
||||||
|
- Git 仓库:dev 分支初版提交 + 功能迭代提交,master 分支 --no-ff 合并,遵循 conventional commits 规范
|
||||||
|
- 书籍详情弹窗:点击书架书籍实时调 /book/info + /book/getprogress API,展示详细信息+阅读进度,数据入库 book_details/book_progress 表
|
||||||
|
- 笔记详情弹窗:点击笔记书籍实时调 /book/bookmarklist + /review/list/mine API,划线按章节分组+想法/点评,数据入库 book_highlights/book_reviews/book_chapters 表
|
||||||
|
- 推荐页面:个性化推荐(/book/recommend)+ 关键词搜索(/store/search),数据入库 recommended_books/search_results 表
|
||||||
|
- 修复 Flask 中文 URL 参数编码问题(latin-1 -> utf-8 转码)
|
||||||
|
- MySQL 版本建表语句 schema_mysql.sql:18 张表完整 DDL,utf8mb4 字符集,含索引和字段注释
|
||||||
|
- 新增 7 张按需数据表:book_details, book_progress, book_highlights, book_chapters, book_reviews, recommended_books, search_results
|
||||||
|
|
||||||
|
## MySQL 迁移
|
||||||
|
- 启动本地 MySQL 5.7.31(通过 launchctl load com.oracle.oss.mysql.mysqld.plist)
|
||||||
|
- MySQL root 密码:258963
|
||||||
|
- 创建数据库 `weread_dashboard`(utf8mb4),执行 schema_mysql.sql 创建 18 张表
|
||||||
|
- 迁移脚本 `migrate_to_mysql.py`:SQLite -> MySQL 全量迁移,共 862 行数据
|
||||||
|
- 时间戳列自动转换(SQLite int -> MySQL TIMESTAMP)
|
||||||
|
- 中文内容 utf8mb4 完整保存,数据校验通过
|
||||||
|
- Flask 服务暂未切换至 MySQL(用户要求服务不重启)
|
||||||
@@ -0,0 +1,524 @@
|
|||||||
|
# 微信读书数据展示网站 - 元数据文档
|
||||||
|
|
||||||
|
> 版本:1.2.0 | 更新时间:2026-07-17 | 数据库:MySQL weread_dashboard
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 数据库概览
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 数据库名称 | weread_dashboard |
|
||||||
|
| 字符集 | utf8mb4 |
|
||||||
|
| 排序规则 | utf8mb4_unicode_ci |
|
||||||
|
| 存储引擎 | InnoDB |
|
||||||
|
| 表数量 | 18 |
|
||||||
|
| 数据总量 | 862 行 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 表分类
|
||||||
|
|
||||||
|
| 分类 | 表名 | 说明 | 数据来源 |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| **基础数据** | books | 书架书籍 | 采集脚本 |
|
||||||
|
| **基础数据** | reading_stats | 阅读统计概要 | 采集脚本 |
|
||||||
|
| **基础数据** | read_times | 阅读时长时间序列 | 采集脚本 |
|
||||||
|
| **基础数据** | read_longest | 阅读时长排行 | 采集脚本 |
|
||||||
|
| **基础数据** | prefer_categories | 偏好分类 | 采集脚本 |
|
||||||
|
| **基础数据** | prefer_authors | 偏好作者 | 采集脚本 |
|
||||||
|
| **基础数据** | prefer_time | 24h 时段分布 | 采集脚本 |
|
||||||
|
| **基础数据** | notebooks | 笔记本概览 | 采集脚本 |
|
||||||
|
| **基础数据** | read_stat_items | 统计摘要项 | 采集脚本 |
|
||||||
|
| **基础数据** | archives | 书单归档 | 采集脚本 |
|
||||||
|
| **基础数据** | fetch_meta | 采集元信息 | 采集脚本 |
|
||||||
|
| **按需数据** | book_details | 书籍详情 | 用户点击时获取 |
|
||||||
|
| **按需数据** | book_progress | 阅读进度 | 用户点击时获取 |
|
||||||
|
| **按需数据** | book_highlights | 划线内容 | 用户点击时获取 |
|
||||||
|
| **按需数据** | book_chapters | 章节信息 | 用户点击时获取 |
|
||||||
|
| **按需数据** | book_reviews | 想法/点评 | 用户点击时获取 |
|
||||||
|
| **按需数据** | recommended_books | 推荐书籍 | 用户浏览推荐时获取 |
|
||||||
|
| **按需数据** | search_results | 搜索结果 | 用户搜索时获取 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 表结构详情
|
||||||
|
|
||||||
|
### 3.1 books - 书架书籍表
|
||||||
|
|
||||||
|
> 存储用户书架中的所有书籍基本信息
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| book_id | VARCHAR(64) | 是 (PK) | - | 书籍唯一标识 |
|
||||||
|
| title | VARCHAR(255) | 是 | '' | 书名 |
|
||||||
|
| author | VARCHAR(255) | 否 | NULL | 作者 |
|
||||||
|
| cover | VARCHAR(512) | 否 | NULL | 封面图 URL |
|
||||||
|
| category | VARCHAR(128) | 否 | NULL | 分类(如"文学-古代诗词") |
|
||||||
|
| finish_reading | TINYINT(1) | 是 | 0 | 是否读完 (1=读完) |
|
||||||
|
| read_update_time | INT UNSIGNED | 否 | NULL | 最近阅读时间 (Unix 时间戳) |
|
||||||
|
| secret | TINYINT(1) | 是 | 0 | 是否私密 (1=私密) |
|
||||||
|
| is_top | TINYINT(1) | 是 | 0 | 是否置顶 (1=置顶) |
|
||||||
|
| update_time | INT UNSIGNED | 否 | NULL | 书籍更新时间 (Unix 时间戳) |
|
||||||
|
| deep_link | VARCHAR(512) | 否 | NULL | 微信读书深度链接 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (book_id)
|
||||||
|
- idx_books_category (category)
|
||||||
|
- idx_books_finish_reading (finish_reading)
|
||||||
|
- idx_books_read_update_time (read_update_time DESC)
|
||||||
|
|
||||||
|
**记录数**:404
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 reading_stats - 阅读统计概要表
|
||||||
|
|
||||||
|
> 存储总体和年度的阅读统计数据
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| mode | VARCHAR(20) | 是 | - | 统计维度: overall/annually |
|
||||||
|
| period | VARCHAR(20) | 是 | '' | 周期标识: "overall" 或 "2026" |
|
||||||
|
| total_read_time | INT UNSIGNED | 是 | 0 | 总阅读时长 (秒) |
|
||||||
|
| read_days | INT UNSIGNED | 是 | 0 | 有效阅读天数 |
|
||||||
|
| day_average_read_time | DOUBLE | 否 | 0 | 日均阅读时长 (秒) |
|
||||||
|
| regist_time | INT UNSIGNED | 否 | NULL | 用户注册时间 (Unix 时间戳) |
|
||||||
|
| raw_json | LONGTEXT | 否 | NULL | 原始 API 返回 JSON |
|
||||||
|
| created_at | TIMESTAMP | 是 | CURRENT_TIMESTAMP | 记录创建时间 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_reading_stats_mode (mode, period)
|
||||||
|
|
||||||
|
**记录数**:2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 read_times - 阅读时长时间序列表
|
||||||
|
|
||||||
|
> 按年/月/日分桶的阅读时长数据
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| mode | VARCHAR(30) | 是 | - | 统计维度 (含 _daily 后缀) |
|
||||||
|
| period | VARCHAR(20) | 是 | '' | 周期标识 |
|
||||||
|
| bucket_timestamp | INT UNSIGNED | 是 | - | 分桶起始时间戳 |
|
||||||
|
| duration | INT UNSIGNED | 是 | 0 | 阅读时长 (秒) |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_read_times_mode_period (mode, period)
|
||||||
|
- idx_read_times_bucket (bucket_timestamp)
|
||||||
|
|
||||||
|
**记录数**:21
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 read_longest - 阅读时长排行表
|
||||||
|
|
||||||
|
> 阅读时间最久的书籍排名
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| mode | VARCHAR(20) | 是 | - | 统计维度 |
|
||||||
|
| period | VARCHAR(20) | 是 | '' | 周期标识 |
|
||||||
|
| book_id | VARCHAR(64) | 是 | - | 书籍 ID |
|
||||||
|
| title | VARCHAR(255) | 否 | NULL | 书名 |
|
||||||
|
| author | VARCHAR(255) | 否 | NULL | 作者 |
|
||||||
|
| cover | VARCHAR(512) | 否 | NULL | 封面图 URL |
|
||||||
|
| read_time | INT UNSIGNED | 是 | 0 | 阅读时长 (秒) |
|
||||||
|
| tags | VARCHAR(512) | 否 | NULL | 标签 (JSON 数组) |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_read_longest_mode (mode, period)
|
||||||
|
|
||||||
|
**记录数**:20
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 prefer_categories - 偏好分类表
|
||||||
|
|
||||||
|
> 用户阅读的书籍分类统计
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| mode | VARCHAR(20) | 是 | - | 统计维度 |
|
||||||
|
| period | VARCHAR(20) | 是 | '' | 周期标识 |
|
||||||
|
| category_id | INT UNSIGNED | 否 | NULL | 分类 ID |
|
||||||
|
| category_title | VARCHAR(128) | 否 | NULL | 分类名称 |
|
||||||
|
| parent_category_title | VARCHAR(128) | 否 | NULL | 父分类名称 |
|
||||||
|
| val | DOUBLE | 否 | 0 | 偏好权重 |
|
||||||
|
| reading_time | INT UNSIGNED | 否 | 0 | 该分类阅读时长 (秒) |
|
||||||
|
| reading_count | INT UNSIGNED | 否 | 0 | 该分类阅读本数 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_prefer_cat_mode (mode, period)
|
||||||
|
|
||||||
|
**记录数**:16
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.6 prefer_authors - 偏好作者表
|
||||||
|
|
||||||
|
> 用户阅读最多的作者统计
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| mode | VARCHAR(20) | 是 | - | 统计维度 |
|
||||||
|
| period | VARCHAR(20) | 是 | '' | 周期标识 |
|
||||||
|
| author_id | INT UNSIGNED | 否 | NULL | 作者 ID |
|
||||||
|
| name | VARCHAR(255) | 否 | NULL | 作者名称 |
|
||||||
|
| count | INT UNSIGNED | 否 | 0 | 阅读书本数 |
|
||||||
|
| read_time | VARCHAR(64) | 否 | NULL | 阅读时长 (格式化字符串) |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_prefer_author_mode (mode, period)
|
||||||
|
|
||||||
|
**记录数**:20
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.7 prefer_time - 24小时时段分布表
|
||||||
|
|
||||||
|
> 一天中每个小时的阅读时长分布
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| mode | VARCHAR(20) | 是 | - | 统计维度 |
|
||||||
|
| period | VARCHAR(20) | 是 | '' | 周期标识 |
|
||||||
|
| hour_index | TINYINT UNSIGNED | 是 | - | 小时 (0-23) |
|
||||||
|
| duration | INT UNSIGNED | 是 | 0 | 阅读时长 (秒) |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_prefer_time_mode (mode, period)
|
||||||
|
|
||||||
|
**记录数**:24
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.8 notebooks - 笔记本概览表
|
||||||
|
|
||||||
|
> 有笔记的书籍及其笔记统计
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| book_id | VARCHAR(64) | 是 (PK) | - | 书籍 ID |
|
||||||
|
| title | VARCHAR(255) | 否 | NULL | 书名 |
|
||||||
|
| author | VARCHAR(255) | 否 | NULL | 作者 |
|
||||||
|
| cover | VARCHAR(512) | 否 | NULL | 封面图 URL |
|
||||||
|
| review_count | INT UNSIGNED | 是 | 0 | 想法/点评数 |
|
||||||
|
| note_count | INT UNSIGNED | 是 | 0 | 划线数 |
|
||||||
|
| bookmark_count | INT UNSIGNED | 是 | 0 | 书签数 |
|
||||||
|
| reading_progress | INT UNSIGNED | 是 | 0 | 阅读进度 (0-100) |
|
||||||
|
| marked_status | TINYINT(1) | 是 | 0 | 标记状态 (1=读完) |
|
||||||
|
| sort | BIGINT | 否 | NULL | 排序值 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (book_id)
|
||||||
|
|
||||||
|
**记录数**:32
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.9 read_stat_items - 统计摘要项表
|
||||||
|
|
||||||
|
> App 首页展示的阅读统计摘要
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| mode | VARCHAR(20) | 是 | - | 统计维度 |
|
||||||
|
| period | VARCHAR(20) | 是 | '' | 周期标识 |
|
||||||
|
| stat_name | VARCHAR(64) | 否 | NULL | 统计项名称 (读过/读完/阅读/笔记) |
|
||||||
|
| counts | VARCHAR(64) | 否 | NULL | 统计值文案 (如"404本") |
|
||||||
|
| scheme | VARCHAR(512) | 否 | NULL | App 跳转链接 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_read_stat_items_mode (mode, period)
|
||||||
|
|
||||||
|
**记录数**:8
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.10 archives - 书单归档表
|
||||||
|
|
||||||
|
> 用户创建的书单
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| name | VARCHAR(255) | 否 | NULL | 书单名称 |
|
||||||
|
| book_ids | LONGTEXT | 否 | NULL | 书籍 ID 列表 (JSON 数组) |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
|
||||||
|
**记录数**:6
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.11 fetch_meta - 采集元信息表
|
||||||
|
|
||||||
|
> 记录数据采集的元信息
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| key | VARCHAR(64) | 是 (PK) | - | 元信息键名 |
|
||||||
|
| value | TEXT | 否 | NULL | 元信息值 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (key)
|
||||||
|
|
||||||
|
**记录数**:1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.12 book_details - 书籍详细信息表
|
||||||
|
|
||||||
|
> 来自微信读书 /book/info 接口,用户点击书籍时按需获取
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| book_id | VARCHAR(64) | 是 (PK) | - | 书籍 ID |
|
||||||
|
| title | VARCHAR(255) | 否 | NULL | 书名 |
|
||||||
|
| author | VARCHAR(255) | 否 | NULL | 作者 |
|
||||||
|
| translator | VARCHAR(255) | 否 | NULL | 译者 |
|
||||||
|
| cover | VARCHAR(512) | 否 | NULL | 封面图 URL |
|
||||||
|
| intro | LONGTEXT | 否 | NULL | 书籍简介 |
|
||||||
|
| category | VARCHAR(128) | 否 | NULL | 分类 |
|
||||||
|
| publisher | VARCHAR(255) | 否 | NULL | 出版社 |
|
||||||
|
| publish_time | VARCHAR(32) | 否 | NULL | 出版时间 |
|
||||||
|
| isbn | VARCHAR(32) | 否 | NULL | ISBN 号 |
|
||||||
|
| word_count | INT UNSIGNED | 否 | 0 | 总字数 |
|
||||||
|
| new_rating | DECIMAL(5,1) | 否 | 0 | 评分 (百分制) |
|
||||||
|
| new_rating_count | INT UNSIGNED | 否 | 0 | 评分人数 |
|
||||||
|
| fetched_at | TIMESTAMP | 是 | CURRENT_TIMESTAMP | 获取时间 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (book_id)
|
||||||
|
|
||||||
|
**记录数**:1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.13 book_progress - 阅读进度表
|
||||||
|
|
||||||
|
> 来自微信读书 /book/getprogress 接口
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| book_id | VARCHAR(64) | 是 (PK) | - | 书籍 ID |
|
||||||
|
| progress | TINYINT UNSIGNED | 是 | 0 | 阅读进度百分比 (0-100) |
|
||||||
|
| chapter_uid | INT UNSIGNED | 否 | NULL | 当前阅读章节 UID |
|
||||||
|
| chapter_offset | INT UNSIGNED | 否 | NULL | 当前章节内偏移量 |
|
||||||
|
| update_time | INT UNSIGNED | 否 | NULL | 最后阅读时间 (Unix 时间戳) |
|
||||||
|
| record_reading_time | INT UNSIGNED | 否 | 0 | 累计阅读时长 (秒) |
|
||||||
|
| finish_time | INT UNSIGNED | 否 | NULL | 读完时间 (Unix 时间戳) |
|
||||||
|
| is_start_reading | TINYINT(1) | 是 | 0 | 是否已开始阅读 |
|
||||||
|
| fetched_at | TIMESTAMP | 是 | CURRENT_TIMESTAMP | 获取时间 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (book_id)
|
||||||
|
|
||||||
|
**记录数**:1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.14 book_highlights - 划线内容表
|
||||||
|
|
||||||
|
> 来自微信读书 /book/bookmarklist 接口
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| book_id | VARCHAR(64) | 是 | - | 书籍 ID |
|
||||||
|
| bookmark_id | VARCHAR(64) | 否 | NULL | 划线唯一 ID |
|
||||||
|
| chapter_uid | INT UNSIGNED | 否 | NULL | 所在章节 UID |
|
||||||
|
| mark_text | LONGTEXT | 否 | NULL | 划线原文内容 |
|
||||||
|
| create_time | INT UNSIGNED | 否 | NULL | 创建时间 (Unix 时间戳) |
|
||||||
|
| type | TINYINT | 否 | NULL | 划线类型 |
|
||||||
|
| range_text | VARCHAR(64) | 否 | NULL | 位置范围 |
|
||||||
|
| color_style | TINYINT | 否 | NULL | 划线颜色样式 |
|
||||||
|
| fetched_at | TIMESTAMP | 是 | CURRENT_TIMESTAMP | 获取时间 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_highlights_book_id (book_id)
|
||||||
|
- idx_highlights_chapter (book_id, chapter_uid)
|
||||||
|
|
||||||
|
**记录数**:142
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.15 book_chapters - 章节信息表
|
||||||
|
|
||||||
|
> 来自微信读书 /book/bookmarklist 接口的 chapters 字段
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| book_id | VARCHAR(64) | 是 | - | 书籍 ID |
|
||||||
|
| chapter_uid | INT UNSIGNED | 是 | - | 章节 UID |
|
||||||
|
| chapter_idx | INT UNSIGNED | 否 | NULL | 章节序号 |
|
||||||
|
| title | VARCHAR(255) | 否 | NULL | 章节标题 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- UNIQUE KEY uk_book_chapter (book_id, chapter_uid)
|
||||||
|
|
||||||
|
**记录数**:77
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.16 book_reviews - 个人想法/点评表
|
||||||
|
|
||||||
|
> 来自微信读书 /review/list/mine 接口
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| book_id | VARCHAR(64) | 是 | - | 书籍 ID |
|
||||||
|
| review_id | VARCHAR(64) | 否 | NULL | 想法唯一 ID |
|
||||||
|
| content | LONGTEXT | 否 | NULL | 想法内容文本 |
|
||||||
|
| create_time | INT UNSIGNED | 否 | NULL | 创建时间 (Unix 时间戳) |
|
||||||
|
| star | TINYINT | 否 | -1 | 评分 (0-5, -1=无评分) |
|
||||||
|
| chapter_name | VARCHAR(255) | 否 | NULL | 所在章节名称 |
|
||||||
|
| is_finish | TINYINT(1) | 否 | 0 | 是否书评 (1=书评) |
|
||||||
|
| fetched_at | TIMESTAMP | 是 | CURRENT_TIMESTAMP | 获取时间 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_reviews_book_id (book_id)
|
||||||
|
|
||||||
|
**记录数**:19
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.17 recommended_books - 推荐书籍表
|
||||||
|
|
||||||
|
> 来自微信读书 /book/recommend 接口
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| book_id | VARCHAR(64) | 是 | - | 书籍 ID |
|
||||||
|
| title | VARCHAR(255) | 否 | NULL | 书名 |
|
||||||
|
| author | VARCHAR(255) | 否 | NULL | 作者 |
|
||||||
|
| cover | VARCHAR(512) | 否 | NULL | 封面图 URL |
|
||||||
|
| intro | TEXT | 否 | NULL | 书籍简介 |
|
||||||
|
| category | VARCHAR(128) | 否 | NULL | 分类 |
|
||||||
|
| reason | VARCHAR(512) | 否 | NULL | 推荐理由 |
|
||||||
|
| reading_count | INT UNSIGNED | 否 | 0 | 在读人数 |
|
||||||
|
| new_rating | DECIMAL(5,1) | 否 | 0 | 评分 (百分制) |
|
||||||
|
| new_rating_count | INT UNSIGNED | 否 | 0 | 评分人数 |
|
||||||
|
| rating_title | VARCHAR(32) | 否 | NULL | 评分标签 (神作/力荐等) |
|
||||||
|
| fetched_at | TIMESTAMP | 是 | CURRENT_TIMESTAMP | 获取时间 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_recommended_book_id (book_id)
|
||||||
|
- idx_recommended_fetched (fetched_at DESC)
|
||||||
|
|
||||||
|
**记录数**:48
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.18 search_results - 搜索结果表
|
||||||
|
|
||||||
|
> 来自微信读书 /store/search 接口
|
||||||
|
|
||||||
|
| 字段名 | 数据类型 | 是否必填 | 默认值 | 说明 |
|
||||||
|
|--------|----------|----------|--------|------|
|
||||||
|
| id | BIGINT | 是 (PK) | AUTO | 自增主键 |
|
||||||
|
| keyword | VARCHAR(255) | 是 | - | 搜索关键词 |
|
||||||
|
| book_id | VARCHAR(64) | 是 | - | 书籍 ID |
|
||||||
|
| title | VARCHAR(255) | 否 | NULL | 书名 |
|
||||||
|
| author | VARCHAR(255) | 否 | NULL | 作者 |
|
||||||
|
| cover | VARCHAR(512) | 否 | NULL | 封面图 URL |
|
||||||
|
| intro | TEXT | 否 | NULL | 书籍简介 |
|
||||||
|
| publisher | VARCHAR(255) | 否 | NULL | 出版社 |
|
||||||
|
| category | VARCHAR(128) | 否 | NULL | 分类 |
|
||||||
|
| reading_count | INT UNSIGNED | 否 | 0 | 在读人数 |
|
||||||
|
| new_rating | DECIMAL(5,1) | 否 | 0 | 评分 (百分制) |
|
||||||
|
| new_rating_count | INT UNSIGNED | 否 | 0 | 评分人数 |
|
||||||
|
| rating_title | VARCHAR(32) | 否 | NULL | 评分标签 |
|
||||||
|
| search_idx | INT UNSIGNED | 否 | 0 | 搜索序号 |
|
||||||
|
| fetched_at | TIMESTAMP | 是 | CURRENT_TIMESTAMP | 获取时间 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- PRIMARY KEY (id)
|
||||||
|
- idx_search_keyword (keyword)
|
||||||
|
- idx_search_book_id (book_id)
|
||||||
|
|
||||||
|
**记录数**:20
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 表关系说明
|
||||||
|
|
||||||
|
### 4.1 实体关系
|
||||||
|
|
||||||
|
```
|
||||||
|
books (书架)
|
||||||
|
├── book_id ──── book_details (书籍详情) [1:1 按需]
|
||||||
|
├── book_id ──── book_progress (阅读进度) [1:1 按需]
|
||||||
|
├── book_id ──── book_highlights (划线) [1:N 按需]
|
||||||
|
├── book_id ──── book_chapters (章节) [1:N 按需]
|
||||||
|
├── book_id ──── book_reviews (想法) [1:N 按需]
|
||||||
|
└── book_id ──── notebooks (笔记本概览) [1:1 有笔记的书]
|
||||||
|
|
||||||
|
read_longest (阅读排行)
|
||||||
|
└── book_id ──── books (书架) [N:1 关联]
|
||||||
|
|
||||||
|
recommended_books (推荐)
|
||||||
|
└── book_id (独立,不关联 books)
|
||||||
|
|
||||||
|
search_results (搜索)
|
||||||
|
└── book_id (独立,不关联 books)
|
||||||
|
|
||||||
|
archives (书单)
|
||||||
|
└── book_ids (JSON 数组,逻辑关联 books)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 逻辑分组
|
||||||
|
|
||||||
|
- **静态数据组**(采集时全量写入):books, reading_stats, read_times, read_longest, prefer_categories, prefer_authors, prefer_time, notebooks, read_stat_items, archives, fetch_meta
|
||||||
|
- **动态数据组**(用户操作时按需写入):book_details, book_progress, book_highlights, book_chapters, book_reviews, recommended_books, search_results
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 数据类型规范
|
||||||
|
|
||||||
|
| MySQL 类型 | 用途 | 对应 SQLite 类型 |
|
||||||
|
|------------|------|------------------|
|
||||||
|
| VARCHAR(n) | 变长字符串(书名、作者等) | TEXT |
|
||||||
|
| LONGTEXT | 长文本(简介、划线内容) | TEXT |
|
||||||
|
| TEXT | 中等文本 | TEXT |
|
||||||
|
| INT UNSIGNED | 无符号整数(时间戳、计数) | INTEGER |
|
||||||
|
| TINYINT(1) | 布尔值(0/1) | INTEGER |
|
||||||
|
| TINYINT UNSIGNED | 小范围整数(小时 0-23、进度 0-100) | INTEGER |
|
||||||
|
| BIGINT | 自增主键 | INTEGER |
|
||||||
|
| DECIMAL(5,1) | 精度数值(评分) | REAL |
|
||||||
|
| DOUBLE | 浮点数(权重、日均时长) | REAL |
|
||||||
|
| TIMESTAMP | 时间戳(自动维护) | INTEGER (Unix 时间戳) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 字符集与编码
|
||||||
|
|
||||||
|
- 数据库字符集:`utf8mb4`(支持完整 Unicode,包括 Emoji)
|
||||||
|
- 排序规则:`utf8mb4_unicode_ci`(不区分大小写,支持多语言排序)
|
||||||
|
- 连接字符集:`utf8mb4`(PyMySQL 连接配置中指定)
|
||||||
|
- 前端编码:`UTF-8`(HTML meta 标签声明)
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
# 微信读书数据展示网站 - 技术文档
|
||||||
|
|
||||||
|
> 版本:1.2.0 | 更新时间:2026-07-17 | 作者:WorkBuddy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 项目概述
|
||||||
|
|
||||||
|
### 1.1 项目简介
|
||||||
|
|
||||||
|
本项目是一个个人读书数据展示网站,数据来源于微信读书。通过微信读书 API 拉取用户的阅读数据(书架、阅读统计、笔记等),存入本地数据库,并通过 Web 仪表盘进行可视化展示。
|
||||||
|
|
||||||
|
### 1.2 核心功能
|
||||||
|
|
||||||
|
| 模块 | 功能说明 |
|
||||||
|
|------|----------|
|
||||||
|
| 数据采集 | 从微信读书 API 拉取书架、阅读统计、笔记本数据,存入本地数据库 |
|
||||||
|
| 总览仪表盘 | 累计阅读时长、阅读天数、藏书量、笔记数等核心指标 |
|
||||||
|
| 数据图表 | 年度阅读趋势(柱状图)、24h 时段分布(折线图)、偏好分类(饼图)、偏好作者(条形图) |
|
||||||
|
| 读书排行 | 阅读时长最久的 20 本书卡片展示 |
|
||||||
|
| 书架 | 404 本书的网格展示,支持分类筛选和"仅看已读完" |
|
||||||
|
| 笔记概览 | 有笔记的书籍列表,点击查看划线和想法详情 |
|
||||||
|
| 书籍详情 | 点击书架书籍弹出详情(书名、作者、出版社、ISBN、评分、阅读进度) |
|
||||||
|
| 笔记详情 | 点击笔记书籍查看划线(按章节分组)和想法/点评 |
|
||||||
|
| 推荐页面 | 个性化推荐书籍 + 关键词搜索,数据入库 |
|
||||||
|
|
||||||
|
### 1.3 技术栈
|
||||||
|
|
||||||
|
| 层级 | 技术 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 前端 | HTML5 + CSS3 + 原生 JavaScript | 无框架依赖,轻量高效 |
|
||||||
|
| 图表 | ECharts 5.5.0 | 年度趋势、时段分布、分类饼图、作者排行 |
|
||||||
|
| 后端 | Python Flask | 轻量级 Web 框架,提供 RESTful API |
|
||||||
|
| 数据库 | MySQL 5.7+(主)/ SQLite(采集) | MySQL 为生产数据库,SQLite 用于数据采集中间存储 |
|
||||||
|
| 数据采集 | 微信读书 Agent Gateway API | 通过 weread-skills 技能提供的 API 接口 |
|
||||||
|
| 版本控制 | Git | dev 分支开发,master 分支发布,遵循 Conventional Commits |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 系统架构
|
||||||
|
|
||||||
|
### 2.1 整体架构图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 用户浏览器(前端) │
|
||||||
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||||
|
│ │ 总览仪表盘│ │ 数据图表 │ │ 书架列表 │ │ 推荐搜索 │ │
|
||||||
|
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ └───────────┴─────┬─────┴───────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌────────▼────────┐ │
|
||||||
|
│ │ 健康检查模块 │ │
|
||||||
|
│ │ (MySQL 连接检测) │ │
|
||||||
|
│ └────────┬────────┘ │
|
||||||
|
└─────────────────────────┼───────────────────────────────┘
|
||||||
|
│ HTTP / RESTful API
|
||||||
|
┌─────────────────────────┼───────────────────────────────┐
|
||||||
|
│ Flask 后端服务 │
|
||||||
|
│ │ │
|
||||||
|
│ ┌──────────────────────▼──────────────────────┐ │
|
||||||
|
│ │ API 路由层 │ │
|
||||||
|
│ │ /api/overview /api/shelf /api/book/detail │ │
|
||||||
|
│ │ /api/recommend /api/search /api/health ... │ │
|
||||||
|
│ └──────────────────────┬──────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌──────────────────────▼──────────────────────┐ │
|
||||||
|
│ │ db_required 装饰器 │ │
|
||||||
|
│ │ (连接管理 + 错误处理 + 503 告警) │ │
|
||||||
|
│ └──────────────────────┬──────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌──────────────────────▼──────────────────────┐ │
|
||||||
|
│ │ PyMySQL 数据访问层 │ │
|
||||||
|
│ └───────────┬──────────────────┬──────────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ ┌───────────▼─────┐ ┌────────▼─────────┐ │
|
||||||
|
│ │ MySQL 数据库 │ │ 微信读书 API 代理 │ │
|
||||||
|
│ │ weread_dashboard│ │ (按需获取+入库) │ │
|
||||||
|
│ └─────────────────┘ └──────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────────────┼───────────────────────────────┐
|
||||||
|
│ 数据采集层(独立运行) │
|
||||||
|
│ ┌──────────────────────▼──────────────────────┐ │
|
||||||
|
│ │ fetch_data.py 采集脚本 │ │
|
||||||
|
│ │ 拉取书架/统计/笔记 → SQLite → migrate_to_mysql│ │
|
||||||
|
│ └──────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 数据流设计
|
||||||
|
|
||||||
|
```
|
||||||
|
微信读书 API
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
fetch_data.py(采集脚本)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
weread.db(SQLite 中间存储)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
migrate_to_mysql.py(迁移脚本)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
MySQL weread_dashboard(生产数据库)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Flask app.py(后端服务,端口 8080)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
前端页面(ECharts 可视化)
|
||||||
|
```
|
||||||
|
|
||||||
|
**按需数据流**(用户点击书籍/笔记时):
|
||||||
|
|
||||||
|
```
|
||||||
|
用户点击 → Flask API → 微信读书 API(实时获取)→ 入库 MySQL → 返回前端
|
||||||
|
↓(API 失败时)
|
||||||
|
从 MySQL 读取缓存数据 → 返回前端
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
weread-dashboard/
|
||||||
|
├── app.py # Flask 后端服务(MySQL 数据源)
|
||||||
|
├── fetch_data.py # 数据采集脚本(SQLite)
|
||||||
|
├── migrate_to_mysql.py # SQLite → MySQL 数据迁移脚本
|
||||||
|
├── schema_mysql.sql # MySQL 建表语句(含中文注释)
|
||||||
|
├── requirements.txt # Python 依赖
|
||||||
|
├── weread.db # SQLite 数据库文件(采集中间存储)
|
||||||
|
├── README.md # 项目说明
|
||||||
|
├── docs/ # 文档目录
|
||||||
|
│ ├── TECHNICAL_DOC.md # 技术文档(本文件)
|
||||||
|
│ └── METADATA_DOC.md # 元数据文档
|
||||||
|
├── templates/
|
||||||
|
│ └── index.html # 前端页面
|
||||||
|
└── static/
|
||||||
|
├── css/
|
||||||
|
│ └── style.css # 样式文件
|
||||||
|
└── js/
|
||||||
|
└── app.js # 前端逻辑
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 后端 API 设计
|
||||||
|
|
||||||
|
### 3.1 API 接口列表
|
||||||
|
|
||||||
|
| 接口 | 方法 | 说明 | 数据来源 |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| `/api/health` | GET | 健康检查(MySQL 连接状态) | MySQL |
|
||||||
|
| `/api/overview` | GET | 总览数据(核心指标) | MySQL |
|
||||||
|
| `/api/yearly-trend` | GET | 年度阅读时长时间序列 | MySQL |
|
||||||
|
| `/api/top-books` | GET | 阅读时长排行 TOP 20 | MySQL |
|
||||||
|
| `/api/categories` | GET | 偏好分类 | MySQL |
|
||||||
|
| `/api/authors` | GET | 偏好作者 TOP 20 | MySQL |
|
||||||
|
| `/api/time-distribution` | GET | 24 小时时段分布 | MySQL |
|
||||||
|
| `/api/shelf` | GET | 书架列表(分页) | MySQL |
|
||||||
|
| `/api/notebooks` | GET | 笔记本概览 | MySQL |
|
||||||
|
| `/api/read-stats` | GET | 阅读统计摘要 | MySQL |
|
||||||
|
| `/api/archives` | GET | 书单列表 | MySQL |
|
||||||
|
| `/api/book/detail` | GET | 书籍详情(按需) | 微信读书 API + MySQL |
|
||||||
|
| `/api/book/notes` | GET | 笔记详情(按需) | 微信读书 API + MySQL |
|
||||||
|
| `/api/recommend` | GET | 个性化推荐(按需) | 微信读书 API + MySQL |
|
||||||
|
| `/api/search` | GET | 搜索书籍(按需) | 微信读书 API + MySQL |
|
||||||
|
|
||||||
|
### 3.2 接口详情
|
||||||
|
|
||||||
|
#### 3.2.1 健康检查
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/health
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应(正常)**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"database": "connected",
|
||||||
|
"message": "MySQL 连接正常"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应(异常,HTTP 503)**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"database": "disconnected",
|
||||||
|
"message": "MySQL 连接失败: ..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.2 总览数据
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/overview
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total_read_time": 1223850,
|
||||||
|
"total_read_time_str": "339小时57分钟",
|
||||||
|
"read_days": 304,
|
||||||
|
"year_read_time": 849029,
|
||||||
|
"year_read_time_str": "235小时50分钟",
|
||||||
|
"year_read_days": 198,
|
||||||
|
"shelf_count": 404,
|
||||||
|
"finished_count": 12,
|
||||||
|
"notebook_count": 32,
|
||||||
|
"total_notes": 1709,
|
||||||
|
"regist_time": "2024-03-15",
|
||||||
|
"last_read_time": "2026-07-17",
|
||||||
|
"fetch_time": "2026-07-17 09:50:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.3 书架列表(分页)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/shelf?page=1&per_page=60&category=文学&finished=1
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| page | int | 1 | 页码 |
|
||||||
|
| per_page | int | 60 | 每页数量 |
|
||||||
|
| category | string | "" | 分类筛选(模糊匹配) |
|
||||||
|
| finished | string | "0" | "1"=只看已读完 |
|
||||||
|
|
||||||
|
#### 3.2.4 书籍详情(按需)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/book/detail?book_id=34615967
|
||||||
|
```
|
||||||
|
|
||||||
|
实时调用微信读书 `/book/info` + `/book/getprogress` API,入库后返回。API 失败时回退到数据库缓存。
|
||||||
|
|
||||||
|
#### 3.2.5 错误处理
|
||||||
|
|
||||||
|
所有 API 接口在 MySQL 连接失败时统一返回:
|
||||||
|
|
||||||
|
```
|
||||||
|
HTTP 503 Service Unavailable
|
||||||
|
{
|
||||||
|
"error": "数据库连接失败",
|
||||||
|
"detail": "具体错误信息",
|
||||||
|
"alert": "MySQL 数据库无法连接,请检查 MySQL 服务是否已启动。"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 关键设计决策
|
||||||
|
|
||||||
|
### 4.1 数据库选择
|
||||||
|
|
||||||
|
- **生产环境使用 MySQL**:支持并发访问、事务管理、完善的权限体系
|
||||||
|
- **采集层使用 SQLite**:轻量级,无需额外服务,适合单机数据采集
|
||||||
|
- **迁移脚本桥接**:`migrate_to_mysql.py` 负责 SQLite → MySQL 的数据同步
|
||||||
|
|
||||||
|
### 4.2 按需获取策略
|
||||||
|
|
||||||
|
书籍详情、笔记详情、推荐、搜索等数据采用**按需获取**策略:
|
||||||
|
- 用户点击时实时调用微信读书 API
|
||||||
|
- 获取成功后入库 MySQL 缓存
|
||||||
|
- API 调用失败时自动回退到数据库缓存
|
||||||
|
- 避免初始化时大量 API 调用,提升用户体验
|
||||||
|
|
||||||
|
### 4.3 健康检查与告警机制
|
||||||
|
|
||||||
|
- 后端提供 `/api/health` 接口检测 MySQL 连接状态
|
||||||
|
- 前端页面加载时首先调用健康检查
|
||||||
|
- 连接失败时显示固定告警横幅,阻止后续数据加载
|
||||||
|
- 使用 `db_required` 装饰器统一处理数据库连接异常
|
||||||
|
|
||||||
|
### 4.4 Git 分支策略
|
||||||
|
|
||||||
|
- **dev 分支**:日常开发,功能迭代
|
||||||
|
- **master 分支**:稳定发布版本,通过 `--no-ff` 合并 dev
|
||||||
|
- 提交信息遵循 **Conventional Commits** 规范(`feat:` / `fix:` / `merge:`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 部署与运行
|
||||||
|
|
||||||
|
### 5.1 环境要求
|
||||||
|
|
||||||
|
- Python 3.13+
|
||||||
|
- MySQL 5.7+
|
||||||
|
- 微信读书 API Key(通过 `WEREAD_API_KEY` 环境变量配置)
|
||||||
|
|
||||||
|
### 5.2 启动步骤
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 设置微信读书 API Key
|
||||||
|
export WEREAD_API_KEY=wrk-xxxxxxxx
|
||||||
|
|
||||||
|
# 2. 安装 Python 依赖
|
||||||
|
pip install flask pymysql
|
||||||
|
|
||||||
|
# 3. 启动 MySQL 服务
|
||||||
|
sudo /usr/local/mysql/support-files/mysql.server start
|
||||||
|
|
||||||
|
# 4. 创建数据库并建表
|
||||||
|
mysql -u root -p < schema_mysql.sql
|
||||||
|
|
||||||
|
# 5. 采集数据(首次)
|
||||||
|
python fetch_data.py
|
||||||
|
|
||||||
|
# 6. 迁移数据到 MySQL
|
||||||
|
python migrate_to_mysql.py
|
||||||
|
|
||||||
|
# 7. 启动 Web 服务
|
||||||
|
python app.py
|
||||||
|
# 访问 http://127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 数据更新
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 重新采集最新数据
|
||||||
|
python fetch_data.py
|
||||||
|
|
||||||
|
# 同步到 MySQL
|
||||||
|
python migrate_to_mysql.py
|
||||||
|
```
|
||||||
+165
-158
@@ -35,268 +35,275 @@ def api_call(api_name, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def init_db(db_path):
|
def init_db(db_path):
|
||||||
"""初始化数据库表结构"""
|
"""
|
||||||
|
初始化 SQLite 数据库表结构。
|
||||||
|
创建所有数据表(如果不存在),包括:
|
||||||
|
- 基础数据表:书架、阅读统计、笔记本等(采集时写入)
|
||||||
|
- 按需数据表:书籍详情、划线、想法等(用户点击时写入)
|
||||||
|
"""
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(db_path)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
# 书架书籍表
|
# ===== 基础数据表(采集脚本写入) =====
|
||||||
|
|
||||||
|
# 书架书籍表 - 存储用户书架中的所有书籍信息
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS books (
|
CREATE TABLE IF NOT EXISTS books (
|
||||||
book_id TEXT PRIMARY KEY,
|
book_id TEXT PRIMARY KEY, -- 书籍唯一标识
|
||||||
title TEXT,
|
title TEXT, -- 书名
|
||||||
author TEXT,
|
author TEXT, -- 作者
|
||||||
cover TEXT,
|
cover TEXT, -- 封面图 URL
|
||||||
category TEXT,
|
category TEXT, -- 分类(如 "文学-古代诗词")
|
||||||
finish_reading INTEGER DEFAULT 0,
|
finish_reading INTEGER DEFAULT 0, -- 是否读完 (1=读完, 0=未读完)
|
||||||
read_update_time INTEGER,
|
read_update_time INTEGER, -- 最近阅读时间 (Unix 时间戳)
|
||||||
secret INTEGER DEFAULT 0,
|
secret INTEGER DEFAULT 0, -- 是否私密书籍 (1=私密)
|
||||||
is_top INTEGER DEFAULT 0,
|
is_top INTEGER DEFAULT 0, -- 是否置顶 (1=置顶)
|
||||||
update_time INTEGER,
|
update_time INTEGER, -- 书籍信息更新时间 (Unix 时间戳)
|
||||||
deep_link TEXT
|
deep_link TEXT -- 微信读书 App 深度链接
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 阅读统计概要表
|
# 阅读统计概要表 - 存储总体/年度阅读统计数据
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS reading_stats (
|
CREATE TABLE IF NOT EXISTS reading_stats (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
mode TEXT,
|
mode TEXT, -- 统计维度: overall(总体)/annually(年度)
|
||||||
period TEXT,
|
period TEXT, -- 周期标识: "overall" 或 "2026"
|
||||||
total_read_time INTEGER,
|
total_read_time INTEGER, -- 总阅读时长 (秒)
|
||||||
read_days INTEGER,
|
read_days INTEGER, -- 有效阅读天数
|
||||||
day_average_read_time REAL,
|
day_average_read_time REAL, -- 日均阅读时长 (秒)
|
||||||
regist_time INTEGER,
|
regist_time INTEGER, -- 用户注册时间 (Unix 时间戳)
|
||||||
raw_json TEXT
|
raw_json TEXT -- 原始 API 返回 JSON (完整备份)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 阅读时长时间序列(按年/月/日分桶)
|
# 阅读时长时间序列表 - 按年/月/日分桶的阅读时长数据
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS read_times (
|
CREATE TABLE IF NOT EXISTS read_times (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
mode TEXT,
|
mode TEXT, -- 统计维度 (含 _daily 后缀表示每日数据)
|
||||||
period TEXT,
|
period TEXT, -- 周期标识
|
||||||
bucket_timestamp INTEGER,
|
bucket_timestamp INTEGER, -- 分桶起始时间戳 (Unix 时间戳)
|
||||||
duration INTEGER
|
duration INTEGER -- 该时段阅读时长 (秒)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 阅读时长最长的书籍排行
|
# 阅读时长排行表 - 阅读时间最久的书籍排名
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS read_longest (
|
CREATE TABLE IF NOT EXISTS read_longest (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
mode TEXT,
|
mode TEXT, -- 统计维度
|
||||||
period TEXT,
|
period TEXT, -- 周期标识
|
||||||
book_id TEXT,
|
book_id TEXT, -- 书籍 ID
|
||||||
title TEXT,
|
title TEXT, -- 书名
|
||||||
author TEXT,
|
author TEXT, -- 作者
|
||||||
cover TEXT,
|
cover TEXT, -- 封面图 URL
|
||||||
read_time INTEGER,
|
read_time INTEGER, -- 阅读时长 (秒)
|
||||||
tags TEXT
|
tags TEXT -- 标签 (JSON 数组格式)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 偏好分类
|
# 偏好分类表 - 用户阅读的书籍分类统计
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS prefer_categories (
|
CREATE TABLE IF NOT EXISTS prefer_categories (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
mode TEXT,
|
mode TEXT, -- 统计维度
|
||||||
period TEXT,
|
period TEXT, -- 周期标识
|
||||||
category_id INTEGER,
|
category_id INTEGER, -- 分类 ID
|
||||||
category_title TEXT,
|
category_title TEXT, -- 分类名称 (如 "文学")
|
||||||
parent_category_title TEXT,
|
parent_category_title TEXT, -- 父分类名称 (如 "精品小说")
|
||||||
val REAL,
|
val REAL, -- 偏好权重值
|
||||||
reading_time INTEGER,
|
reading_time INTEGER, -- 该分类阅读时长 (秒)
|
||||||
reading_count INTEGER
|
reading_count INTEGER -- 该分类阅读本数
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 偏好作者
|
# 偏好作者表 - 用户阅读最多的作者统计
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS prefer_authors (
|
CREATE TABLE IF NOT EXISTS prefer_authors (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
mode TEXT,
|
mode TEXT, -- 统计维度
|
||||||
period TEXT,
|
period TEXT, -- 周期标识
|
||||||
author_id INTEGER,
|
author_id INTEGER, -- 作者 ID
|
||||||
name TEXT,
|
name TEXT, -- 作者名称
|
||||||
count INTEGER,
|
count INTEGER, -- 阅读该作者的书本数
|
||||||
read_time TEXT
|
read_time TEXT -- 阅读时长 (格式化字符串)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 24小时阅读时段分布
|
# 24小时阅读时段分布表 - 一天中每个小时的阅读时长
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS prefer_time (
|
CREATE TABLE IF NOT EXISTS prefer_time (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
mode TEXT,
|
mode TEXT, -- 统计维度
|
||||||
period TEXT,
|
period TEXT, -- 周期标识
|
||||||
hour_index INTEGER,
|
hour_index INTEGER, -- 小时 (0-23)
|
||||||
duration INTEGER
|
duration INTEGER -- 该小时阅读时长 (秒)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 笔记本概览
|
# 笔记本概览表 - 有笔记的书籍及其笔记统计
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS notebooks (
|
CREATE TABLE IF NOT EXISTS notebooks (
|
||||||
book_id TEXT PRIMARY KEY,
|
book_id TEXT PRIMARY KEY, -- 书籍 ID
|
||||||
title TEXT,
|
title TEXT, -- 书名
|
||||||
author TEXT,
|
author TEXT, -- 作者
|
||||||
cover TEXT,
|
cover TEXT, -- 封面图 URL
|
||||||
review_count INTEGER DEFAULT 0,
|
review_count INTEGER DEFAULT 0, -- 想法/点评数
|
||||||
note_count INTEGER DEFAULT 0,
|
note_count INTEGER DEFAULT 0, -- 划线数
|
||||||
bookmark_count INTEGER DEFAULT 0,
|
bookmark_count INTEGER DEFAULT 0, -- 书签数
|
||||||
reading_progress INTEGER DEFAULT 0,
|
reading_progress INTEGER DEFAULT 0, -- 阅读进度 (0-100)
|
||||||
marked_status INTEGER DEFAULT 0,
|
marked_status INTEGER DEFAULT 0, -- 标记状态 (1=读完)
|
||||||
sort INTEGER
|
sort INTEGER -- 排序值 (微信读书返回的排序字段)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 阅读统计摘要项
|
# 阅读统计摘要项表 - App 首页展示的统计摘要
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS read_stat_items (
|
CREATE TABLE IF NOT EXISTS read_stat_items (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
mode TEXT,
|
mode TEXT, -- 统计维度
|
||||||
period TEXT,
|
period TEXT, -- 周期标识
|
||||||
stat_name TEXT,
|
stat_name TEXT, -- 统计项名称 (读过/读完/阅读/笔记)
|
||||||
counts TEXT,
|
counts TEXT, -- 统计值文案 (如 "404本")
|
||||||
scheme TEXT
|
scheme TEXT -- App 跳转链接
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 书单/归档
|
# 书单/归档表 - 用户创建的书单
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS archives (
|
CREATE TABLE IF NOT EXISTS archives (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT,
|
name TEXT, -- 书单名称
|
||||||
book_ids TEXT
|
book_ids TEXT -- 包含的书籍 ID 列表 (JSON 数组)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 数据采集元信息
|
# 数据采集元信息表 - 记录采集时间等元数据
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS fetch_meta (
|
CREATE TABLE IF NOT EXISTS fetch_meta (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY, -- 元信息键名 (如 "last_fetch_time")
|
||||||
value TEXT
|
value TEXT -- 元信息值
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# ===== 按需获取的详细数据表 =====
|
# ===== 按需数据表(用户点击时实时获取并写入) =====
|
||||||
|
|
||||||
# 书籍详细信息(来自 /book/info)
|
# 书籍详细信息表 - 来自微信读书 /book/info 接口
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS book_details (
|
CREATE TABLE IF NOT EXISTS book_details (
|
||||||
book_id TEXT PRIMARY KEY,
|
book_id TEXT PRIMARY KEY, -- 书籍 ID
|
||||||
title TEXT,
|
title TEXT, -- 书名
|
||||||
author TEXT,
|
author TEXT, -- 作者
|
||||||
translator TEXT,
|
translator TEXT, -- 译者
|
||||||
cover TEXT,
|
cover TEXT, -- 封面图 URL
|
||||||
intro TEXT,
|
intro TEXT, -- 书籍简介
|
||||||
category TEXT,
|
category TEXT, -- 分类
|
||||||
publisher TEXT,
|
publisher TEXT, -- 出版社
|
||||||
publish_time TEXT,
|
publish_time TEXT, -- 出版时间
|
||||||
isbn TEXT,
|
isbn TEXT, -- ISBN 号
|
||||||
word_count INTEGER,
|
word_count INTEGER, -- 总字数
|
||||||
new_rating REAL,
|
new_rating REAL, -- 评分 (百分制)
|
||||||
new_rating_count INTEGER,
|
new_rating_count INTEGER, -- 评分人数
|
||||||
fetched_at INTEGER
|
fetched_at INTEGER -- 数据获取时间 (Unix 时间戳)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 阅读进度(来自 /book/getprogress)
|
# 阅读进度表 - 来自微信读书 /book/getprogress 接口
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS book_progress (
|
CREATE TABLE IF NOT EXISTS book_progress (
|
||||||
book_id TEXT PRIMARY KEY,
|
book_id TEXT PRIMARY KEY, -- 书籍 ID
|
||||||
progress INTEGER,
|
progress INTEGER, -- 阅读进度百分比 (0-100)
|
||||||
chapter_uid INTEGER,
|
chapter_uid INTEGER, -- 当前阅读章节 UID
|
||||||
chapter_offset INTEGER,
|
chapter_offset INTEGER, -- 当前章节内偏移量
|
||||||
update_time INTEGER,
|
update_time INTEGER, -- 最后阅读时间 (Unix 时间戳)
|
||||||
record_reading_time INTEGER,
|
record_reading_time INTEGER, -- 累计阅读时长 (秒)
|
||||||
finish_time INTEGER,
|
finish_time INTEGER, -- 读完时间 (Unix 时间戳)
|
||||||
is_start_reading INTEGER,
|
is_start_reading INTEGER, -- 是否已开始阅读 (1=是)
|
||||||
fetched_at INTEGER
|
fetched_at INTEGER -- 数据获取时间 (Unix 时间戳)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 划线内容(来自 /book/bookmarklist)
|
# 划线内容表 - 来自微信读书 /book/bookmarklist 接口
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS book_highlights (
|
CREATE TABLE IF NOT EXISTS book_highlights (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
book_id TEXT,
|
book_id TEXT, -- 书籍 ID
|
||||||
bookmark_id TEXT,
|
bookmark_id TEXT, -- 划线唯一 ID
|
||||||
chapter_uid INTEGER,
|
chapter_uid INTEGER, -- 所在章节 UID
|
||||||
mark_text TEXT,
|
mark_text TEXT, -- 划线原文内容
|
||||||
create_time INTEGER,
|
create_time INTEGER, -- 创建时间 (Unix 时间戳)
|
||||||
type INTEGER,
|
type INTEGER, -- 划线类型
|
||||||
range_text TEXT,
|
range_text TEXT, -- 位置范围
|
||||||
color_style INTEGER,
|
color_style INTEGER, -- 划线颜色样式
|
||||||
fetched_at INTEGER
|
fetched_at INTEGER -- 数据获取时间 (Unix 时间戳)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 章节信息(来自 /book/bookmarklist 的 chapters)
|
# 章节信息表 - 来自微信读书 /book/bookmarklist 接口的 chapters 字段
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS book_chapters (
|
CREATE TABLE IF NOT EXISTS book_chapters (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
book_id TEXT,
|
book_id TEXT, -- 书籍 ID
|
||||||
chapter_uid INTEGER,
|
chapter_uid INTEGER, -- 章节 UID
|
||||||
chapter_idx INTEGER,
|
chapter_idx INTEGER, -- 章节序号
|
||||||
title TEXT,
|
title TEXT, -- 章节标题
|
||||||
UNIQUE(book_id, chapter_uid)
|
UNIQUE(book_id, chapter_uid) -- 联合唯一约束
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 个人想法/点评(来自 /review/list/mine)
|
# 个人想法/点评表 - 来自微信读书 /review/list/mine 接口
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS book_reviews (
|
CREATE TABLE IF NOT EXISTS book_reviews (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
book_id TEXT,
|
book_id TEXT, -- 书籍 ID
|
||||||
review_id TEXT,
|
review_id TEXT, -- 想法唯一 ID
|
||||||
content TEXT,
|
content TEXT, -- 想法内容文本
|
||||||
create_time INTEGER,
|
create_time INTEGER, -- 创建时间 (Unix 时间戳)
|
||||||
star INTEGER,
|
star INTEGER, -- 评分 (0-5, -1=无评分)
|
||||||
chapter_name TEXT,
|
chapter_name TEXT, -- 所在章节名称
|
||||||
is_finish INTEGER,
|
is_finish INTEGER, -- 是否书评 (1=书评)
|
||||||
fetched_at INTEGER
|
fetched_at INTEGER -- 数据获取时间 (Unix 时间戳)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 推荐书籍(来自 /book/recommend)
|
# 推荐书籍表 - 来自微信读书 /book/recommend 接口
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS recommended_books (
|
CREATE TABLE IF NOT EXISTS recommended_books (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
book_id TEXT,
|
book_id TEXT, -- 书籍 ID
|
||||||
title TEXT,
|
title TEXT, -- 书名
|
||||||
author TEXT,
|
author TEXT, -- 作者
|
||||||
cover TEXT,
|
cover TEXT, -- 封面图 URL
|
||||||
intro TEXT,
|
intro TEXT, -- 书籍简介
|
||||||
category TEXT,
|
category TEXT, -- 分类
|
||||||
reason TEXT,
|
reason TEXT, -- 推荐理由
|
||||||
reading_count INTEGER,
|
reading_count INTEGER, -- 在读人数
|
||||||
new_rating REAL,
|
new_rating REAL, -- 评分 (百分制)
|
||||||
new_rating_count INTEGER,
|
new_rating_count INTEGER, -- 评分人数
|
||||||
rating_title TEXT,
|
rating_title TEXT, -- 评分标签 (神作/力荐等)
|
||||||
fetched_at INTEGER
|
fetched_at INTEGER -- 数据获取时间 (Unix 时间戳)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# 搜索结果(来自 /store/search)
|
# 搜索结果表 - 来自微信读书 /store/search 接口
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS search_results (
|
CREATE TABLE IF NOT EXISTS search_results (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
keyword TEXT,
|
keyword TEXT, -- 搜索关键词
|
||||||
book_id TEXT,
|
book_id TEXT, -- 书籍 ID
|
||||||
title TEXT,
|
title TEXT, -- 书名
|
||||||
author TEXT,
|
author TEXT, -- 作者
|
||||||
cover TEXT,
|
cover TEXT, -- 封面图 URL
|
||||||
intro TEXT,
|
intro TEXT, -- 书籍简介
|
||||||
publisher TEXT,
|
publisher TEXT, -- 出版社
|
||||||
category TEXT,
|
category TEXT, -- 分类
|
||||||
reading_count INTEGER,
|
reading_count INTEGER, -- 在读人数
|
||||||
new_rating REAL,
|
new_rating REAL, -- 评分 (百分制)
|
||||||
new_rating_count INTEGER,
|
new_rating_count INTEGER, -- 评分人数
|
||||||
rating_title TEXT,
|
rating_title TEXT, -- 评分标签
|
||||||
search_idx INTEGER,
|
search_idx INTEGER, -- 搜索序号
|
||||||
fetched_at INTEGER
|
fetched_at INTEGER -- 数据获取时间 (Unix 时间戳)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
--text-light: #9b8e80;
|
--text-light: #9b8e80;
|
||||||
--accent: #c17b4a;
|
--accent: #c17b4a;
|
||||||
--accent-light: #e8a87c;
|
--accent-light: #e8a87c;
|
||||||
|
--alert-bg: #fff3cd;
|
||||||
|
--alert-border: #ffc107;
|
||||||
|
--alert-text: #856404;
|
||||||
--accent-dark: #a05c2c;
|
--accent-dark: #a05c2c;
|
||||||
--border: #e8e0d4;
|
--border: #e8e0d4;
|
||||||
--shadow: 0 2px 12px rgba(44, 36, 22, 0.08);
|
--shadow: 0 2px 12px rgba(44, 36, 22, 0.08);
|
||||||
@@ -657,6 +660,47 @@ body {
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 数据库连接告警横幅 ===== */
|
||||||
|
.db-alert-banner {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: var(--alert-bg);
|
||||||
|
border-bottom: 2px solid var(--alert-border);
|
||||||
|
color: var(--alert-text);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
animation: slideDown 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-alert-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-alert-text {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-alert-detail {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.8;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from { transform: translateY(-100%); opacity: 0; }
|
||||||
|
to { transform: translateY(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== 加载动画 ===== */
|
/* ===== 加载动画 ===== */
|
||||||
.loading {
|
.loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
+45
-1
@@ -14,8 +14,52 @@ function formatHours(seconds) {
|
|||||||
return (seconds / 3600).toFixed(1);
|
return (seconds / 3600).toFixed(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 数据库健康检查 =====
|
||||||
|
/**
|
||||||
|
* 页面加载时检查 MySQL 数据库连接状态。
|
||||||
|
* 如果连接失败,显示告警横幅并阻止后续数据加载。
|
||||||
|
*/
|
||||||
|
async function checkDBHealth() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(API_BASE + "/api/health");
|
||||||
|
const data = await resp.json();
|
||||||
|
if (resp.ok && data.database === "connected") {
|
||||||
|
// 数据库连接正常,隐藏告警横幅
|
||||||
|
document.getElementById("dbAlertBanner").style.display = "none";
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// 数据库连接异常,显示告警横幅
|
||||||
|
showDBAlert(data.message || "数据库连接失败");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 健康检查接口本身无法访问
|
||||||
|
showDBAlert("无法连接到服务器,请检查后端服务是否运行。");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示数据库连接告警横幅。
|
||||||
|
* @param {string} message - 告警详情信息
|
||||||
|
*/
|
||||||
|
function showDBAlert(message) {
|
||||||
|
const banner = document.getElementById("dbAlertBanner");
|
||||||
|
const detail = document.getElementById("dbAlertDetail");
|
||||||
|
if (banner) banner.style.display = "flex";
|
||||||
|
if (detail) detail.textContent = message;
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 初始化 =====
|
// ===== 初始化 =====
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
// 首先检查数据库连接状态
|
||||||
|
const isHealthy = await checkDBHealth();
|
||||||
|
if (!isHealthy) {
|
||||||
|
// 数据库连接失败,不加载后续数据
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库连接正常,加载所有数据模块
|
||||||
loadOverview();
|
loadOverview();
|
||||||
loadYearlyTrend();
|
loadYearlyTrend();
|
||||||
loadTimeDistribution();
|
loadTimeDistribution();
|
||||||
|
|||||||
@@ -8,6 +8,13 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- 数据库连接告警横幅(MySQL 连接失败时显示) -->
|
||||||
|
<div id="dbAlertBanner" class="db-alert-banner" style="display:none;">
|
||||||
|
<span class="db-alert-icon">⚠️</span>
|
||||||
|
<span class="db-alert-text">MySQL 数据库连接失败,数据可能无法正常加载。请检查 MySQL 服务是否已启动。</span>
|
||||||
|
<span class="db-alert-detail" id="dbAlertDetail"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 导航栏 -->
|
<!-- 导航栏 -->
|
||||||
<nav class="navbar">
|
<nav class="navbar">
|
||||||
<div class="nav-container">
|
<div class="nav-container">
|
||||||
|
|||||||
Reference in new issue
Block a user