Java Web词汇量测试项目拆解:从Maven到Servlet的完整实践

发布时间:2026/9/11 10:29:16
Java Web词汇量测试项目拆解:从Maven到Servlet的完整实践 简介这是一套基于Java、JavaScript和HTML构建的英语词汇量在线测试系统源码面向英语学习者、Java Web初学者及测试系统开发人员可用于搭建词汇量评估平台也能用于学习前后端联调、会话管理和基础工程构建。压缩包共60个文件其中包含12个Java源文件、8个JavaScript文件、6个HTML文件以及PNG/JPG图片、XML配置、JSON配置和Maven相关文件等整体约36.56MB。Java源文件用于处理用户验证、成绩记录及数据交互等后端逻辑JavaScript实现答题校验、实时计分和页面动态效果HTML负责组织题目与表单结构图片素材则为测试界面和题目配图提供视觉支持。项目目录清晰附有readme.txt和pom.xml便于理解项目结构和快速启动。通过阅读这套源码能够掌握Java、JavaScript、HTML三种语言在Web项目中的协作方式了解在线测试系统的设计与开发流程也方便在此基础上扩展题库、增加用户管理或统计分析功能。目前已有358人学习下载。1. 词汇量测试这个 Java Web 项目拆开能学到什么词汇量测试这个需求乍看就是出题、作答、给分好像半天就能写完。但当题库要随机、成绩要落库、用户要登录时项目很快变成一场前后端状态管理的拉锯战。这个基于 Java 服务器端、JavaScript 交互层和 HTML 结构层的英语词汇量测试源码恰好把这条链路完整跑了一遍Maven 管依赖和构建12 个 Java 类处理后端逻辑8 个 JavaScript 文件负责答题交互6 个 HTML 页面承载测试界面12 张 PNG 配图补充题目上下文。适合刚学完 Java Web 想练手完整流程的人也适合在开发背单词工具但还在纠结答题流程怎么设计的工程师。下面从工程结构、出题流程、成绩落库和本地调试四个方向拆把接口设计和参数边界说清楚。2. 工程结构拆解从 pom.xml 到 resources 下的静态文件拿到源码包后很多人习惯先点开src/main/java但真正决定这个项目能不能跑起来的是根目录里的pom.xml。一个 Java Web 项目在 IDEA 里看起来再光鲜最后都要被 Maven 的坐标、依赖和打包方式拉回现实。2.1 Maven 工程怎么认出这是个 Web 项目pom.xml 里的第一个重点是packaging标签。如果是 jar说明这是一个可独立运行的纯 Java 应用如果是 war说明它需要部署到 Tomcat、Jetty 这类 Servlet 容器里。从项目包含 HTML 和 JavaScript 静态资源这一点看war 是更常见的形态。第二重点是依赖范围Servlet API 通常在编译期需要运行期由容器提供所以 scope 是 provided数据库驱动则往往声明为 runtime避免在代码里出现硬编码。下面这段是常见的 Maven Web 项目骨架具体版本号以仓库里的实际 pom.xml 为准project modelVersion4.0.0/modelVersion groupIdcom.vocabulary/groupId artifactIdvocabulary-test/artifactId version1.0.0/version packagingwar/packaging dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.27/version scoperuntime/scope /dependency /dependencies /project这里给出的版本号只是示例。在实际项目里要以 pom 中锁定的版本为准尤其是 MySQL 驱动的大版本要跟本地数据库版本兼容比如 MySQL 5.7 用 8.0 驱动虽然能连但时区参数不配置会直接报错。packaging 选择 war 还有一个好处前端 HTML 和 JavaScript 可以直接放在 webapp 或 resources/static 目录下由容器统一对外提供不用像前后端分离项目那样单独搭 Node 服务。2.2 目录结构与 59 个文件的分布从文件数量看这属于一个轻量级项目12 个 Java、8 个 JavaScript、6 个 HTML、12 个 PNG共 59 个文件。这样的体量最适合用来理解一个 Web 应用的最小完整形态。我一般会把这类项目整理成下面这个结构源码包里的实际布局可能略有差异但职责分层基本一致vocabulary-test ├── pom.xml └── src └── main ├── java │ ├── controller # 处理请求的 Servlet │ ├── service # 测试逻辑与成绩计算 │ └── dao # 数据库访问 ├── resources │ ├── static │ │ ├── html # 登录页、测试页、结果页 │ │ ├── js # 题目加载与交互脚本 │ │ └── images # PNG 配图 │ └── db.properties # 数据库连接配置 └── webapp └── WEB-INF └── web.xml静态资源放在 resources/static 下是 Spring Boot 的习惯而传统 Servlet 项目更常见的是放在 webapp 下。这个源码包既有 resources 又有 Java说明它可能采用了其中一种混合方式。区分方法很简单看 web.xml 里有没有配置 Servlet 映射或者看 Java 代码里读取文件用的是ResourceUtils还是ServletContext.getResourceAsStream。下面的表格是这 59 个文件按职责的归类能帮你快速定位入口文件类型数量实际职责Java 源文件12登录验证、出题接口、成绩存储、会话管理JavaScript8请求题库、渲染题目、答案校验、进度展示HTML6用户登录、测试说明、答题页、结果页等PNG 图片12题目配图与界面图标2.3 .idea 与 .gitignore 里能挖出的信息我看到文件清单里有.idea目录下的 uiDesigner.xml、sqldialects.xml、vcs.xml、misc.xml 和 dataSources.xml。这些文件不是源码但信息密度很高。dataSources.xml记录了你在 IDEA 里配置过的数据库连接串和驱动能直接看出项目用的是 MySQL 还是 SQLitesqldialects.xml告诉你 SQL 方言这是后来排查 SQL 语法报错的重要线索。.gitignore则在告诉你哪些文件不该进版本控制。常见问题是把 dataSources.xml 一起提交导致别人拉下代码后多出本地不需要的数据源配置。正确的做法是只保留项目必须的源码和 pom.xml把.idea整体忽略。3. 出题与答题流程后端题库接口与前端状态管理题目从数据库到用户点击要经过“查询题库 → JSON 序列化 → 前端渲染 → 选答案 → 判分”五个步骤。每一步都有容易出错的地方比如答案字段泄漏到前端、随机函数不是真正的随机等。3.1 题库表一道题最少需要哪些字段一道题从呈现到判分最少需要题干、选项、正确答案和可选配图。如果将选项拆到单独的表会引入额外的关联查询对 59 个文件的轻量项目来说没有必要。常见做法是把四个选项并排放在同一条记录里用 question 表的四个字段保存。这样查询时一次 IO 就能拿到整题也方便做随机抽取。表结构可以这样设计CREATE TABLE question ( id INT PRIMARY KEY AUTO_INCREMENT, word VARCHAR(64) NOT NULL, meaning VARCHAR(128) NOT NULL COMMENT 题干配图, option_a VARCHAR(128) NOT NULL, option_b VARCHAR(128) NOT NULL, option_c VARCHAR(128) NOT NULL, option_d VARCHAR(128) NOT NULL, answer TINYINT NOT NULL COMMENT 正确选项 1-4, image VARCHAR(128) DEFAULT NULL COMMENT 配图文件名 );answer 字段用 1-4 表示 A-D而不是直接存答案内容这样前端在做答案比较时只需要判断selectedIndex question.answer不需要做字符串匹配减少因空格和大小写导致的不一致。image 字段存相对文件名真正访问时由后端拼出完整路径前端只拿到相对路径可以减少暴露服务器目录结构的风险。字段类型说明wordvarchar(64)英语单词题干主体meaningvarchar(128)中文释义用于出题时作为选项之一option_a - dvarchar(128)四个候选释义answertinyint正确选项取值为 1 到 4imagevarchar(128)配图文件名如ambition.png3.2 后端出题接口Servlet 返回 JSON题目表建好后需要一个接口把题目数据提供给前端。传统 Servlet 项目里通常是写一个QuestionServlet映射到/api/questions。它负责从数据库读取全部题目再用 Jackson 或 Gson 序列化成 JSON 返回。注意不要直接返回数据库里的原文而是过滤掉 answer 字段避免前端源码里泄露答案。我一般会创建一个只包含题目内容的视图对象VO把答案单独留在后端。WebServlet(/api/questions) public class QuestionServlet extends HttpServlet { private QuestionDao questionDao new QuestionDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { // 可选参数 limit控制本次测试题目数量默认为 10 String limitParam req.getParameter(limit); int limit 10; if (limitParam ! null) { try { limit Integer.parseInt(limitParam); } catch (NumberFormatException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, limit 必须是数字); return; } } ListQuestionVO questions questionDao.findRandom(limit); resp.setContentType(application/json;charsetUTF-8); new Gson().toJson(questions, resp.getWriter()); } }doGet 方法里先解析 limit 参数再做参数校验最后把 VO 列表序列化为 JSON。这里将数据类型转换单独 catch是因为用户传入limitabc时不能直接让 500 错误打回页面而是返回 400 提示请求参数有误。findRandom(limit)是 DAO 层方法SQL 一般用ORDER BY RAND() LIMIT ?但数据量大时RAND()全表扫描会有性能问题更好的做法是随机取主键再回表查询后面可以自己优化。3.3 前端动态渲染HTML 模板与 JavaScript 校验HTML 页面里只需要一个容器节点和四个选项区域剩下的交互由 JavaScript 控制。这里要注意的是题目选项应该是按钮而不是文字这样用户点击后能直接触发事件不需要额外的表单提交。div idquestion-area h2 idword/h2 img idquestion-image alt题干配图 / div idoptions button classoption>let questionList []; let current 0; let score 0; async function initQuiz() { const res await fetch(/api/questions?limit10); questionList await res.json(); renderQuestion(); } function renderQuestion() { const q questionList[current]; document.getElementById(word).textContent q.word; document.getElementById(question-image).src /images/ (q.image || default.png); const buttons document.querySelectorAll(.option); buttons.forEach((btn, index) { btn.textContent q[option_ String.fromCharCode(97 index)]; btn.dataset.index index 1; }); document.getElementById(progress).textContent (current 1) / questionList.length; } function handleOptionClick(btn) { const q questionList[current]; if (parseInt(btn.dataset.index) q.answer) score; current; if (current questionList.length) { renderQuestion(); } else { showResult(score); } }代码里String.fromCharCode(97 index)是一种把索引映射成 a/b/c/d 选项字段名的技巧这样不用写四行重复的 if。需要注意dataset.index是字符串比较时必须用parseInt或Number强转否则会出现2 2不成立的隐蔽 bug。3.4 计分规则正确率与词汇量等级的映射测试结束后的等级映射并没有统一标准项目源码里的规则可以在 service 层看到。一般做法是按正确率分级并用一个工具方法计算。public class VocabularyGrader { public static String grade(int correct, int total) { double rate (double) correct / total; if (rate 0.9) return A; if (rate 0.8) return B; if (rate 0.6) return C; return D; } }这种映射简单直观但实际产品中最好导出对照表给业务方确认。以下是几种常见的等级映射参考正确率区间等级词汇量参考90% - 100%A熟练使用80% - 89%B日常沟通无压力60% - 79%C尚需巩固0% - 59%D词汇储备不足映射表在 Java 中可以用枚举实现也可以用 Map 存放区间避免出现魔法数字。4. 用户登录与成绩记录Session 和 JDBC 的配合测试系统与官网静态页不同用户提交成绩后要能查到自己历史测试记录因此登录是必要环节。Java Web 里最轻量的方案就是 Session登录成功后在 session 里写入 userId需要鉴权的页面或接口通过 Filter 拦截。注意 Session 只适合单机部署如果以后要横向扩展需要换成 Redis 分布式会话但这是后话。4.1 Session 校验哪些接口需要登录才能访问常见做法是通过一个LoginFilter过滤/api/*下的受保护接口。宁可多拦截一个接口也不要放过一个因为测试记录属于用户隐私数据。WebFilter(/api/secure/*) public class AuthFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq (HttpServletRequest) req; HttpSession session httpReq.getSession(false); if (session ! null session.getAttribute(userId) ! null) { chain.doFilter(req, resp); } else { ((HttpServletResponse) resp).sendError(HttpServletResponse.SC_UNAUTHORIZED); } } }getSession(false)的核心作用是不会为无登录请求的客户端自动创建 session。如果这里不传 false每个机器人请求都会在服务器上留下一个空 session慢慢把内存耗尽。Filter 里只保留 session 属性判断业务逻辑不要写在这里。4.2 用户登录的 Servlet 实现登录接口一般用 POST 提交用户名和密码。密码不能明文存储至少要用 SHA-256 加盐。以下代码演示了登录校验的基本结构。WebServlet(/api/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { String username req.getParameter(username); String password req.getParameter(password); if (username null || password null || username.trim().isEmpty() || password.trim().isEmpty()) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, 用户名或密码不能为空); return; } User user userDao.findByUsername(username); if (user ! null user.getPassword().equals(hash(password))) { req.getSession(true).setAttribute(userId, user.getId()); resp.setStatus(HttpServletResponse.SC_OK); } else { resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, 用户名或密码错误); } } private String hash(String raw) { // 实际实现里要加盐并压缩多次这里省略 return Integer.toHexString(raw.hashCode()); } }登录成功时直接让容器创建 session并把 userId 放进去。错误提示只返回 401 状态码不区分“用户不存在”和“密码错误”这是为了防止账号枚举。上面示例里的hash方法只是示意真实项目中不能用String.hashCode()因为它既不抗碰撞也不固定正确做法是加盐 多次哈希比如 PBKDF2。4.3 成绩写入数据库PreparedStatement 防注入成绩记录表至少需要用户 ID、测试时间、答对数量和总题数。等级可以在展示时再算但也可以在写入时算好存起来。CREATE TABLE test_history ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, correct INT NOT NULL, total INT NOT NULL, grade VARCHAR(4) NOT NULL, test_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );写入逻辑用 PreparedStatement不要用字符串拼接 SQLpublic void saveHistory(Connection conn, int userId, int correct, int total, String grade) throws SQLException { String sql INSERT INTO test_history (user_id, correct, total, grade) VALUES (?, ?, ?, ?); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, userId); ps.setInt(2, correct); ps.setInt(3, total); ps.setString(4, grade); ps.executeUpdate(); } }参数占位符?的顺序必须和 SQL 中列的顺序一致四个 setXxx 调用分别绑定用户、答对数、题数和等级。使用 PreparedStatement 不仅避免拼接还能复用执行计划在高频写入时有性能优势。注意try-with-resources会自动关闭 PreparedStatement不需要手写 close。4.4 连接泄漏与乱码最常见的两个坑很多人写完第一个版本后测试几次就发现数据库连接耗尽。原因通常是conn没有在 finally 中关闭。前面的例子只关闭了 PreparedStatementConnection 如果是自己通过DriverManager.getConnection拿到的也必须关闭。建议统一用一个DbUtil工具类提供getConnection()和close(AutoCloseable...)方法并在每个 DAO 方法里调用。另一个高发问题是中文乱码。响应 JSON 时设置resp.setCharacterEncoding(UTF-8)还不够要放在getWriter()之前。对于 POST 请求Tomcat 默认按 ISO-8859-1 解析参数所以还要加上req.setCharacterEncoding(UTF-8)并且放在第一次读取参数之前。这两个顺序写反测试时会随机出现时而正常时而乱码的现象。问题表现检查位置连接泄漏运行几分钟后请求变慢或超时DAO 中 Connection 是否关闭中文乱码题目与选项显示为问号Servlet 的编码设置顺序Session 失效刷新后需要重新登录Filter 中是否误调用了 getSession(true)5. 本地验证用 curl 与 IDEA 快速定位接口问题项目部署到 Tomcat 后很多人习惯直接打开浏览器点页面但真正遇到问题时浏览器帮不了太多。用 curl 验证接口能快速把问题定位在后端还是前端。5.1 打包并启动在项目根目录执行以下命令跳过测试可以加快打包速度mvn clean package -DskipTests生成的 war 包在target/目录下。IDEA 里配置 Tomcat 时把 Application context 设为/这样前端接口路径/api/questions不用带项目名前缀和本地联调保持一致。5.2 用 curl 跑通登录和受保护接口启动后先用 curl 发起登录请求把 session 保存到本地文件curl -c cookies.txt \ -d usernameadminpassword123456 \ http://localhost:8080/api/login-c cookies.txt表示将响应中的 Set-Cookie 写入文件。随后请求受保护接口时用-b cookies.txt带上这个 cookiecurl -b cookies.txt \ http://localhost:8080/api/questions?limit3如果返回 JSON 数组说明登录、Session 和出题链路都正常。如果返回 401问题在 Session 或 Filter。如果返回 400说明 limit 参数解析异常。前端页面加载不出来时先看浏览器开发者工具 Network 面板里接口的响应状态码再决定是调 JS 还是调 Java。最后一个小技巧在 IDEA 的 Run Configuration 里给 Tomcat 设置 VM 参数-Dfile.encodingUTF-8能避免控制台日志里中文乱码干扰排错判断。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询