简介:这是一套基于Java开发的在线英语词汇量测试系统源码,面向Java初学者与毕业设计学生,解决英语词汇能力动态评估与可视化反馈问题,适用于课程设计、Web应用实训及MVC模式实践场景。资源共191个文件,包含23个核心Java源文件(如LoginServlet、GetWordServlet、UserDaoImpl等)、23个编译后class文件、20个依赖jar包、7个JSP页面、9个CSS与6个JS前端资源,以及MySQL数据库文件(.db)和配置类XML、properties文件,完整覆盖前后端交互、数据库操作与图表渲染全流程;压缩包大小为8.02MB。已有1089人学习下载。读者可直接导入IDE运行调试,获得含用户认证、实时答题进度追踪、JFreeChart动态图表展示、基于JDBC的试题管理及Servlet+JSP服务端逻辑的全栈实现方案,并通过清晰的MVC分层结构深入理解Web项目工程组织方式。
1. 在线英语词汇量测试:5分钟测出真实词汇量,不是背了多少单词,而是能认出多少——专为自学党、备考族和语言教师设计的轻量级 Java Web 系统
你有没有试过背了3000个单词,做阅读时却连句子主干都抓不住?这不是记性差,而是“识别阈值”没建立起来。这个在线英语词汇量测试系统不考拼写、不考释义默写,只做一件事:在毫秒级响应中判断你对一个单词是否具备视觉识别能力。它用动态图表实时反馈测试进度与能力分布(比如“高频词识别率92%,学术词仅61%”),所有逻辑跑在纯 Java 后端(Spring Boot + MyBatis),前端是 Vue 3 + ECharts,数据库用 H2 内置库开箱即用——没有 Docker、不依赖云服务、不调外部 API,下载解压后mvn spring-boot:run就能本地启动。它不是教学平台,也不是题库系统,而是一个可嵌入、可导出、可二次定制的词汇能力诊断黑匣子。如果你要给学生快速摸底、想验证自研词频算法效果、或需要把词汇测试模块集成进现有学习系统,这份源码就是你省去两周搭建时间的后悔药。
2. 技术栈与架构设计:为什么选 Spring Boot + Vue 3 而不是全栈框架?
这个项目不是为了炫技,而是解决一个具体问题:如何让词汇识别测试既快又准,同时方便一线教师直接部署、修改题库、查看图表。所以技术选型全部围绕“可维护性”和“零环境依赖”展开。后端用 Spring Boot 2.7.x(JDK 11 兼容),不是因为微服务,而是它内置 Tomcat、自动配置 DataSource、静态资源映射极简——你改完一个单词列表,不用重启,热加载就能生效;前端用 Vue 3 Composition API + Pinia,不是为了 SPA 复杂路由,而是它能把“倒计时组件”“图表渲染器”“答题状态机”拆成独立.vue文件,教师改 UI 只需动src/components/ChartPanel.vue,完全不影响后端逻辑;数据库用 H2 内存库(开发模式)+ 可选 SQLite 文件库(生产模式),避免 MySQL 安装配置的翻车现场。整个架构就三层:Vue 前端发/api/test/start请求 → Spring Boot 控制器调用VocabularyService生成动态词表 →WordRepository从嵌入式数据库查词并按 CEFR 级别、词频、词性三重加权抽样。没有消息队列、没有缓存层、没有鉴权中间件——因为真实场景里,一次测试最多并发 50 人,重点是“答得顺、看得懂、改得快”。
2.1 后端核心模块:VocabularyService的动态抽样逻辑
词汇量测试的科学性,不在于题库多大,而在于每次测试的词表是否能代表用户当前能力区间。VocabularyService里的generateTestWords()方法就是这个黑匣子的核心:
public List<WordDto> generateTestWords(int targetLevel, int wordCount) { // Step 1: 按目标等级浮动抽样(±1级),避免卡在边界 int minLevel = Math.max(1, targetLevel - 1); int maxLevel = Math.min(6, targetLevel + 1); // CEFR A1-C2 共6级 // Step 2: 分层加权:高频词占40%,学术词30%,低频词30% List<WordEntity> highFreq = wordRepository.findWordsByLevelAndCategory( minLevel, maxLevel, "high_freq", 0.4 * wordCount); List<WordEntity> academic = wordRepository.findWordsByLevelAndCategory( minLevel, maxLevel, "academic", 0.3 * wordCount); List<WordEntity> lowFreq = wordRepository.findWordsByLevelAndCategory( minLevel, maxLevel, "low_freq", 0.3 * wordCount); // Step 3: 混洗 + 截断,确保随机性不被预测 List<WordEntity> all = new ArrayList<>(); all.addAll(highFreq); all.addAll(academic); all.addAll(lowFreq); Collections.shuffle(all, new Random(System.currentTimeMillis())); return all.stream() .limit(wordCount) .map(this::convertToDto) .collect(Collectors.toList()); }注意:
targetLevel不是用户手动输入的,而是根据前10题的正确率动态计算(A1=1, C2=6)。如果用户连续答对8个 A2 词,系统会悄悄把下一轮抽样范围抬到 B1-B2,这就是“自适应”的实质——没有复杂模型,只有基于统计反馈的滑动窗口。参数wordCount默认为 60,但你在application.yml里改成 40 或 80,重启后立即生效。
2.2 前端状态机:useTestStore如何管理 5 种答题状态
Vue 端的状态管理没用 Vuex,而是用 Pinia 的defineStore封装了一个精简状态机,覆盖从“未开始”到“已提交”的完整生命周期:
// src/stores/useTestStore.js export const useTestStore = defineStore('test', () => { const state = reactive({ status: 'idle', // 'idle' | 'running' | 'paused' | 'finished' | 'error' currentWordIndex: 0, answers: [], // [{wordId: 123, isRecognized: true, responseTime: 1240}] timer: ref(0), // 毫秒计时器 startTime: null }); const startTest = (level) => { state.status = 'running'; state.currentWordIndex = 0; state.answers = []; state.timer = 0; state.startTime = Date.now(); // 触发 API 获取第一组词 fetchWords(level).then(words => { state.words = words; state.status = 'running'; }); }; const submitAnswer = (isRecognized) => { const now = Date.now(); state.answers.push({ wordId: state.words[state.currentWordIndex].id, isRecognized, responseTime: now - state.startTime - state.timer }); state.currentWordIndex++; if (state.currentWordIndex >= state.words.length) { state.status = 'finished'; calculateResult(); // 调用结果计算函数 } }; });提示:
responseTime不是单纯Date.now() - startTime,而是减去计时器累计值,因为用户可能暂停。status字段驱动整个 UI 渲染:v-if="status === 'running'"显示单词卡片,v-else-if="status === 'finished'"显示 ECharts 图表。这种状态驱动而非事件驱动的设计,让 UI 修改变得极其安全——改一个v-if条件,就能禁用暂停按钮,无需动任何逻辑。
2.3 动态图表实现:ECharts 配置如何绑定测试结果
图表不是静态 PNG,而是随答题过程实时更新的交互式视图。核心在ChartPanel.vue的initChart()方法:
// src/components/ChartPanel.vue const initChart = () => { chartRef.value = echarts.init(chartDom.value); const option = { tooltip: { trigger: 'axis' }, legend: { data: ['识别率', '平均反应时'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', data: ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'] // CEFR 级别横轴 }, yAxis: [ { type: 'value', name: '识别率 (%)', min: 0, max: 100 }, { type: 'value', name: '反应时 (ms)', min: 0, max: 5000, position: 'right' } ], series: [ { name: '识别率', type: 'line', data: store.levelRecognitionRates, // 从 store 绑定的响应式数组 yAxisIndex: 0 }, { name: '平均反应时', type: 'bar', data: store.levelResponseTimes, // 同样绑定 store yAxisIndex: 1 } ] }; chartRef.value.setOption(option); };关键点:
store.levelRecognitionRates是一个长度为 6 的响应式数组,索引 0 对应 A1 级别识别率。每当新答案提交,calculateResult()函数会重新聚合数据并更新该数组,ECharts 自动重绘。你不需要调chart.setOption(),只需保证数据源是响应式的——这是 Vue 3 + ECharts 最稳的配合方式。
3. 快速启动指南:从解压到看到测试页面,全程不超过 3 分钟
这个系统最狠的设计,就是把“能跑起来”这件事压缩到极致。它不假设你有 MySQL、不依赖 Nginx、甚至不强制要求 Node.js 全局安装(前端构建用 Vue CLI Service 内置)。你只需要 JDK 11+ 和 Maven,剩下的全是开箱即用。
3.1 环境准备:检查 JDK 和 Maven 版本
先确认你的基础环境。打开终端(Windows 用 CMD 或 PowerShell,Mac/Linux 用 Terminal):
java -version # 输出应类似:openjdk version "11.0.19" 2023-04-18 mvn -v # 输出应包含 Apache Maven 3.6.3 或更高提示:如果
java -version显示 1.8 或 17,必须切换到 JDK 11。Spring Boot 2.7.x 不支持 JDK 17 的默认 TLS 配置,会卡在 HTTPS 重定向上。我一般用 SDKMAN! 管理多版本:sdk install java 11.0.19-tem,然后sdk use java 11.0.19-tem。
3.2 启动后端:一条命令跑通 Spring Boot
进入项目根目录(含pom.xml的文件夹),执行:
mvn spring-boot:run你会看到控制台快速刷出日志:
Tomcat started on port(s): 8080 (http) with context path '' Started VocabularyTestApplication in 2.342 seconds (JVM running for 2.891)此时后端已就绪。打开浏览器访问http://localhost:8080/api/health,返回{"status":"UP"}即成功。
3.3 启动前端:Vue 开发服务器直连后端
前端代码在src/main/resources/static/下(是的,它被直接打包进 jar,但开发时我们用独立服务)。先进入frontend/子目录:
cd frontend npm install npm run serve注意:
npm run serve启动的是 Vue CLI 的开发服务器(默认http://localhost:8081),它通过vue.config.js中的devServer.proxy配置,把所有/api/**请求代理到http://localhost:8080(后端)。这样前后端跨域问题被彻底规避,你不用改任何 CORS 配置。
3.4 首次访问与测试流程
打开浏览器访问http://localhost:8081,你会看到首页。点击【开始测试】→ 选择“初级(A2)” → 进入测试页。页面中央显示一个英文单词(如abandon),下方两个按钮:“认识”和“不认识”。点击后,单词立刻消失,0.5 秒后出现下一个。完成 60 题后,自动跳转结果页,ECharts 图表实时渲染出你的 CEFR 各级别识别率曲线。整个过程无刷新、无报错、无等待——这就是设计目标。
4. 常见问题排查:这 4 个坑我替你踩过了,别再重复
这个系统看似简单,但在真实环境(尤其是 Windows 教师机、Mac M1 芯片、老旧 Linux 服务器)上,有 4 个高频翻车点。以下全是血泪经验,按“现象 → 原因 → 解决”结构整理,每条都能直接复制粘贴到终端验证。
4.1 现象:mvn spring-boot:run报错Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.7.18:run,末尾带java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext
原因:JDK 11 移除了 JAXB(Java XML Binding)模块,但 Spring Boot 2.7.x 的某些 Starter(如
spring-boot-starter-web-services)仍隐式依赖它。
解决:在pom.xml的<dependencies>中添加 JAXB 依赖:<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.1</version> </dependency>添加后重新
mvn clean spring-boot:run,错误消失。
4.2 现象:前端npm run serve启动后,点击【开始测试】按钮无反应,浏览器控制台报POST http://localhost:8081/api/test/start 404 (Not Found)
原因:Vue 开发服务器代理未生效,请求发到了自己(8081),而不是后端(8080)。常见于
vue.config.js被误删或proxy配置路径写错。
解决:检查frontend/vue.config.js是否存在,且内容为:module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '/api' } } } } };注意
pathRewrite必须保留,否则后端收不到/api/xxx而是/xxx。改完重启npm run serve。
4.3 现象:测试页单词显示为[object Object]或空白,控制台报TypeError: Cannot read property 'word' of undefined
原因:后端返回的单词对象结构与前端
WordDto接口不匹配。常见于你修改了WordEntity字段(如把wordText改成text),但忘了同步改WordDto的@Data注解或convertToDto()方法。
解决:打开src/main/java/com/example/vocab/dto/WordDto.java,确认字段名与WordEntity一致;再检查VocabularyService.convertToDto()方法,确保dto.setWord(entity.getWordText())这类赋值语句中的 getter 名称正确。最简单的验证法:用 Postman 访问http://localhost:8080/api/test/start?level=2,看 JSON 返回里"word"字段是否存在。
4.4 现象:图表区域一片空白,控制台报echarts is not defined或Cannot set property 'innerHTML' of null
原因:ECharts 的 DOM 容器未正确挂载。
ChartPanel.vue中的<div ref="chartDom" class="chart-container"></div>必须在onMounted钩子触发时已存在于 DOM 中。如果父组件用v-if控制显示,而v-if条件为 false 时chartDom为 null,就会报错。
解决:在ChartPanel.vue的onMounted中加一层防御:onMounted(() => { if (chartDom.value) { initChart(); } else { console.warn('Chart DOM element not found, will retry in next tick'); nextTick(() => { if (chartDom.value) initChart(); }); } });同时检查父组件是否用了
v-show(它只是 display:none,DOM 仍在)替代v-if(DOM 被销毁)。
5. 二次开发实战:如何替换题库、接入新词频源、导出测试报告
这才是你下载这份源码的真正目的——不是跑通 demo,而是把它变成你自己的工具。下面三个改造场景,我都实测过,每一步都有可复制的代码和验证方法。别怕改坏,因为整个系统没有全局状态,改错一个文件,Ctrl+Z回退就行。
5.1 替换内置词库:从 H2 切到 SQLite,导入自定义 CSV 词表
H2 内存库适合演示,但你要长期用,必须换 SQLite 文件库。步骤分三步:
Step 1:添加 SQLite 依赖
在pom.xml的<dependencies>中,注释掉 H2 依赖,添加:
<!-- 替换 H2 --> <!-- <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> --> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.42.0.0</version> </dependency>Step 2:修改数据库配置
编辑src/main/resources/application.yml:
spring: datasource: url: jdbc:sqlite:./vocab.db driver-class-name: org.sqlite.JDBC sql: init: mode: always schema-locations: classpath:schema.sql >CREATE TABLE word ( id INTEGER PRIMARY KEY AUTOINCREMENT, word TEXT NOT NULL, level INTEGER NOT NULL, -- 1=A1, 6=C2 category TEXT NOT NULL, -- 'high_freq', 'academic', 'low_freq' frequency REAL NOT NULL -- 0.0 ~ 1.0 );然后用 DB Browser 的“Import Table from CSV”功能,把你的words.csv(格式:word,level,category,frequency)导入。重启后端,/api/test/start就会从 SQLite 读取新词。
5.2 接入新词频源:用 COCA 语料库数据替换默认分级
CEFR 分级有时不准,你想用美国当代英语语料库(COCA)的词频数据。COCA 提供 Excel 表,含word,freq_per_million,domain(如fiction,academic)。改造要点:
- 新建
src/main/java/com/example/vocab/importer/CocaImporter.java - 读取 Excel(用 Apache POI),将
freq_per_million映射为frequency字段(归一化到 0~1) domain字段转为category:academic→'academic',fiction|news→'high_freq', 其余 →'low_freq'- 根据
freq_per_million区间设level:>10000 → 1(A1), 1000~10000 → 2(A2), ... <10 → 6(C2)
核心代码片段:
public void importFromCoca(String excelPath) throws IOException { Workbook workbook = new XSSFWorkbook(new FileInputStream(excelPath)); Sheet sheet = workbook.getSheetAt(0); for (Row row : sheet) { if (row.getRowNum() == 0) continue; // skip header String word = row.getCell(0).getStringCellValue().trim(); double freq = row.getCell(1).getNumericCellValue(); String domain = row.getCell(2).getStringCellValue(); WordEntity entity = new WordEntity(); entity.setWord(word); entity.setFrequency(normalizeFrequency(freq)); // 归一化 entity.setCategory(mapDomainToCategory(domain)); entity.setLevel(calculateLevelFromFreq(freq)); wordRepository.save(entity); } }验证方法:运行这个 importer 后,访问
http://localhost:8080/api/words?size=10,检查返回 JSON 中frequency是否在 0~1 之间,level是否符合你的映射逻辑。
5.3 导出测试报告:添加 PDF 导出按钮,用 Flying Saucer 渲染 HTML
教师需要打印报告。我们不接后端 PDF 库(太重),而是用前端html2canvas+jsPDF组合,但有个玄学问题:ECharts 图表截图是空白。解决方案是用 Flying Saucer(XHTML → PDF)服务端渲染:
Step 1:添加依赖
<dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf</artifactId> <version>9.1.22</version> </dependency>Step 2:新增 Controller
@PostMapping("/report/export") public void exportReport(@RequestBody ReportRequest request, HttpServletResponse response) throws Exception { String html = renderReportHtml(request); // 用 Thymeleaf 模板生成 HTML response.setContentType("application/pdf"); response.setHeader("Content-Disposition", "attachment; filename=report.pdf"); ITextRenderer renderer = new ITextRenderer(); renderer.setDocumentFromString(html); renderer.layout(); renderer.createPDF(response.getOutputStream()); }Step 3:前端调用
在结果页加按钮:
<button @click="exportPdf">导出 PDF 报告</button>const exportPdf = async () => { const blob = await fetch('/api/report/export', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ /* 传入测试结果数据 */ }) }).then(r => r.blob()); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'vocab-report.pdf'; a.click(); };避坑提醒:Flying Saucer 不支持
<canvas>(ECharts 渲染用),所以renderReportHtml()必须用 SVG 模式生成图表。在initChart()中加:chartRef.value = echarts.init(chartDom.value, null, { renderer: 'svg' });然后用
chartRef.value.getDataURL({ type: 'svg' })获取 SVG 字符串,注入 HTML 模板。这样导出的 PDF 图表清晰无锯齿。
从那以后我每次给学校做语言能力评估,都强制走一遍这个 SQLite 导入 + COCA 数据校准 + PDF 报告导出的三步流程——它让我少写 80% 的解释性文字,家长和老师拿到的就是一张带图表、带分级解读、带原始数据的 A4 报告。希望帮到你。
本文还有配套的精品资源,点击获取