☰
JavaWeb课程设计实战:Servlet调取翻译API与Redis缓存实现
2026/10/1 12:16:23 网站建设 项目流程

简介:这份资源面向JavaWeb初学者与课程设计开发者,围绕“调取第三方API实现翻译功能”这一典型场景,提供一套可运行的完整项目参考。内容涵盖前端Cookie缓存、后端Servlet与JSP协同、Redis缓存翻译结果、MVC分层设计,以及API限流、错误处理与密钥安全等实践要点,帮助读者理解HTTP协议、JSON数据格式与RESTful接口调用在真实项目中的落地方式。压缩包共94个文件,约2.23MB,以xml配置、class字节码、jar依赖、js脚本、css样式与java源码为主,另含jsp页面、html入口及课程设计报告文档,目录结构清晰,便于按模块查阅。目前已有197人学习。通过分析源码与调试流程,读者可掌握缓存优化、前后端交互与接口调用的排错思路,适合作为课程设计或自学练手项目。

1. 从一份 JavaWeb 课程设计压缩包说起:它到底能不能跑起来

打开这份基于 javaweb 程序调取 API 实现翻译功能.zip,第一眼看到的不是业务代码,而是一堆javax.jms.jar、javax.annotation.jar、javax.servlet.jsp.jstl.jar这类 Java EE 时代的依赖包,外加src/servlet、src/utils、web/WEB-INF、out/artifacts和一份web应用开发课程设计报告.docx。这不是一个 Spring Boot 项目,而是一个典型的、用原生 Servlet + JSP 手搓出来的 JavaWeb 课程设计。它的核心链路很清晰:前端页面收集待翻译文本,Servlet 接住请求,utils 里的工具类去调在线翻译 API,拿到 JSON 结果后回写页面,中间用 Cookie 和 Redis 做两级缓存。

适合谁看?如果你正在做 JavaWeb 课程设计、需要一份能交作业又能讲清楚原理的参考实现,或者你刚学完 Servlet/JSP 想找一个带真实 API 调用的练手项目,这份资源是够用的。但如果你期待的是开箱即用的生产级翻译服务,那得先接受它的定位——教学项目,依赖偏老,配置得自己补。下面按「先跑通、再优化、最后避坑」的顺序拆。

2. 环境搭建与项目导入:把压缩包变成能跑的 Web 应用

2.1 依赖结构与 JDK/Tomcat 版本选择

先看目录里的 jar 包清单:javax.servlet.jar、javax.servlet.jsp.jar、javax.servlet.jsp.jstl.jar、javax.persistence.jar、javax.ejb.jar、javax.jms.jar、javax.annotation.jar、javax.transaction.jar、javax.resource.jar。这套组合是 Java EE 5/6 时代的典型配置,说明项目没有用 Maven 或 Gradle 管理依赖,而是把 jar 直接丢在WEB-INF/lib下(压缩包里以1218_java lib形式呈现)。这意味着你不能指望 IDEA 自动帮你解析依赖树,得手动把这些 jar 加到模块的 Libraries 里。

JDK 选 8 最稳。JDK 11 之后javax.*包名逐步被jakarta.*取代,虽然 JDK 8 编译的字节码在高版本 JVM 上能跑,但 Tomcat 10 开始默认走 Jakarta EE 命名空间,javax.servlet直接找不到类。所以版本组合建议锁死:JDK 8 + Tomcat 8.5 或 9.0。Tomcat 9 仍然兼容javax.servlet,是这套代码的舒适区。

导入步骤不复杂,但有几个点容易翻车:

# 1. 解压后确认目录结构 unzip 基于javaweb程序调取API实现翻译功能.zip -d fanyi-project cd fanyi-project # 典型结构:src/ web/ out/ .idea/ fanyi.iml README.md # 2. 检查 web/WEB-INF 下是否有 web.xml 和 lib 目录 ls web/WEB-INF/ # 期望看到:web.xml lib/ classes/(或由 IDEA 编译输出到 out/artifacts)

逻辑说明:src放 Java 源码,web是 Web 资源根目录,WEB-INF/web.xml是部署描述符,lib放第三方 jar。out/artifacts/production是 IDEA 的编译输出,导入后可以删掉重新构建,避免旧 class 干扰。

参数说明:如果你用 IDEA,File → Project Structure → Modules里把src标记为 Sources,web标记为 Web Resource Directory,然后在Artifacts里新建一个Web Application: Exploded,输出目录指向out/artifacts/production。Libraries里把WEB-INF/lib下所有 jar 加进去。这一步不做,编译时javax.servlet.http.HttpServlet会直接报红。

2.2 在 IDEA 中配置 Tomcat 并跑通第一个 Servlet

配置 Tomcat 的入口在Run → Edit Configurations → + → Tomcat Server → Local。关键参数只有三个:Application server指向本机 Tomcat 安装目录,HTTP port默认 8080(被占用就改 8081),Deployment标签页里加一个Artifact,选刚才建的 exploded 包,Application context填/fanyi。

<!-- web/WEB-INF/web.xml 里应该有 servlet 映射,确认类似配置存在 --> <servlet> <servlet-name>TranslateServlet</servlet-name> <servlet-class>servlet.TranslateServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>TranslateServlet</servlet-name> <url-pattern>/translate</url-pattern> </servlet-mapping>

逻辑说明:servlet-class必须和src/servlet下的真实类名一致,包名不能错。如果项目用的是@WebServlet("/translate")注解方式,那web.xml里可能没有这段,两者选其一即可,不要重复映射,否则启动时报Servlet mapping conflict。

参数说明:url-pattern决定前端请求路径。假设 context 是/fanyi,那前端index.html里的请求地址应该是/fanyi/translate,少一层前缀就是 404。启动后浏览器访问http://localhost:8080/fanyi/index.html,能看到页面说明部署成功;点翻译按钮没反应,就按 F12 看 Network 里请求发到哪了。

常见做法是先在utils包里找到调 API 的工具类,把里面的 API Key 占位符换成自己的。如果用的是某翻译平台的 RESTful 接口,通常需要appid和key两个参数,缺一个就返回 401。这一步不补,页面能打开但翻译永远失败。

3. 翻译链路拆解:Servlet 调 API、JSON 解析与两级缓存落地

3.1 Servlet 接收请求并转发给翻译 API

整个后端的入口是一个继承HttpServlet的类,重写doGet或doPost。前端用fetch或XMLHttpRequest把待翻译文本发过来,Servlet 拿到参数后,拼装成目标 API 要求的请求格式。以常见的 RESTful 翻译接口为例,通常是 POST 一个 JSON body,带q(待翻译文本)、source、target三个字段。

// src/servlet/TranslateServlet.java 核心逻辑示意 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("application/json;charset=UTF-8"); String text = req.getParameter("text"); String from = req.getParameter("from"); // 如 "auto" String to = req.getParameter("to"); // 如 "en" // 1. 先查 Redis 缓存 String cacheKey = from + ":" + to + ":" + text; String cached = RedisUtil.get(cacheKey); if (cached != null) { resp.getWriter().write("{\"result\":\"" + cached + "\",\"from\":\"cache\"}"); return; } // 2. 缓存未命中,调 API String result; try { result = TranslateApiUtil.call(text, from, to); } catch (Exception e) { resp.setStatus(502); resp.getWriter().write("{\"error\":\"翻译服务暂时不可用\"}"); return; } // 3. 写回 Redis,设置过期时间 RedisUtil.setex(cacheKey, 3600, result); resp.getWriter().write("{\"result\":\"" + result + "\",\"from\":\"api\"}"); }

逻辑说明:先查缓存再调 API 是标准套路,能显著降低 API 调用量和响应延迟。from字段返回cache还是api,方便前端调试时确认缓存是否生效。异常分支返回 502 而不是 500,语义上更准确——是上游 API 的问题,不是本服务崩溃。

参数说明:RedisUtil.setex的第三个参数是过期秒数,设 3600 表示一小时。设太短缓存命中率低,设太长翻译结果可能过时(对翻译场景其实无所谓,但课程设计报告里可以写「根据业务热度调整」)。req.setCharacterEncoding("UTF-8")必须在getParameter之前调用,否则中文会乱码,这是血泪经验。

3.2 JSON 解析与前端 Cookie 缓存的配合

API 返回的通常是 JSON,解析方式取决于你引入的库。这份资源没有明确带 Gson 或 Jackson,常见做法是手动字符串截取或用org.json。如果WEB-INF/lib里没有 JSON 库,建议加一个gson-2.8.9.jar,比手写正则靠谱得多。

// utils/TranslateApiUtil.java 解析返回 JSON public static String call(String text, String from, String to) throws Exception { String url = "https://api.example.com/translate"; String body = String.format("{\"q\":\"%s\",\"source\":\"%s\",\"target\":\"%s\"}", text.replace("\"", "\\\""), from, to); HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("Authorization", "Bearer " + API_KEY); conn.setDoOutput(true); conn.getOutputStream().write(body.getBytes("UTF-8")); if (conn.getResponseCode() != 200) { throw new RuntimeException("API status " + conn.getResponseCode()); } String json = new String(conn.getInputStream().readAllBytes(), "UTF-8"); // 用 Gson 解析出 translatedText 字段 JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); return obj.get("translatedText").getAsString(); }

逻辑说明:text.replace("\"", "\\\"")是防止用户输入引号破坏 JSON 结构,虽然不完美但课程设计够用。Authorization头放 API Key,注意不要把它硬编码后提交到公开仓库。readAllBytes()是 JDK 9+ 的方法,JDK 8 下要用BufferedReader循环读。

参数说明:API_KEY建议从环境变量或web.xml的<context-param>读取,不要写死在源码里。前端 Cookie 缓存则由 JS 负责:用户点翻译后,先把text的 MD5 作为 key 存进 Cookie,下次同样输入先查 Cookie,命中就直接显示,连后端都不请求。Cookie 容量只有 4KB 左右,别存长文本,存个哈希标记就行。

3.3 Redis 缓存的键设计与限流策略

Redis 在这套链路里承担「跨用户共享缓存」的角色。同一个词被不同用户查,第一次调 API,后面全走 Redis。键的设计直接决定命中率:from:to:text的拼接方式简单直观,但text太长会让 key 膨胀。常见做法是对text做 MD5 再拼,长度固定且避免特殊字符。

// utils/RedisUtil.java 简易封装 public class RedisUtil { private static JedisPool pool = new JedisPool("localhost", 6379); public static String get(String key) { try (Jedis jedis = pool.getResource()) { return jedis.get(key); } } public static void setex(String key, int seconds, String value) { try (Jedis jedis = pool.getResource()) { jedis.setex(key, seconds, value); } } }

逻辑说明:JedisPool是连接池,避免每次请求新建连接。try-with-resources确保连接归还。如果 Redis 没启动,getResource会抛异常,整个翻译功能挂掉——所以生产上要加降级:Redis 连不上就直接调 API,不要让缓存层拖垮主流程。

参数说明:JedisPool默认连localhost:6379,改地址就改构造参数。限流策略可以在 Servlet 里加一个基于AtomicInteger的计数器,每分钟超过 N 次就返回 429。课程设计里写「令牌桶」或「滑动窗口」都行,但代码别太复杂,能讲清楚即可。

4. 避坑与排查:从 401 到乱码的五个真实翻车点

4.1 现象:调用 API 返回 401 Unauthorized,提示 incorrect api key

原因:API Key 没填、填错、或者请求头格式不对。有些平台要求Authorization: Bearer sk-xxx,有些要求把 key 放在 URL 参数里,还有的要求签名。课程设计里最常见的是直接复制了示例代码但没换 key。

解决:先确认utils里API_KEY常量是不是还是占位符。然后用 Postman 单独测一次 API,排除代码问题。如果 Postman 能通、代码不通,检查请求头字段名大小写——HTTP 头不区分大小写,但某些服务端实现会挑。最后确认 key 有没有多余空格。

4.2 现象:页面中文显示为问号或乱码

原因:三处编码没统一——前端 HTML 的<meta charset>、Servlet 的setCharacterEncoding、Tomcat 的URIEncoding。任何一处是 ISO-8859-1,中文就废。

解决:HTML 里写<meta charset="UTF-8">;Servlet 里req.setCharacterEncoding("UTF-8")和resp.setContentType("text/html;charset=UTF-8")都加上;Tomcat 的server.xml里 Connector 加URIEncoding="UTF-8"。三处齐了才稳。

4.3 现象:Redis 连接超时,翻译功能整体不可用

原因:代码里 Redis 查询没有 try-catch,Redis 一挂,异常直接冒泡到 Servlet,用户看到 500。课程设计演示时如果没先启动 Redis,必翻车。

解决:把 Redis 操作包在 try-catch 里,捕获JedisConnectionException后返回 null,让流程继续走 API 调用。缓存是加速手段,不是必需依赖,这个降级思路在报告里也是加分项。

4.4 现象:Tomcat 启动报 ClassNotFoundException: javax.servlet.http.HttpServlet

原因:Tomcat 10 默认用 Jakarta EE 9+,包名是jakarta.servlet.*,而项目代码和 jar 都是javax.servlet.*。版本不匹配。

解决:换 Tomcat 9.0.x,或者把项目迁移到 Jakarta 命名空间(改 import 和依赖,工作量大,课程设计没必要)。最省事的就是锁 Tomcat 9。

4.5 现象:前端请求 404,但 Servlet 明明写了

原因:url-pattern和前端请求路径对不上,或者 context path 没算进去。比如 context 是/fanyi,url-pattern是/translate,前端却请求/translate而不是/fanyi/translate。

解决:F12 看 Network 里实际请求 URL,和web.xml里的映射逐段比对。IDEA 的 Tomcat 配置里Application context填了什么,前端就要带什么前缀。改成相对路径translate也能避开这个问题。

5. 进阶技巧:把课程设计讲成能过答辩的技术方案

5.1 用 AOP 思路统一记录 API 调用量与缓存命中率

课程设计答辩时,老师最爱问「你怎么证明缓存有效」。与其口头说,不如在utils里加一个简单的统计类,用AtomicLong记录apiCalls和cacheHits,每处理一次请求就自增。然后在页面底部或单独一个/stats接口输出两个数字,命中率一目了然。

public class Stats { public static final AtomicLong API_CALLS = new AtomicLong(); public static final AtomicLong CACHE_HITS = new AtomicLong(); public static String report() { long api = API_CALLS.get(); long hit = CACHE_HITS.get(); long total = api + hit; double rate = total == 0 ? 0 : (hit * 100.0 / total); return String.format("总请求:%d API调用:%d 缓存命中:%d 命中率:%.1f%%", total, api, hit, rate); } }

逻辑说明:在 Servlet 的缓存命中分支里CACHE_HITS.incrementAndGet(),在调 API 分支里API_CALLS.incrementAndGet()。report()返回的字符串可以直接写进响应,或者存到application作用域供 JSP 读取。这个改动不到 20 行,但答辩时能直接甩数据。

参数说明:AtomicLong保证并发安全,比long加synchronized轻量。命中率低于 30% 说明 key 设计有问题,或者测试时每次输入都不一样——演示时记得用同一句话连点两次。

5.2 把 API Key 从源码里挪出去

硬编码 key 是课程设计的通病,也是答辩时容易被挑的点。最简单的改法是在web.xml里加<context-param>,Servlet 初始化时用getServletContext().getInitParameter("apiKey")读取。

<context-param> <param-name>apiKey</param-name> <param-value>${API_KEY}</param-value> </context-param>

逻辑说明:${API_KEY}是占位符,实际部署时在 Tomcat 的setenv.sh或 IDEA 的 VM options 里传-DAPI_KEY=sk-xxx,再用System.getProperty覆盖。这样源码里看不到真实 key,提交到公开仓库也不怕。

参数说明:如果嫌麻烦,至少把 key 写在一个config.properties里,.gitignore掉这个文件,仓库里只留config.properties.example。这个习惯我从第一次把 key 推到公开仓库后就强制自己养成了,每次新建项目先写.gitignore。

5.3 验证清单:答辩前跑一遍这五步

检查项操作预期结果
环境JDK 8 + Tomcat 9 启动无 ClassNotFound 异常
页面访问/fanyi/index.html页面正常渲染,无乱码
翻译输入「你好」点翻译返回英文结果,Network 显示 200
缓存再点一次同样输入响应里from字段为cache
降级停掉 Redis 再翻译仍能返回结果,不报 500

这张表我每次交付课程设计前都会走一遍,尤其是最后一条降级测试,很多同学只测正常流程,一停 Redis 就露馅。把这五步跑通,答辩时老师随便挑一个场景你都能接住。

从那以后我每次拿到这种课程设计压缩包,都先看WEB-INF/lib里的 jar 清单定版本,再找utils里的 API 调用类确认 key 的位置,最后才动手配 Tomcat。顺序反了,就会在环境上耗掉大半时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询