简介:这份资源面向JavaWeb初学者与课程设计开发者,围绕“调取第三方API实现翻译功能”这一典型场景,提供一套可运行、可参考的完整项目源码。内容涵盖前端Cookie缓存、后端Servlet与JSP协同、Redis缓存翻译结果、MVC分层架构,以及API限流、错误处理与密钥安全等最佳实践,帮助读者理解前后端如何协作完成一次翻译请求的完整链路。压缩包共94个文件,约2.23MB,以xml配置、class字节码、jar依赖库、js脚本、css样式、java源码及jsp页面为主,另含课程设计报告文档与说明文件,目录结构清晰,便于按模块查阅。目前已有198人学习下载。通过分析与调试这些代码,读者可掌握HTTP协议、JSON数据格式、RESTful API设计原则及缓存优化思路,适合作为课程设计参考或JavaWeb入门练手项目。
1. 从表单到译文:JavaWeb 调翻译 API 到底在做什么
很多同学第一次接到「网页上做个翻译功能」的需求,第一反应是去找个 JS 库在前端硬翻,结果要么词库太小翻不准,要么跨域直接翻车。真正在生产里跑得住的方案,是让 JavaWeb 后端当中间人:浏览器把待翻译文本 POST 给 Servlet 或 Controller,后端拿着 API Key 去调第三方翻译接口,拿到 JSON 结果再回吐给前端。这样做的好处很实在——API Key 不出现在浏览器里,请求可以统一做限流、缓存和日志,前端只关心「发文本、收译文」这一件事。
这篇笔记就围绕「基于 javaweb 程序调取 API 实现翻译功能」这条主线,把选型、HTTP 调用、参数配置、密钥管理、踩坑排查一路讲透。适合正在做 JavaWeb 课程设计、企业后台多语言模块,或者想给现有系统加一个翻译入口的开发者。下面所有代码都是能直接放进 IDEA 跑的最小可复现版本,不依赖任何不存在的官方文档。
2. 选哪家翻译 API:免费额度、鉴权方式和接入成本对比
动手写代码之前,先把「调哪家」这件事定下来。翻译 API 的差异主要在三处:鉴权方式(有的用 AppID+密钥签名,有的用 Bearer Token)、免费额度、以及返回结构。选错了后面改起来很烦,所以这一步值得花十分钟。
2.1 主流翻译 API 的鉴权与额度对照
下面这张表是我自己在几个项目里实际用过的对比,参数以各家公开文档的通用形态为准,具体数值请以你注册时看到的为准,不要照抄。
| 服务 | 鉴权方式 | 免费额度形态 | 返回结构 | 接入难度 |
|---|---|---|---|---|
| 百度翻译 | AppID + 密钥,MD5 签名 | 每月一定字符量 | JSON,trans_result 数组 | 中,要算签名 |
| 有道智云 | AppKey + AppSecret,SHA256 签名 | 新用户试用额度 | JSON,translation 数组 | 中 |
| 讯飞星火翻译 | Bearer Token(API Key) | 按 token 计费 | JSON | 低 |
| 通用大模型 API | Bearer Token(sk- 开头) | 常有免费额度 | JSON,choices 结构 | 低,但要做提示词 |
从热搜里能看到大量unexpected status 401 unauthorized: incorrect api key provided: sk-svcac****这类报错,基本都是 Bearer Token 类接口的密钥问题。所以如果你只是想快速跑通,优先选 Bearer Token 鉴权的服务,省掉签名那一步。
2.2 为什么我一般优先选 Bearer Token 方案
签名类接口(百度、有道)需要你把 AppID、密钥、随机数、时间戳拼成一个字符串再做 MD5 或 SHA256,任何一步顺序错了就返回签名错误,排查起来很痛苦。Bearer Token 方案只需要在请求头里放Authorization: Bearer sk-xxxx,出错信息也直白——401 就是密钥不对,403 就是没权限,429 就是超频。
代价是 Bearer Token 类接口通常按 token 计费,长文本成本更高。所以我的习惯是:短句、后台管理类翻译用 Bearer Token 方案快速上线;大批量文档翻译再考虑签名类接口压成本。这个取舍没有标准答案,看你的量级。
2.3 用 IDEA 建一个最小 JavaWeb 工程
在 IDEA 里新建项目,选 Java Enterprise 或 Maven Webapp 都行。用 Maven 的话,pom.xml里至少要有 Servlet API 和一个 HTTP 客户端。我一般用 OkHttp,比原生 HttpURLConnection 少写很多样板代码。
<dependencies> <!-- Servlet API,Tomcat 提供,scope 用 provided --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- OkHttp 做 HTTP 调用,比原生简洁 --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> <!-- JSON 解析 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.43</version> </dependency> </dependencies>依赖说明:Servlet API 用provided是因为 Tomcat 自带,打进 war 包反而冲突;OkHttp 4.x 需要 Java 8 以上;fastjson 只是图方便,你也可以换 Jackson 或 Gson。版本号写的是我本地能跑通的组合,你升级时注意 OkHttp 4.x 的 API 和 3.x 有差异。
提示:IDEA 里如果
javax.servlet一直标红,检查 Project Structure 里有没有把 Tomcat 的 lib 加进依赖,或者确认provided依赖被正确识别。
3. 后端调翻译 API 的最小可运行代码
这一章是核心,把「Servlet 收请求 → 组装 API 请求 → 解析响应 → 返回前端」这条链路完整写出来。每一步我都会说清楚参数怎么改、失败时看哪里。
3.1 一个 Servlet 打通翻译请求全流程
先看完整代码,再逐段拆解。假设我们调的是一个 Bearer Token 鉴权的翻译接口,请求体是 JSON。
@WebServlet("/api/translate") public class TranslateServlet extends HttpServlet { // 从环境变量读密钥,绝不硬编码在代码里 private static final String API_KEY = System.getenv("TRANSLATE_API_KEY"); private static final String API_URL = "https://api.example.com/v1/translate"; private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) // 连接超时 .readTimeout(30, TimeUnit.SECONDS) // 读取超时,翻译可能慢 .build(); @Override 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 target = req.getParameter("target"); // 目标语言,如 en、ja if (text == null || text.trim().isEmpty()) { resp.getWriter().write("{\"code\":400,\"msg\":\"text 不能为空\"}"); return; } // 组装请求体 JSONObject body = new JSONObject(); body.put("q", text); body.put("target", target == null ? "en" : target); Request request = new Request.Builder() .url(API_URL) .addHeader("Authorization", "Bearer " + API_KEY) .addHeader("Content-Type", "application/json") .post(RequestBody.create( body.toJSONString(), MediaType.parse("application/json"))) .build(); try (Response response = client.newCall(request).execute()) { String result = response.body().string(); if (!response.isSuccessful()) { // 把上游错误原样透出,方便前端和日志定位 resp.setStatus(response.code()); resp.getWriter().write("{\"code\":" + response.code() + ",\"msg\":" + JSON.toJSONString(result) + "}"); return; } resp.getWriter().write(result); } catch (IOException e) { resp.setStatus(502); resp.getWriter().write("{\"code\":502,\"msg\":\"上游调用失败\"}"); } } }逻辑说明:doPost先做参数校验,空文本直接返回 400,避免浪费一次 API 调用。请求头里的Authorization是 Bearer Token 方案的关键,格式必须是Bearer加一个空格再加密钥,少这个空格就是 401。try-with-resources保证 Response 一定被关闭,否则连接池会泄漏。
参数说明:connectTimeout设 10 秒,readTimeout设 30 秒——翻译接口偶尔会因为长文本变慢,读超时给太短会误报失败。target参数做默认值兜底,前端不传就翻成英文。上游返回非 2xx 时,我把状态码和原始错误体一起透出,这样前端能看到401、429这些真实原因,而不是笼统的「翻译失败」。
3.2 密钥管理:为什么不能写死在代码里
热搜里incorrect api key provided出现频率极高,一半原因是密钥写死在代码里然后提交到了 Git,另一半是复制密钥时带了空格或换行。正确做法是用环境变量或配置中心。
# Linux / macOS 启动 Tomcat 前设置 export TRANSLATE_API_KEY="sk-你的真实密钥" # Windows PowerShell $env:TRANSLATE_API_KEY="sk-你的真实密钥"代码里用System.getenv("TRANSLATE_API_KEY")读取。这样密钥不进代码库,换环境只改环境变量。如果你用 IDEA 跑,在 Run Configuration 的 Environment variables 里填,别写进application.properties再提交。
注意:密钥前后如果有空格,
Bearer sk-xxx这种带尾空格的请求头会被服务端判为无效,报 401。复制后建议用trim()处理一遍。
3.3 前端页面怎么把文本发过来
后端通了,前端就简单了。一个 textarea 加一个按钮,用 fetch 发 POST。
async function doTranslate() { const text = document.getElementById('src').value; const target = document.getElementById('lang').value; const resp = await fetch('/api/translate', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `text=${encodeURIComponent(text)}&target=${target}` }); const data = await resp.json(); if (resp.ok) { document.getElementById('dst').value = data.translation || data.result; } else { alert('翻译失败:' + JSON.stringify(data.msg)); } }逻辑说明:这里用application/x-www-form-urlencoded是因为后端doPost里用的是req.getParameter,两者要对应。如果你后端改成读 JSON body,前端 header 和 body 也要跟着改,这是新手最容易对不上的地方。encodeURIComponent必须加,否则文本里的&、=会把参数截断。
4. 避坑与排查:翻译接口调不通时先看这几条
调 API 这件事,报错信息往往比代码本身更值得研究。下面五条是我和同事踩过的真实坑,按「现象 → 原因 → 解决」写。
4.1 401 Unauthorized:密钥问题的三种形态
现象:返回unexpected status 401 unauthorized: incorrect api key provided。
原因:一是密钥本身错了或过期;二是请求头格式不对,比如写成Authorization: sk-xxx少了Bearer;三是密钥带了首尾空格或换行。
解决:先把密钥单独拿出来用 curl 测一遍,确认密钥有效;再检查请求头拼接,用"Bearer " + key.trim();最后确认环境变量真的被读到了,可以在启动日志里打印密钥长度(不要打印密钥本身)。
4.2 400 报错:请求体格式和字段名对不上
现象:返回api error: 400,或者提示某个字段缺失。
原因:不同服务商的字段名不一样,有的用q,有的用text,有的用messages数组;还有的把语言代码写成zh-CN而不是zh。
解决:对照你选的那家文档,把请求体字段名和取值逐个核对。最省事的办法是先照文档用 curl 跑通,再把 curl 里的 body 原样搬到 Java 里。
4.3 429 限流:免费额度下的高频调用
现象:短时间连续翻译,突然开始返回 429 或「请求过于频繁」。
原因:免费额度通常有 QPS 限制,前端用户狂点按钮就会触发。
解决:后端加一层简单限流,比如用Semaphore或 Guava RateLimiter;前端按钮点击后置灰几秒。更彻底的做法是对相同文本做缓存,同一句话不重复调 API。
4.4 中文乱码:编码没统一
现象:翻译结果里中文变成问号或方块。
原因:请求或响应没设 UTF-8。Servlet 默认编码在某些容器里不是 UTF-8。
解决:req.setCharacterEncoding("UTF-8")和resp.setContentType("application/json;charset=UTF-8")两行都要写,缺一不可。前端页面也要<meta charset="UTF-8">。
4.5 超时与连接泄漏:OkHttp 没关 Response
现象:跑一段时间后接口越来越慢,最后报连接池耗尽。
原因:Response没关闭,或者 OkHttpClient 每次请求都 new 一个。
解决:用 try-with-resources 关 Response;OkHttpClient 做成单例,全局复用一个实例,它内部自带连接池。
5. 让翻译功能更耐用的三个进阶技巧
基础版跑通后,真正决定这个功能能不能上生产的,是缓存、批量处理和降级。这一章讲三个我实际用过的技巧。
5.1 用本地缓存挡住重复翻译
同一段文本被反复翻译是常态,尤其是界面上的固定文案。加一个带过期时间的本地缓存,能省下大量 API 调用。
// 简单的带过期缓存,生产可换 Caffeine 或 Redis private static final Map<String, CacheEntry> CACHE = new ConcurrentHashMap<>(); private static final long TTL = 10 * 60 * 1000L; // 10 分钟 static class CacheEntry { String value; long expireAt; CacheEntry(String v, long t) { value = v; expireAt = t; } } private String getCached(String key) { CacheEntry e = CACHE.get(key); if (e == null || e.expireAt < System.currentTimeMillis()) { CACHE.remove(key); return null; } return e.value; }逻辑说明:key 用「源文本 + 目标语言」拼成,避免不同语言互相覆盖。TTL 设 10 分钟是个折中,太短起不到作用,太长会返回过期译文。生产环境建议换 Caffeine,它自带淘汰策略,不用自己管内存。
5.2 批量翻译:一次请求翻多段文本
界面上有多个字段要翻时,逐条调 API 又慢又费额度。多数翻译接口支持传数组。
JSONArray arr = new JSONArray(); arr.add("第一段"); arr.add("第二段"); body.put("q", arr); // 字段名以你选的接口为准逻辑说明:把q从字符串改成数组,返回结果通常也是数组,按顺序对应。注意有些接口对数组长度有限制,比如一次最多 50 条,超了要分批。分批时记得保持顺序,别用并发打乱对应关系。
5.3 降级策略:API 挂了页面不能白屏
上游接口不可能永远可用。我的习惯是给翻译功能加一层降级:调用失败时返回原文并标注「翻译暂不可用」,而不是抛异常让整个页面崩掉。
| 场景 | 处理方式 | 用户感知 |
|---|---|---|
| 401/403 密钥问题 | 返回原文 + 告警日志 | 看到原文,运维收到告警 |
| 429 限流 | 返回缓存或原文 | 稍后重试可恢复 |
| 超时 | 重试一次,仍失败返回原文 | 基本无感 |
| 上游 5xx | 返回原文 + 记录 | 看到原文 |
这张表的核心思路是:翻译是增强功能,不是核心链路,任何情况下都不该让它拖垮主流程。密钥类错误必须告警,因为那是配置问题,不修会一直错;限流和超时可以靠重试和缓存扛过去。
最后说个我自己的习惯:每次接入一个新的翻译 API,我都会先用 curl 在命令行把鉴权、请求体、返回结构跑通,确认无误再写 Java 代码。这样出问题时能立刻分清是「接口本身的问题」还是「我代码的问题」,省掉大量来回猜的时间。密钥永远走环境变量,永远trim(),永远不提交到 Git。希望帮到你。
本文还有配套的精品资源,点击获取