
简介这份资源面向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 签名每月一定字符量JSONtrans_result 数组中要算签名有道智云AppKey AppSecretSHA256 签名新用户试用额度JSONtranslation 数组中讯飞星火翻译Bearer TokenAPI Key按 token 计费JSON低通用大模型 APIBearer Tokensk- 开头常有免费额度JSONchoices 结构低但要做提示词从热搜里能看到大量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 APITomcat 提供scope 用 provided -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- OkHttp 做 HTTP 调用比原生简洁 -- dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version4.12.0/version /dependency !-- JSON 解析 -- dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version2.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;charsetUTF-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_KEYsk-你的真实密钥 # Windows PowerShell $env:TRANSLATE_API_KEYsk-你的真实密钥代码里用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;charsetUTF-8)两行都要写缺一不可。前端页面也要meta charsetUTF-8。4.5 超时与连接泄漏OkHttp 没关 Response现象跑一段时间后接口越来越慢最后报连接池耗尽。原因Response没关闭或者 OkHttpClient 每次请求都 new 一个。解决用 try-with-resources 关 ResponseOkHttpClient 做成单例全局复用一个实例它内部自带连接池。5. 让翻译功能更耐用的三个进阶技巧基础版跑通后真正决定这个功能能不能上生产的是缓存、批量处理和降级。这一章讲三个我实际用过的技巧。5.1 用本地缓存挡住重复翻译同一段文本被反复翻译是常态尤其是界面上的固定文案。加一个带过期时间的本地缓存能省下大量 API 调用。// 简单的带过期缓存生产可换 Caffeine 或 Redis private static final MapString, 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。希望帮到你。本文还有配套的精品资源点击获取