内容导航

Project

错题轻舟

面向纸质错题整理与个性化复习的小程序项目,覆盖题目框选、OCR 辅助识别、错题库管理、用户隔离、PDF 试卷生成和本地化部署。

小程序开发 微信小程序OCRFastAPIPDFNginxFRP
本文目录

状态:开发中

错题轻舟

错题轻舟是一套面向学生、家长和课后辅导场景的错题整理与复习工具。它的目标不是简单保存试卷照片,而是把纸质试卷、练习册和作业本中的错题拆成可编辑、可检索、可复习的结构化资料,并在需要时重新生成复习试卷。

项目分为微信小程序前端和 FastAPI 服务端两部分。小程序端负责图片选择、题目框选、识别结果校对、错题编辑、错题库浏览和试卷预览;服务端负责登录鉴权、用户隔离、图片上传、题图裁剪拼接、OCR 辅助识别、错题数据存储和 PDF 文件生成。

本文是公开版项目记录。文中的域名、端口、路径、密钥、topic、数据库内容和服务器标识均做了脱敏或泛化处理,不对应真实生产配置。

背景

传统错题整理通常有三个痛点:

  • 手抄题目耗时,特别是数学题、几何题和带图题。
  • 只拍照片又不便于检索、筛选和生成复习资料。
  • 不同阶段、不同练习册中的错题容易分散,复习时难以重新组合。

错题轻舟的设计思路是保留“人工确认”的准确性,同时把重复性工作交给系统处理。用户仍然手动框选题目、校对识别结果、填写答案和错因;系统负责图片裁剪、OCR 识别、题图保存、错题入库和试卷排版。

核心目标

  • 支持手机拍照或相册选择试卷图片。
  • 支持手动框选题目区域,避免全页 OCR 造成分题不准。
  • 支持多区域拼接,适配跨栏题、带图题和局部组合题。
  • 每道题保存独立题图,而不是只保存整张试卷。
  • OCR 只作为题干录入辅助,不自动猜测答案和错因。
  • 学科、题型、答案、答案图片和错因由用户确认后保存。
  • 错题库支持搜索、编辑、删除和复习筛选。
  • 试卷导出前支持预览,并可逐题设置是否编入、是否保留题图。
  • 服务端按用户身份隔离数据,避免多用户数据混用。

功能流程

上传图片
  -> 手动框选题目区域
  -> 多区域裁剪与拼接
  -> OCR 辅助识别
  -> 用户校对题干
  -> 补充答案 / 答案图 / 错因
  -> 保存到错题库
  -> 筛选题目并预览试卷
  -> 导出 PDF 复习试卷

小程序端不把 OCR 识别结果直接视为最终题目内容,而是把它作为“草稿”。用户可以在保存前修改题干、题型和答案信息,这样能够减少 OCR 误识别带来的长期数据污染。

前端设计

小程序前端按页面和业务能力拆分:

app / config
  - 全局配置、API 地址、登录状态初始化

utils
  - 请求封装
  - 坐标转换
  - 屏幕布局状态

services
  - 用户登录
  - 图片上传
  - OCR 调用
  - 错题增删改查
  - PDF 生成请求

features
  - 框选会话
  - 题目数据归一化
  - 试卷选择状态

pages
  - 首页
  - 框选错题
  - 编辑错题
  - 错题库
  - 生成试卷
  - 我的账户
  - 软件声明与反馈

框选页面是前端中最复杂的部分。它需要处理图片在不同屏幕中的显示比例、拖动偏移、缩放状态和原图坐标之间的换算。用户看到的框选位置必须能够准确映射到服务端裁剪坐标,否则会出现“框选看起来正确,但裁剪结果偏移”的问题。

为了适配手机、平板、折叠屏和窗口化运行场景,页面布局不做简单等比缩放,而是根据设备宽度、可用高度和操作密度调整区域。小屏幕优先保证图片展示和框选手柄可操作;大屏幕则给预览、题目信息和操作按钮更多空间。

服务端设计

服务端采用 FastAPI 实现,核心模块包括:

认证模块
  - 接收 wx.login code
  - 换取用户标识
  - 生成服务端登录态

上传模块
  - 校验图片类型
  - 保存原始题图和答案图
  - 返回受控访问地址

图片处理模块
  - 按原图坐标裁剪
  - 支持多区域纵向拼接
  - 支持黑白增强、降噪和锐化参数

OCR 模块
  - 对题图进行预处理
  - 调用 OCR 引擎
  - 返回识别文本和结构化结果

错题模块
  - 新增、查询、修改、删除
  - 按用户身份隔离数据
  - 保存题干、答案、错因、题图和时间信息

PDF 模块
  - 按用户选择生成复习试卷
  - 题目页与答案错因页分离
  - 支持保留题图和基础排版

服务端只监听本机内部端口,公网访问由 Nginx 或云入口统一代理。密钥、微信 AppSecret、数据库文件、用户上传图片和导出 PDF 不进入公开仓库,也不在公开文章中展示真实值。

数据模型

当前数据模型保持轻量,适合个人或小规模使用:

users
  - id
  - openid_hash
  - token_hash
  - created_at

mistakes
  - id
  - user_id
  - subject
  - question_type
  - question_text
  - answer
  - answer_image_path
  - wrong_reason
  - image_path
  - created_at
  - updated_at

files
  - uploads/
  - exports/
  - stitched/
  - answer-images/

数据库保存结构化字段和文件路径,图片与 PDF 保存在文件系统目录中。这样可以避免把大文件直接写入数据库,也便于后续迁移到对象存储。

OCR 策略

早期尝试过整页 OCR 自动分题,但实际效果不稳定。试卷中经常存在多栏排版、题目跨栏、图文混排、选项挤压和手写痕迹,完全依赖 OCR 自动分题会导致题目边界不准。

因此当前策略改为:

用户先框选题目区域
  -> 服务端只识别该区域
  -> 用户再校对识别文本

这降低了自动化程度,但显著提高了可控性。OCR 的定位从“自动完成错题录入”调整为“减少用户手打题干”,更符合错题本的真实使用场景。

PDF 生成

试卷生成不是简单把错题列表拼接成 PDF,而是加入了选择和预览步骤:

  • 用户先在错题库或试卷页面筛选题目。
  • 每道题可以设置是否编入试卷。
  • 每道题可以设置是否保留原始题图。
  • 试题页不直接显示答案和错因。
  • 答案和错因统一放到最后的附录页。
  • 选择题排版尽量保留选项分行。

这种设计让 PDF 更接近可打印复习资料,而不是错题详情截图合集。

部署结构

正式部署采用“云服务器公网入口 + 本地 Debian 服务承载”的结构:

微信小程序
  -> HTTPS 合法域名
  -> 云服务器 Nginx
  -> 云服务器本机 FRP 映射端口
  -> FRP 隧道
  -> 本地 Debian Nginx
  -> 本地 FastAPI 127.0.0.1:<APP_PORT>

这样做的原因是:

  • 小程序必须访问 HTTPS 合法域名。
  • 本地 Debian 方便长期保存数据和迁移。
  • 云服务器只承担公网入口、证书和转发,不保存主要错题数据。
  • 后续可以在本地 Debian Nginx 中按域名分发更多服务。

云服务器上的 Nginx 不直接承载后端业务,而是把请求转发到 FRP 映射端口。本地 Debian 的 Nginx 再根据 Host 或路径把请求分发到对应服务。当前小程序后端只是其中一个服务,后续可以继续接入博客、自动化工作流或通知中心。

部署步骤概览

公开版部署流程保留结构,不包含真实路径和密钥:

# 本地 Debian
mkdir -p <APP_DIR>
cd <APP_DIR>
python3 -m venv .venv
./.venv/bin/python -m pip install -r requirements.txt
./.venv/bin/python -m pip install -r requirements-ocr.txt

服务端使用 systemd 常驻:

[Service]
WorkingDirectory=<APP_DIR>
ExecStart=<APP_DIR>/.venv/bin/python -m uvicorn main:app --host 127.0.0.1 --port <APP_PORT>
Restart=always
EnvironmentFile=/etc/<APP_NAME>.env

本地 Debian Nginx 只反代本机服务:

server {
    listen 80;
    server_name <APP_DOMAIN>;

    client_max_body_size 30m;

    location / {
        proxy_pass http://127.0.0.1:<APP_PORT>;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

FRP 客户端只把本地 Nginx 暴露给云端映射端口:

serverAddr = "<CLOUD_SERVER>"
serverPort = <FRP_BIND_PORT>

auth.method = "token"
auth.token = "<FRP_TOKEN>"

[[proxies]]
name = "miniapp-api"
type = "tcp"
localIP = "127.0.0.1"
localPort = 80
remotePort = <REMOTE_PORT>

云服务器 Nginx 最终只反代到本机 FRP 映射端口:

server {
    listen 443 ssl;
    server_name <APP_DOMAIN>;

    location / {
        proxy_pass http://127.0.0.1:<REMOTE_PORT>;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

隐私与安全处理

这个项目涉及学生错题、短信式登录状态、图片文件和服务端密钥,因此公开记录时遵循以下原则:

  • 不公开真实 API Key、AppSecret、FRP token、Webhook、数据库文件和用户图片。
  • 不公开真实服务器 IP、完整域名配置、内网机器名和系统用户。
  • 不把 uploads/exports/mistakes.db.venv/、日志文件提交到公开仓库。
  • 生产环境密钥放在 /etc/<APP_NAME>.env 或同级安全位置,文件权限限制为管理员可读。
  • 小程序前端中的固定 API Key 只能作为过渡校验,不应作为最终安全边界。
  • 用户数据访问以服务端登录态和用户标识为准,所有错题查询都必须按 user_id 过滤。
  • 对上传文件限制类型和大小,避免非图片文件进入处理流程。
  • 云服务器只开放 80/443 和必要的 FRP 控制端口,SSH 尽量限制来源 IP。
  • FRP dashboard 只监听本机,不直接暴露到公网。

当前状态

已完成:

  • 小程序页面基础框架。
  • 图片上传与题目框选。
  • 多区域拼接与题图保存。
  • OCR 辅助识别。
  • 错题新增、编辑、删除和列表展示。
  • 微信登录入口与用户隔离基础。
  • PDF 试卷预览与导出流程。
  • 服务端迁移到本地 Debian 的基础部署路径。

仍在改进:

  • 框选体验和不同设备布局适配。
  • OCR 识别后的选项排版清洗。
  • PDF 预览和最终导出的一致性。
  • 更细的题型判断和筛选条件。
  • 用户注销、数据导出和备份恢复流程。
  • 部署文档与软著材料的同步维护。

复盘

错题整理系统最容易陷入“完全自动化”的误区。实际使用中,OCR、自动分题和题型判断都存在不确定性,尤其是小学和初中数学题经常带图、跨栏或选项不规整。因此这个项目最终选择了更稳的路线:让用户控制题目区域和最终内容,让服务端承担重复处理。

从工程角度看,当前架构的重点不是追求复杂技术栈,而是把边界划清楚:小程序负责交互,服务端负责计算和存储,云服务器负责公网入口,本地 Debian 承载长期数据。这样后续无论是迁移服务器、扩展通知系统,还是把图片存储迁移到对象存储,都有清晰的替换位置。

系列笔记

这个系列还没有公开笔记。

评论

Giscus 评论尚未配置。填写 GitHub Discussions 的仓库和分类 ID 后,这里会显示评论区。