从零到可运行:基于 Vue3 + FastAPI + DeepSeek-V3 的 AI 英语单词学习系统全栈实战

从零到可运行:基于 Vue3 + FastAPI + DeepSeek-V3 的 AI 英语单词学习系统全栈实战

一份真实、完整、可复现的全栈项目复盘。包含架构设计、数据库建模、AI 词库生成、前端交互、踩坑修复与实测数据。

目录

  1. 项目背景与目标
  2. 技术选型
  3. 项目结构
  4. 数据库设计
  5. [后端 API 设计](#后端 API 设计)
  6. 前端页面与交互
  7. [AI 词库生成:DeepSeek-V3 接入实战](#AI 词库生成:DeepSeek-V3 接入实战)
  8. 关键问题修复记录(踩坑实录)
  9. 运行与部署
  10. 实测数据
  11. 总结与后续优化方向

一、项目背景与目标

单词学习类 App 的最大痛点有两个:词库是死板的 (背来背去就那一本书)和 学习过程没有正反馈(不知道哪些词掌握了、哪些还没)。本项目尝试用全栈工程手段解决这两个问题:

  • AI 动态词库 :接入大模型按难度(四级/六级/商务/托福/雅思)实时生成新词,生成的词自动累计入库并去重,词库越用越丰富;
  • 学习闭环:随机出词 → 翻卡查看释义 → 标记熟练度(比较熟悉 75 / 完全掌握 100)→ 进度条与统计页实时反馈,形成"学-记-测-查"的完整闭环。

项目由用户基于 AI 导出的方案(郭震 AI 的英语单词学习系统方案)起步,经历了前端多处语法/构建错误修复、后端依赖冲突解决、AI 接口打通、词库去重策略设计等一系列工程问题,最终前后端成功运行、AI 生成功能可用。


二、技术选型

层级 技术 版本 用途
前端框架 Vue 3 ^3.5.40 响应式 UI
构建工具 Vite ^8.2.0 开发服务器与构建
状态管理 Pinia ^2.3.1 全局学习状态(当前单词/难度/熟练度)
路由 Vue Router ^4.5.0 学习/词库/统计三个页面
样式 TailwindCSS ^3.4.17 原子化 CSS,快速布局
HTTP Axios ^1.7.9 前端调用后端 API
后端框架 FastAPI 0.104.1 高性能异步 Web 框架
ORM SQLAlchemy 2.0.23 数据库映射
数据库 SQLite 内置 零配置本地存储
数据校验 Pydantic 2.5.0 请求/响应模型校验
AI 接入 openai SDK + SiliconFlow 3.x 调用 DeepSeek-V3 生成单词
ASGI 服务器 Uvicorn 0.24.0 后端运行

选型理由:前后端分离、接口清晰;Vue3 + Pinia 的 Composition API 适合中小型交互应用;FastAPI 自带 OpenAPI 文档便于调试;SQLite 免部署适合单机学习工具;AI 生成用统一 OpenAI 协议,通过 SiliconFlow 平台低成本接入 DeepSeek-V3。


三、项目结构

复制代码
English-words-app/
├── backend/                        # FastAPI 后端
│   ├── app/
│   │   ├── main.py                 # 应用入口 + 全部 API 路由
│   │   ├── database.py             # SQLite 连接与 Session
│   │   ├── models.py               # ORM 模型(words / learning_records)
│   │   ├── schemas.py              # Pydantic 模型
│   │   ├── seeds.py                # 五个难度各 12 个种子单词
│   │   └── services/
│   │       ├── ai_service.py       # DeepSeek-V3 单词生成器(含备用词库)
│   │       └── word_service.py     # 词库/随机取词/学习记录/统计业务逻辑
│   ├── requirements.txt
│   └── words.db                    # SQLite 数据库
└── frontend/                       # Vue3 前端
    ├── index.html
    ├── vite.config.js
    ├── tailwind.config.js
    ├── postcss.config.js
    └── src/
        ├── main.js
        ├── App.vue                # 顶部导航 + 路由出口
        ├── style.css              # Tailwind 指令入口
        ├── api/client.js          # Axios 封装
        ├── stores/wordStore.js    # Pinia 全局状态
        └── components/
            ├── StudyView.vue      # 学习页(核心)
            ├── VocabView.vue      # 词库浏览页
            └── StatsView.vue      # 统计页

四、数据库设计

共两张表,设计上刻意保持简单:词库表负责"词是什么",学习记录表负责"你学得怎么样"

4.1 words 词库表

字段 类型 说明
id Integer, PK 主键
word String(100), unique, index 单词本身,唯一约束是"累计去重"的数据库层保障
phonetic String(100) 音标
meaning Text 中文释义
definition Text 英文定义
example Text 英文例句
example_cn Text 例句中文翻译
difficulty Enum(CET4/CET6/BEC/TOEFL/IELTS) 所属难度
pos String(20) 词性(noun/verb/adj/adv)
created_at DateTime 创建时间

4.2 learning_records 学习记录表

字段 类型 说明
id Integer, PK 主键
word_id Integer 关联单词(一对多:一个单词可有多次学习记录)
times_learned Integer, default 0 学习次数
last_learned DateTime 最近学习时间
proficiency Float, default 0 熟练度 0-100
created_at DateTime 创建时间

为什么不需要重新设计数据库? 最初需求是"每次随机词库都要累计起来但是要去重",两张表天然满足:words.word 唯一索引 + 服务层插入前查重 = 词库累计去重;learning_recordsword_id 独立记录熟练度 = 学习进度可追踪。后续所有功能迭代(优先未学词、排除刚看过的词、熟练度回传)都只改查询逻辑,不动表结构。


五、后端 API 设计

全部接口集中在 backend/app/main.py,CORS 已放开 5173/3000 两个开发端口。

方法 路径 功能 关键参数
POST /api/words/generate AI 生成词库(累计去重) difficulty, count(1-100)
GET /api/words 获取指定难度词库列表 difficulty, skip, limit
GET /api/study/random 获取随机学习单词 difficulty, exclude_id(排除刚看过的)
POST /api/study/record 记录学习进度 word_id, proficiency, mark_as_learned
GET /api/stats 学习统计(总词数/分难度词数) -
GET /api/health 健康检查 -

5.1 核心接口:随机取词的三级优先策略

这是解决"单词总是那几个、不跟词库走"的关键逻辑:

python 复制代码
@staticmethod
def get_random_word(db: Session, difficulty: DifficultyLevel, exclude_id: int = None) -> Word:
    """获取随机单词:优先未学过的词,其次未完全掌握的,最后兜底随机;可排除指定词避免连续重复"""
    base = db.query(Word).filter(Word.difficulty == difficulty)
    if exclude_id is not None:
        base = base.filter(Word.id != exclude_id)

    # 1. 从未学过(无学习记录)
    unlearned = base.outerjoin(
        LearningRecord, Word.id == LearningRecord.word_id
    ).filter(LearningRecord.id.is_(None)).all()
    pool = unlearned
    if not pool:
        # 2. 学过但未完全掌握(proficiency < 100)
        pool = base.outerjoin(
            LearningRecord, Word.id == LearningRecord.word_id
        ).filter(
            LearningRecord.id.isnot(None),
            LearningRecord.proficiency < 100
        ).all()
    if not pool:
        # 3. 兜底:当前难度全部单词
        pool = base.all()

    if not pool:
        return None
    return random.choice(pool)

三层语义:先把没学过的词喂给你 → 再复习学得不熟的 → 全都掌握了才随机复习exclude_id 由前端传入当前单词 id,点"下一个"不会连续抽到同一词。

5.2 熟练度回传

WordResponse 增加 proficiency 字段,查询时左连学习记录取最新熟练度:

python 复制代码
@staticmethod
def to_response_with_proficiency(db: Session, word: Word) -> dict:
    """将 Word 转为 WordResponse dict,并附加该词的学习熟练度"""
    data = WordResponse.from_orm(word).__dict__
    record = db.query(LearningRecord).filter(
        LearningRecord.word_id == word.id
    ).first()
    data["proficiency"] = record.proficiency if record else 0
    return data

六、前端页面与交互

6.1 学习页(StudyView.vue)------ 核心交互

页面元素与交互逻辑:

元素 交互 实现
难度标签(四级/六级/商务/托福/雅思) 点击切换难度并立即加载该难度单词 setDifficulty() 内部调用 getRandomWord()
单词卡片 显示单词/音标/词性;点击中文区翻转显示英文定义 CSS 3D 翻转
熟练度进度条 实时反映当前词的熟练度(0/75/100) 后端回传 proficiency
⏭️ 下一个 随机换词,排除当前词 exclude_id 参数
👍 比较熟悉 熟练度记为 75 并自动换下一个 markAsLearned(75)
✓ 完全掌握 熟练度记为 100 并自动换下一个 markAsLearned(100)
🤖 生成更多词库 调用 AI 生成 20 个新词(去重累计) generateWords(20)

AI 生成期间按钮进入 disabled 加载态;生成完成后随机展示一个(新生成或未学过的)单词:

6.2 词库页(VocabView.vue)

按难度切换标签,以卡片网格展示单词、音标、中文释义和英文例句,支持翻页拉取。

6.3 统计页(StatsView.vue)

展示五个难度的单词分布、总词库数与学习进度百分比(已学 / 总词库)。

6.4 全局状态(wordStore.js)

Pinia store 集中管理:currentWorddifficultyshowTranslationlearnedCountloadingproficiencyPercentage 计算属性,以及 setDifficulty/getRandomWord/markAsLearned/generateWords/fetchStats 五个动作。切换难度的关键修复:

js 复制代码
const setDifficulty = async (level) => {
  difficulty.value = level
  showTranslation.value = false
  await getRandomWord() // 切换难度后立即加载该难度单词
}

七、AI 词库生成:DeepSeek-V3 接入实战

7.1 接入配置

通过 SiliconFlow 平台(https://api.siliconflow.cn/v1)调用 deepseek-ai/DeepSeek-V3 模型,环境变量配置在 backend/.env

复制代码
OPENAI_API_KEY=sk-xxx
OPENAI_MODEL=deepseek-ai/DeepSeek-V3
OPENAI_BASE_URL=https://api.siliconflow.cn/v1

7.2 生成逻辑(ai_service.py)

python 复制代码
async def generate_word(self, difficulty: DifficultyLevel, db=None) -> dict:
    # 1. 查询该难度已有单词,拼进 prompt 提示模型避开(去重的第一道防线)
    existing = db.query(Word.word).filter(Word.difficulty == difficulty).all()
    if existing:
        exclude_words = "不要生成以下已存在的单词:" + "、".join([w[0] for w in existing][:80])

    # 2. 构造 prompt,要求返回 JSON
    prompt = f"""请生成一个{difficulty_prompts[difficulty]}的英语单词,返回JSON格式,包含:
    {{ "word": "...", "phonetic": "...", "meaning": "...", "definition": "...",
       "example": "...", "example_cn": "...", "pos": "..." }}
    要求:
    1. 返回格式必须是有效的JSON
    4. 不要返回markdown格式,直接返回JSON
    5. {exclude_words}"""

    # 3. 调用 DeepSeek-V3
    response = self.client.chat.completions.create(model=self.model, messages=[...], temperature=0.7)

    # 4. 清理模型返回(可能用 ```json 代码块包裹),再 json.loads
    content = response.choices[0].message.content.strip()
    if content.startswith("```"):
        content = content.strip("`")
        if content.startswith("json"):
            content = content[4:]
        content = content.strip()
    word_data = json.loads(content)
    word_data["difficulty"] = difficulty
    return word_data

三层去重防线

  1. Prompt 层:把该难度已有单词列表告诉模型"不要生成这些";
  2. 应用层 :插入前再次按 word 精确查重,存在则跳过;
  3. 数据库层words.word 唯一索引兜底,即使并发也不会重复插入。

7.3 容错降级

  • 未配置 API KeyWordGenerator 的 client 为 None,服务照常启动,生成接口回退到内置备用词库;
  • AI 调用失败_generate_fallback_word() 从本地备用词列表随机返回一个,保证接口永不报错;
  • 返回格式异常 :先剥离 ```json 包裹再 json.loads,失败则走备用词库;
  • 超时 :openai client 设置 timeout=60

八、关键问题修复记录(踩坑实录)

以下是本项目中真实遇到并解决的问题,按类型归类,供遇到同类坑的同学参考。

8.1 前端构建类

问题 根因 修复
Vite build 失败 App.vue 出现两个 <script setup> 块 + 残留 HelloWorld import 合并为单个 script setup,删除残留 import
Vite build 失败 StatsView.vue 有重复 script 块 合并去重
Tailwind 样式不生效 style.css 使用 @import 'tailwindcss/base' 非标准写法 改为标准 @tailwind base; @tailwind components; @tailwind utilities; 指令,并补齐 tailwind.config.js / postcss.config.js

8.2 后端依赖与运行时类

问题 根因 修复
openai 调用报 proxies 参数错误 openai 1.3.5 与 httpx 版本不兼容 升级 openai 至 3.0.0
升级后报 SocketTimeoutError openai 3.x 与旧版 aiohttp 冲突 升级 aiohttp 至 3.14.3
学习记录报 None 错误 times_learned 字段初始为 None,+= 1 崩溃 改为 (record.times_learned or 0) + 1
生成词失败 备用词库数据缺 difficulty 字段,触发 Pydantic 校验失败 备用词库补齐 difficulty
端口被占用 旧进程未退出 `lsof -ti:8000

8.3 功能逻辑类(本轮修复)

问题 根因 修复
点击四级/六级/商务标签,单词都一样 setDifficulty 只改标签状态,没重新加载该难度单词 切换难度后自动 getRandomWord()
词库"固定不变"、总抽到那几个词 随机取词不分学过与否,纯随机 三级优先策略:未学 → 未掌握 → 兜底
"下一个"可能连续抽到同一词 随机无排除逻辑 接口新增 exclude_id,前端传当前词 id
熟练度进度条一直 0% WordResponse schema 根本没有 proficiency 字段,后端从不返回 schema 增加字段 + to_response_with_proficiency() 查询学习记录回传
找不到增加单词量的入口 "生成词库"按钮只在无词时显示 学习页常驻"🤖 生成更多词库"按钮
生成接口重复词也报"成功生成" 未区分新增与跳过 先查重再插入,返回真实新增数与跳过数

九、运行与部署

9.1 后端

bash 复制代码
cd backend
python -m venv venv                 # 首次
source venv/bin/activate
pip install -r requirements.txt     # 首次
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000

首次启动会自动建表;需要种子词时执行 python app/seeds.py

9.2 前端

bash 复制代码
cd frontend
npm install          # 首次
npm run dev          # 开发,默认 http://localhost:5173
npm run build        # 生产构建

9.3 环境变量

backend/.env

复制代码
OPENAI_API_KEY=<你的 SiliconFlow Key>
OPENAI_MODEL=deepseek-ai/DeepSeek-V3
OPENAI_BASE_URL=https://api.siliconflow.cn/v1

不配置也能启动,AI 生成会走备用词库。

9.4 常见运维

bash 复制代码
# 清理 8000 端口占用
lsof -ti:8000 | xargs kill -9

# 查看后端日志
tail -f backend/nohup.out   # 按实际启动方式

十、实测数据

数据采集于 2026-08-13 10:11,真实运行环境。

指标 数值
总词库数 115
CET4 58
CET6 13
BEC 12
TOEFL 12
IELTS 20
学习记录数 47
已学单词数(去重) 47
AI 生成实测 一次点击从 103 增至 115(新增 12 个,均为去重后真实新增)
随机取词 不同难度返回独立词库;exclude_id 生效,不连续重复
熟练度回传 已学词返回真实值(如 abandon=100);未学词返回 0

十一、总结与后续优化方向

收获

  1. 全栈闭环:一个需求从数据库设计到前端交互完整落地,Vue3 + FastAPI 的组合开发效率高、调试链路清晰;
  2. AI 接入没那么神秘 :OpenAI 协议 + 第三方平台(SiliconFlow)把模型调用简化成一次 HTTP 请求,难点在输出格式容错业务去重
  3. "去重累计"是工程问题:单靠数据库唯一约束不够,要 prompt 提示 + 应用层查重 + 数据库约束三层配合;
  4. 看似简单的功能 bug 往往在数据链路断层:进度条 0% 不是前端问题,是后端 schema 根本没返回该字段------排查要顺着数据流从源头找。

可优化方向

  • 间隔重复算法:引入 SM-2 算法,按熟练度与遗忘曲线安排复习节奏,已掌握的单词降权出现;
  • 学习记录历史learning_records 增加时间维度查询,统计页展示熟练度分布趋势;
  • 批量生成优化:当前逐词调用大模型较慢(20 词约 30-60 秒),可改为一次 prompt 返回 5-10 个词批量入库,并加进度提示;
  • 用户体系:接入登录后,学习记录按用户隔离,词库支持多人共享;
  • 前端体验:单词朗读(Web Speech API)、答错重测、学习打卡等。
相关推荐
kaixin_啊啊1 小时前
香精近红外总体步骤概览
人工智能·matlab·近红外
u0103055271 小时前
昇腾Model-Agent端云协同架构解析
人工智能
ValhallaCoder1 小时前
Leetcode-hot100(2026.08.17)
python·算法·leetcode
IT爱学堂1 小时前
尚硅谷Java+AI大模型应用开发革新版本 2025年3月
java·开发语言·人工智能
W_326001 小时前
Python-OpenCV图像像素与通道:通道拆分合并、深浅拷贝
图像处理·人工智能·python·opencv·机器学习
CIO_Alliance1 小时前
AI基础系列(1)| 向量、矩阵、张量在AI中分别扮演什么角色?
大数据·人工智能·线性代数·ai·矩阵·企业cio联盟·企业级ai化转型
通问AI1 小时前
人形机器人量产技术笔记:从5500台出货看规模化路径
人工智能
让你三行代码QAQ1 小时前
AI大模型开发核心名词速览
python
无凭2 小时前
字节Agent框架 DeerFlow 的可观测性(二):运行中的中间事件是怎样到达前端的?
人工智能