资讯动态

JavaWeb在线翻译全实现:Servlet、Redis缓存与翻译API整合

发布时间:2026/9/15 1:52:28 来源:尧图企业网站定制
简介一份面向JavaWeb课程设计的完整翻译功能实现源码包适合正在学习Servlet、JSP与MVC架构的开发者用来打通前端交互、后端API调用与缓存优化的完整链路。资源围绕在线翻译场景覆盖Cookie缓存、Redis缓存、限流与错误处理等关键实践并配有课程设计报告便于直接参考或二次开发。压缩包共94个文件包含java源码、class编译文件、jar依赖库、js/css前端资源、xml配置及docx报告等整体仅2.23MB结构清晰适合快速部署与调试。目前已有197人学习可作为理解JavaWeb请求处理、第三方API对接及缓存机制的落地样例。1. 一个翻译功能为什么值得用 JavaWeb 完整走一遍你在浏览器里输入一句中文点击“翻译”不到一秒看到英文结果。这个交互背后不是单个接口调用那么简单而是一条从 JSP 页面到 Servlet、再到远程翻译 API、最后经由 Redis 缓存返回的完整链路。我拆过一个 javaweb 课程设计项目代码里有 javax.servlet 依赖、Servlet 工具类、JSP 页面还有 Redis 缓存的身影说明它不是在玩具级地“调一个接口”而是把 Web 应用的核心知识点串进来了。这个项目的核心是前端接收用户输入后端封装 HTTP 请求调用翻译 API返回结果并在 Redis 和 Cookie 中做两层缓存。对于正在做课程设计的人它能让你一次性接触 Servlet 生命周期、HTTP 协议、JSON 解析和缓存策略对于有几年经验的开发者它的价值在于那些容易被忽略的细节比如签名编码顺序、API 限流后的重试边界、缓存 key 的粒度怎么设计。下面我把拆解过程、可复现代码和踩坑点完整写出来。2. 翻译 API 的选型、签名算法与接口协议2.1 选型为什么优先考虑百度翻译开放平台市面上的翻译 API 不少百度翻译开放平台、有道翻译云、腾讯云机器翻译是课程设计中最常出现的三家。我的选择标准是先看免费额度和接入成本。百度翻译标准版每月有免费调用量QPS 限制在每秒几次这个量级足够学习使用有道的签名方式类似但控制台配置项更多腾讯云适合已经在用腾讯云产品的情况。平台免费额度签名方式主要限制百度翻译标准版每月免费量MD5(appidqsalt密钥)QPS 被限制在每秒数次有道翻译按控制台应用额度签名由应用层生成需要先在云平台建应用腾讯云有免费体验包腾讯云 API v3计费维度较多这个表格不是让你照抄选型而是理解不同厂商的 API 设计思路是一样的用 AppID 标识调用者用密钥参与签名用 QPS 限制保证服务稳定性。实际触发限流时返回体里通常带“访问频率受限”之类的错误码比如百度翻译的 54003。所以选型阶段就要想好后端怎么应对这个错误而不是等上线了再补。2.2 请求协议POST 表单、UTF-8 与 MD5 签名百度翻译 API 的请求是 POST 到/api/trans/vip/translate参数以表单格式传递核心参数有q、from、to、appid、salt、sign。sign的算法是取appid q salt 密钥这个字符串做 MD5结果转成小写十六进制。最容易出错的是顺序q必须用原始文本不能在前面做 URL 编码或 HTML 转义后参与签名。public class SignUtil { public static String computeSign(String appid, String query, String salt, String secretKey) { String raw appid query salt secretKey; return md5(raw); } private static String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] bytes md.digest(input.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(MD5 not supported, e); } } }这段代码里的关键点是 UTF-8 编码。中文文本在签名前必须先编码成字节如果你的应用默认字符集是 GBK算出来的 MD5 会和服务器端不一致最终返回签名错误。另外salt一般用当前时间戳同一个时间戳如果重复使用并且请求内容相同签名也会相同这在低并发场景没问题但高并发时最好加上随机数。2.3 响应 JSON 结构与错误码映射成功响应的 JSON 格式通常是{ from: en, to: zh, trans_result: [ { src: Hello, dst: 你好 } ] }失败时不是 HTTP 500而是返回 HTTP 200 错误码例如{error_code:54001,error_msg:Invalid Sign}。这就意味着后端不能只看 HTTP 状态码必须解析响应体里的error_code字段。常见错误码如下错误码含义处理建议52001请求超时可以在业务层重试一次52002系统错误稍后重试54001签名错误检查拼接顺序和密钥54003访问频率受限增加后端限流或查 Redis 缓存58001语言方向不支持校验前端传入的 from/to这里有一个工程上容易忽略的点把错误码直接抛给前端是没有意义的用户只会看到“翻译失败”。比较好的做法是在后端把错误码翻译成用户可读的提示比如“请求太频繁请稍后再试”同时记录完整错误日志方便排查到底是谁触发了限流。2.4 JavaWeb 项目骨架与依赖准备课程设计版本的源码里典型结构是src/servlet放控制层src/utils放 HTTP 和签名工具web/js放前端资源web/WEB-INF/web.xml做 Servlet 映射。如果使用 Maven核心依赖如下dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdcom.google.code.gson/groupId artifactIdgson/artifactId version2.10.1/version /dependency dependency groupIdredis.clients/groupId artifactIdjedis/artifactId version4.4.3/version /dependency提示javax.servlet-api的 scope 设为provided因为 Tomcat 容器里已经有实现类如果不加这个 scope打 WAR 包时会把 servlet-api 也打进去轻则冲突重则启动报错。如果你拿到的项目是 Eclipse 直接导出的非 Maven 工程lib 目录里通常已经放了javax.servlet.jar、javax.annotation.jar等文件那就不需要再引入 Maven 依赖。3. Servlet 接收请求、调用翻译 API 与 Redis 缓存的完整实现3.1 分层设计为什么不在 Servlet 里直接写 HTTP 调用很多新手会把远程 API 调用代码直接写在doGet或doPost里一个方法几百行没法测试。实际项目中可以拆成四层Servlet 负责参数解析和 JSON 响应TranslateService 负责缓存判断和 API 调度HttpClientUtil 负责发起 POST 请求RedisCacheUtil 负责缓存读写。拆分的直接收益是换一家翻译厂商时只改 Service 和 ClientController 永远不动。层类名职责控制层TranslateServlet读取请求参数调用 Service写 JSON业务层TranslateService先查缓存再决定是否调 API工具层HttpClientUtil封装 POST、超时、流读取缓存层RedisCacheUtil基于 Jedis 的字符串读写和过期设置3.2 HttpClientUtil用 HttpURLConnection 发送 POST 表单请求不引入 Apache HttpClient 或 OKHttp 时Java 标准库的 HttpURLConnection 足够完成这个功能。下面的工具类接收 URL 和参数 Map返回响应字符串public class HttpClientUtil { public static String postForm(String url, MapString, String params, int timeoutMillis) throws IOException { HttpURLConnection conn (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod(POST); conn.setConnectTimeout(timeoutMillis); conn.setReadTimeout(timeoutMillis); conn.setDoOutput(true); conn.setRequestProperty(Content-Type, application/x-www-form-urlencoded; charsetUTF-8); String body params.entrySet().stream() .map(e - encode(e.getKey()) encode(e.getValue())) .collect(Collectors.joining()); try (OutputStream os conn.getOutputStream()) { os.write(body.getBytes(StandardCharsets.UTF_8)); } int code conn.getResponseCode(); InputStream is code 400 ? conn.getErrorStream() : conn.getInputStream(); try (BufferedReader reader new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { return reader.lines().collect(Collectors.joining(\n)); } } private static String encode(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8); } }这段代码里有两个关键参数timeoutMillis一般设 30005000 毫秒。太短容易因网络抖动失败太长会占住 Servlet 线程池。另一个是Content-Type必须写成表单格式因为翻译 API 接收的是application/x-www-form-urlencoded不是 JSON。如果你改成application/jsonAPI 会直接拒绝请求。3.3 TranslateServiceRedis 缓存命中、过期与穿透Redis 在这里的价值是减少 API 调用次数因为翻译接口的计费和限流都按调用次数算。缓存 key 我习惯设计成trans:{from}:{to}:{md5(q)}用原文的 MD5 生成 key避免长字符串直接暴露在 key 里。value 直接存翻译结果的 JSON 字符串。public class TranslateService { private static final int CACHE_TTL_SECONDS 86400; public String translate(String q, String from, String to) { String cacheKey buildCacheKey(q, from, to); String cached RedisCacheUtil.get(cacheKey); if (cached ! null) { return cached; } String result callRemoteApi(q, from, to); RedisCacheUtil.setex(cacheKey, CACHE_TTL_SECONDS, result); return result; } private String buildCacheKey(String q, String from, String to) { return trans: from : to : SignUtil.md5(q); } }setex设置了 24 小时过期时间这是为了避免 Redis 里存太多历史翻译结果导致内存膨胀。课程设计阶段的数据量不大24 小时足够如果做成公共服务建议改成“最近 7 天无访问自动淘汰”或者直接用 Redis 的EXPIRE设置不固定 TTL。另外要注意调用失败时不要把错误信息缓存进去否则后面请求会一直拿到错误结果。3.4 超时重试与错误码分流远程 API 调用总会遇到网络抖动或对方服务端异常。我的处理原则是只有超时类异常才重试而且最多重试一次业务错误码如 54003 限流则不要重试因为重试会放大压力。示例逻辑如下try { return service.translate(q, from, to); } catch (SocketTimeoutException e) { return service.translate(q, from, to); } catch (ApiLimitException e) { response.setStatus(429); response.getWriter().write({\error\:\翻译请求太频繁请稍后再试\}); }注意这里重试翻译请求是幂等的因为同样的输入会得到同样的结果不会产生重复数据。但如果你的 API 调用包含“写入”或“扣费”语义重试前必须确认上一次请求是否真的失败了。对翻译场景来说重试间隔建议大于 100 毫秒否则可能连续触发限流。4. JSP 与 JavaScript 协作Cookie 缓存、异步刷新和 MVC 边界4.1 前端为什么需要 Cookie 缓存翻译 API 有 QPS 限制用户反复翻译同一句话时即使 Redis 能扛住前端也仍然多了一次网络往返。更快的方式是把最近几次翻译结果存在浏览器 Cookie 里用户点击翻译按钮前先查本地。Cookie 总大小大约 4KB所以不要存整段长文章而是存最近 10 条或只存当前输入对应的结果。4.2 Cookie 读写与编码别把中文直接塞进 CookieJSP 页面本身是服务端渲染但翻译请求适合用 AJAX 完成避免整页刷新。下面这段 JavaScript 实现 Cookie 的读取和写入function getCookie(name) { const prefix name ; const parts document.cookie.split(; ); for (const part of parts) { if (part.indexOf(prefix) 0) { return decodeURIComponent(part.substring(prefix.length)); } } return null; } function setCookie(name, value, hours) { const expires new Date(Date.now() hours * 3600 * 1000).toUTCString(); document.cookie name encodeURIComponent(value) ; expires expires ; path/; }这里必须对 value 做encodeURIComponent因为翻译结果包含中英文、标点和换行这些字符在 Cookie 里会被切断或导致解析错误。path/也很关键如果不写Cookie 只对当前 JSP 路径生效换到其他 Servlet 路径就读取不到。然后通过fetch调用后端async function doTranslate() { const q document.getElementById(q).value.trim(); if (!q) return; const cachedKey t_ q; const cached getCookie(cachedKey); if (cached) { document.getElementById(result).innerText cached; return; } const param new URLSearchParams(); param.append(q, q); param.append(from, auto); param.append(to, zh); const resp await fetch(translateServlet, { method: POST, body: param }); const json await resp.json(); const dst json.trans_result ? json.trans_result[0].dst : 翻译失败; document.getElementById(result).innerText dst; setCookie(cachedKey, dst, 24); }这里的URLSearchParams会在发送请求时自动把 body 编码为表单格式Servlet 端用request.getParameter(q)能直接读取。注意fromauto是让 API 自动识别源语言翻译有效但会降低一点点准确性如果是固定中译英建议在前端写死。4.3 Cookie 与 Redis 的双层缓存边界Cookie 和 Redis 属于两层缓存二者不一定强一致。Redis 更新了某个翻译结果用户浏览器里的 Cookie 可能还是旧值。我的处理方式是Cookie 有效期设短一点比如 24 小时用户手动清缓存后请求会落到 Redis此时两个缓存会有短暂不一致但翻译结果本身变化不频繁所以这个策略是可接受的。比较项CookieRedis存放位置浏览器后端服务器容量限制约 4KB由内存和配置决定生命周期expires 控制EXPIRE 控制适用数据当前用户最近少量记录全站高频翻译结果4.4 Servlet 返回 JSONGson 与 UTF-8 编码Servlet 输出 JSON 时必须设置Content-Typeapplication/json; charsetUTF-8否则中文会出现乱码。推荐用 Gson 把 Map 转成 JSON而不是手动拼接字符串MapString, Object result new HashMap(); result.put(from, from); result.put(to, to); result.put(trans_result, List.of(Map.of(src, q, dst, translated))); response.setContentType(application/json; charsetUTF-8); response.getWriter().write(new Gson().toJson(result));这段代码里的Map.of要求 Java 9 以上如果你在 JDK8 环境下开发换成LinkedHashMap并按顺序 put 即可。注意List.of创建的列表不可变如果你后面要对trans_result做修改需要额外new ArrayList(...)。这不算大问题但如果是老项目使用的 javax.servlet 4.0通常 Tomcat 8.5 JDK8 就能跑不需要升级 JDK。5. 进阶技巧API 密钥安全、curl 全链路验证与批量翻译拆分5.1 把密钥请出源码用 web.xml 配置环境参数课程设计里最常见的隐患是把 AppID 和密钥硬编码在 Servlet 常量中一旦源码被提交到 Git 仓库密钥就等于公开了。常见做法是放到web.xml的context-param部署时替换成真实值context-param param-nametranslate.api.appid/param-name param-value你的APPID/param-value /context-param context-param param-nametranslate.api.secret/param-name param-value你的SECRET/param-value /context-param然后在 ServletContext 初始化时读取存到配置对象中。相比硬编码这样至少能保证代码和配置分离。如果项目要交到 GitHubweb.xml里也不要写真实密钥可以用占位符。更工程化的方案是环境变量或 JNDI但对课程设计来说做到这一步已经足够展示安全意识。5.2 用 curl 验证完整链路确认 Redis 是否命中部署到 Tomcat 后先用 curl 绕过浏览器和 JavaScript直接验证后端接口。假设项目上下文路径是/translateServlet 映射为translateServletcurl -s -X POST http://localhost:8080/translate/translateServlet \ -d qhellofromautotozh第一次请求会打到远程 APIRedis 中写入缓存第二次执行相同命令后端直接从 Redis 读取。想确认 Redis 里真的有缓存可以执行redis-cli --scan --pattern trans:*如果看到对应qhello的 key说明缓存生效。还可以用redis-cli ttl trans:en:zh:5d41402abc4b2a76b9719d911017c592查看剩余过期时间。验证时注意 curl 的-d默认使用表单编码正好匹配 Servlet 的读取方式如果用了-H Content-Type: application/json后端就要改成从 request body 里解析 JSON两者不要搞混。5.3 批量翻译时的 QPS 控制与文本拆分翻译 API 对单次请求的文本长度有限制长文章需要按段落切分成多个请求。直接 for 循环调用会把 QPS 拉满触发 54003 限流。常见的做法是固定线程池并发同时用同步阻塞控制请求间隔ExecutorService pool Executors.newFixedThreadPool(4); for (String paragraph : paragraphs) { pool.submit(() - service.translate(paragraph, zh, en)); Thread.sleep(120); } pool.shutdown();这个 120 毫秒的间隔意味着每秒最多约 8 个请求具体数值要根据你申请的 QPS 配额调整。如果 API 返回 54003说明间隔还不够大把 120 改成 150 或 200 毫秒再试。注意Thread.sleep会让当前线程暂停如果放在主线程里页面会等待所有翻译任务完成才返回建议把整个批量过程放到独立的后台线程中执行再用轮询接口返回进度。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价