Fastadmin集成百度翻译API:后台内容翻译功能完整实现
一个月前我在维护的Fastadmin后台管理系统中接到一个需求运营录入的商品中文标题、卖点描述要能一键生成英文版本方便后续投放海外渠道。手动翻译当然不现实几十条数据还能靠人工几百上千条就是灾难。我第一反应是接一个现成的翻译API最终落地用的是百度翻译API。整个过程不复杂但签名规则、配置缓存、QPS限流这些坑不踩一次还真容易忽略。这篇把申请应用、Fastadmin后台配置、服务类封装、控制器接口、前端弹窗交互以及线上调试经验完整写出来给同样在Fastadmin里做内容翻译的朋友一个可以直接抄作业的参考。代码我基于Fastadmin 1.x和ThinkPHP5环境跑通大体上ThinkPHP5.0/5.1也都能用。1. 这个需求从哪来后台内容翻译的三个典型场景1.1 电商商品的多语言描述电商类项目是最常见的场景。我在做二手设备管理后台时商品表里已经有了中文标题、中文规格、中文卖点但平台要出海运营在录入设备时根本不会填英文。一开始让运营手动复制到翻译软件再粘贴回来效率低不说还容易漏字段。后来我把Fastadmin后台的商品编辑页加了一个“翻译”按钮点击后把当前商品的中文标题、规格、卖点全部带出来调用翻译API生成英文再回填到对应的name_en、spec_en、sell_point_en字段。运营只需要点一下再稍微润色一下专业术语就能发布。这类需求的特点是字段固定、单条数据字段多、实时性要求不高。非常适合在后端控制器里封装一个统一接口然后在前端用Fastadmin的表格操作事件去触发。1.2 资讯内容的自动翻译内容管理系统同样经常遇到。编辑写了一篇中文资讯需要同步发表到英文站、日文站但编辑团队里不是每个人都有外语能力。与其等翻译人员排期不如先在后台提供“机器翻译摘要”的功能。我在某个资讯项目的做法是在文章编辑页放一个“翻译摘要”按钮把当前文章的中文摘要送到百度翻译翻译成英文或日文后填入旁边的摘要多语言输入框。这里有一个交互细节很关键——翻译结果比较长如果直接塞进原来的输入框可能撑破弹窗布局。这就涉及到后面会专门讲的layer弹窗大小适配问题热搜里那个“fastadmin弹窗大小”其实说的就是这种事。1.3 存量历史数据的批量净化还有一种场景不是实时翻译而是要把老数据一次性补全。比如我接手的项目里有几十万条历史商品记录只有中文没有英文而业务方要求下个月全部同步到海外站。这种场景不能用后台按钮一条条点必须写脚本批量跑。最合适的办法是在Fastadmin里加一个ThinkPHP命令把待翻译记录分批读出来丢到队列里由worker进程慢慢调百度翻译API翻译完再更新数据库。这样做的好处是不影响后台正常使用也不会因为单次请求过多触发QPS限制。1.4 为什么我最终选百度翻译API市面上主流的翻译API有百度、阿里云、腾讯云我在确定方案前都简单对比过。Fastadmin这类框架的集成逻辑很简单后端能发HTTP请求能解析JSON就能接。但不同厂商的接入成本和限制差别挺大。维度百度翻译API阿里云机器翻译腾讯云机器翻译接入方式HTTP请求 MD5签名SDK或HTTP签名SDK或HTTP签名文档清晰度清晰有现成调试工具清晰但SDK较重清晰偏云API免费额度标准版有免费字符额度新用户有体验额度提供免费体验额度语言覆盖中英日韩法西俄等主流语言齐全多语言偏专业文档多语言偏社交娱乐Fastadmin集成难度低直接封装Http类即可中需要引入SDK中需要引入SDK我选百度的核心原因不是哪家更好而是它的签名规则最简单sign md5(appid q salt 密钥)不需要复杂的时间戳加签流程也不要求在服务器上额外装SDK。这对Fastadmin这种追求轻量的后台框架非常友好。2. 准备好了再动手应用申请与Fastadmin配置项落地2.1 申请百度翻译开发者应用第一步是登录百度翻译开放平台用百度账号进入控制台创建一个应用。创建时要选服务类型我选的是“通用翻译API”的标准版目前有免费的基础额度对后台人工点几次翻译的场景完全够用。创建完成后控制台会给出两个关键信息APP ID和密钥。这两个值要妥善保存尤其是密钥本质上就是密码泄露了别人就能用你的额度去调接口。申请过程中有一个设置经常被人忽略IP白名单。百度翻译API支持限制调用来源IP建议把线上Fastadmin所在服务器的公网IP填进去。本地开发调试的时候可以暂时不填但部署到正式环境后一定要绑定否则应用被盗用损失的是你自己的字符包。2.2 在Fastadmin后台添加翻译配置我不建议把APP ID和密钥硬编码到代码里原因很简单Fastadmin后台有现成的系统配置功能放到后台让运维或负责人自己维护换密钥时不用改代码重新上线。打开Fastadmin后台的“系统配置”新增一个配置组比如叫“百度翻译”然后添加三个字段配置名称字段标识类型百度翻译APP IDbaidu_translate_appid文本百度翻译密钥baidu_translate_secret密码默认目标语言baidu_translate_to下拉选择保存之后代码里通过config(site.xxx)就能读取。Fastadmin会把后台系统配置绑定到site配置组下所以访问方式是$appid config(site.baidu_translate_appid); $secret config(site.baidu_translate_secret); $defaultTo config(site.baidu_translate_to) ?: en;如果你不想用后台系统配置也可以直接编辑application/extra/site.php手动追加配置项return [ name 我的项目, app_name My Admin, // ... 其他配置 baidu_translate_appid 你的APPID, baidu_translate_secret 你的密钥, baidu_translate_to en, ];两种方式都行我个人的习惯是能用后台界面就绝不动文件。文件方式虽然直观但容易被团队里其他人误改并且文件一旦被覆盖配置就丢了。2.3 配置读取和缓存一个容易翻车的细节很多人在Fastadmin里改了系统配置发现业务代码里读到的还是旧值第一反应是代码写错了。其实大概率是配置缓存问题。Fastadmin运行时会缓存系统配置到runtime目录后台修改配置后不一定立刻生效。遇到这种情况去后台右上角“清除缓存”或者手动删除runtime下的缓存文件再刷新页面就好了。如果你直接编辑了application/extra/site.php这种文件配置通常能立刻读到但为了保险我仍然建议在后台系统配置页面随便保存一次触发配置重新加载。另外服务器上runtime目录必须有写权限否则Fastadmin的很多缓存机制会静默失败表现就是“改了没反应”。3. 核心封装一个可复用的BaiduTranslate服务类3.1 翻译接口签名规则理解百度翻译通用翻译API的完整请求URL是https://fanyi-api.baidu.com/api/trans/vip/translatePOST和GET都支持推荐用POST因为中文文本在GET里要做URL编码一旦编码错了签名就不对。请求参数主要有这几个参数含义q待翻译文本from源语言如 zh、en、autoto目标语言如 en、jp、korappid创建应用时拿到的APP IDsalt随机字符串每次请求尽量不同sign签名等于 md5(appid q salt 密钥)签名是整个接入过程最容易出错的地方。官方规则是四个值按顺序拼成一个字符串然后做MD5。这里有几个坑必须注意第一q必须是原始待翻译文本绝对不能是URL编码后的值。Text参数在请求时要编码但参与签名计算时必须用原始文本。第二拼接顺序必须是appid q salt 密钥不能调换成appid salt q 密钥。第三salt每次请求最好都生成一个随机值这样签名每次都不同也能防止请求被重放。举个例子如果APP ID是20230001文本是hellosalt是12345密钥是abcdefg那么参与MD5的字符串就是20230001hello12345abcdefg把这个字符串做MD5得到的值作为sign参数传上去。3.2 服务类完整实现Fastadmin基于ThinkPHP5自带了一个HTTP客户端就是think\helper\Http。我在项目里把它封装进一个统一的服务类后续所有需要翻译的地方都调这个类不直接写裸请求。文件路径我放在application/common/library/BaiduTranslate.php命名空间是app\common\library。这样无论是后台控制器、前台控制器还是命令行脚本都能直接use调用。?php namespace app\common\library; use think\helper\Http; class BaiduTranslate { protected $appid; protected $secretKey; protected $httpTimeout 10; public function __construct($appid , $secretKey , $httpTimeout 0) { $this-appid $appid ?: config(site.baidu_translate_appid); $this-secretKey $secretKey ?: config(site.baidu_translate_secret); if ($httpTimeout 0) { $this-httpTimeout $httpTimeout; } } /** * 翻译文本 * * param string|array $q 待翻译文本支持传入数组逐条翻译 * param string $from 源语言默认auto自动识别 * param string $to 目标语言默认en * return string|array */ public function translate($q, $from auto, $to en) { if (is_array($q)) { $result []; foreach ($q as $item) { $result[] $this-translate($item, $from, $to); } return $result; } $q trim($q); if ($q ) { return ; } $salt random_int(10000, 99999); $sign md5($this-appid . $q . $salt . $this-secretKey); $params [ q $q, from $from, to $to, appid $this-appid, salt $salt, sign $sign, ]; $url https://fanyi-api.baidu.com/api/trans/vip/translate; $response Http::post($url, $params, [timeout $this-httpTimeout]); if ($response false) { throw new \Exception(百度翻译API请求失败请检查服务器网络); } $data json_decode($response, true); if (!is_array($data) || isset($data[error_code])) { $code isset($data[error_code]) ? $data[error_code] : 0; $msg isset($data[error_msg]) ? $data[error_msg] : 未知错误; throw new \Exception(百度翻译接口返回错误 . $msg, $code); } $translated ; foreach ($data[trans_result] as $item) { $translated . $item[dst]; } return $translated; } }翻译接口返回的JSON结构里正常结果在trans_result数组里每一项有src和dst两个字段。src是原文dst是译文。官方接口一次可以传多条文本但为了日志和排错方便我一般是一条条翻译。这个类里有两个细节值得说一下。一个是random_int生成salt比用rand更可靠PHP 7后也建议用这个。另一个是配置文件读取放到了构造函数里调用方不需要关心配置从哪来只管传参。3.3 异常码与重试逻辑百度翻译API的响应里如果出现error_code说明请求失败。我在项目里搜到的真实报错集中在几个码上54001签名错误、52003未授权、54003访问频率受限。签名错误和未授权通常是代码或配置问题重试多少次都没用应该直接抛出异常让测试人员看到。而超时、频率受限这类临时性问题可以做有限重试。我在服务类里加了一个带重试的方法public function translateWithRetry($q, $from auto, $to en, $retry 3) { $attempt 0; $lastException null; while ($attempt $retry) { try { return $this-translate($q, $from, $to); } catch (\Exception $e) { $lastException $e; $attempt; // 只有这几种错误码值得重试 if (in_array($e-getCode(), [52001, 52002, 54003])) { usleep($attempt * 1000000); // 递增等待1秒、2秒、3秒 continue; } throw $e; } } throw $lastException; }注意重试间隔。百度翻译标准版的QPS非常低只有每秒1次重试太密集反而更容易触发限流。我这里的递增等待策略比较保守实测下来能缓解大部分高峰期抖动问题。4. 控制器接入与前端交互从后端接口到弹窗显示4.1 设计一个可用的翻译接口服务类封装好之后第二步就是给前端提供一个翻译接口。我在Fastadmin后台新建了一个控制器Translate放在application/admin/controller/Translate.php里面只写了一个方法。?php namespace app\admin\controller; use think\Exception; use app\common\library\BaiduTranslate; class Translate extends Backend { protected $noNeedRight [*]; public function translate() { if (!$this-request-isAjax()) { $this-error(仅支持Ajax请求); } $text $this-request-post(text, , trim); $from $this-request-post(from, auto, trim); $to $this-request-post(to, , trim); if ($text ) { $this-error(翻译内容不能为空); } $to $to ?: config(site.baidu_translate_to) ?: en; try { $translator new BaiduTranslate(); $dst $translator-translate($text, $from, $to); } catch (Exception $e) { $this-error($e-getMessage()); } $this-success(翻译成功, null, [dst $dst]); } }看到这里你可能会问为什么要单独建控制器而不是直接放在商品控制器里因为翻译本身是个通用能力商品能用文章能用分类也能用。单独暴露一个接口所有业务共用避免以后加一个模块就要复制一份翻译逻辑。出于安全考虑这个接口只接受Ajax请求并且Fastadmin默认登录后才能访问。如果再讲究一点可以把$noNeedRight [*]去掉在后台菜单权限里给每个角色单独配置是否允许调用翻译接口防止普通运营滥用你的字符包。4.2 表格按钮与layer弹窗大小适配接口就绪后前端怎么调就是关键。Fastadmin后台列表页一般用Table.api来初始化我想在商品列表里加一个“翻译当前行”的按钮让运营选中某条数据后点击按钮在弹出的文本域里改一下内容再点确定就拿到翻译结果。Fastadmin弹窗默认用的是layerlayer.prompt可以弹一个输入框出来但默认宽度不一定合适。项目里团队有不少人反馈“弹窗太小”“翻译内容长了被截断”其实就是没有主动控制弹窗大小。layer的area参数就是干这个用的layer.prompt({ title: 输入要翻译的内容, formType: 2, // textarea area: [600px, 300px], value: row.title || }, function (value, index) { layer.close(index); $.ajax({ url: translate/translate, type: POST, data: { text: value, from: auto, to: en }, dataType: json, success: function (res) { if (res.code 1) { layer.open({ type: 1, title: 翻译结果, area: [800px, 500px], content: div stylepadding:20px;word-break:break-all; res.data.dst /div }); } else { Layer.msg(res.msg); } } }); });这个就是热搜词“fastadmin弹窗大小”真正对应的解法。layer.open默认会按内容自适应高度文本长的时候容易出现滚动条错位、按钮被挤到看不见。显式设置area之后弹窗宽高固定内容再长也在容器内滚动后台体验会好很多。4.3 防重复请求与批量翻译交互还有个交互细节容易被忽略用户连点两次翻译按钮会同时发出两个请求不仅浪费API额度还可能因为并发触发限流。我在前端处理时给按钮加了loading状态请求结束前禁止二次点击。批量翻译场景也一样。不要在前端用for循环并发发送Ajax请求标准版QPS就1前端并发3个请求直接挂。正确做法是每次翻译完成后再发起下一次请求相当于一个简单的串行队列。function translateBatch(list, index, results) { if (index list.length) { return results; } $.ajax({ url: translate/translate, type: POST, data: { text: list[index] }, dataType: json, success: function (res) { if (res.code 1) { results.push(res.data.dst); } else { results.push(); Layer.msg(第 (index 1) 条翻译失败 res.msg); } translateBatch(list, index 1, results); } }); }批量翻译虽然会慢一点但稳定。如果实在需要更快可以考虑升级百度翻译API的付费版本或者走后面章节要讲的队列方案。5. 线上跑起来之后实测调试与常见报错对照5.1 一张错误码排查表接口上线后网上乱七八糟的报错往往比想象中多。我梳理了一份百度翻译API最常见的错误码排查表直接照着这个表定位问题基本能在五分钟内解决error_code含义常见原因处理方式52001请求超时文本太长、网络波动缩短文本、调大timeout、重试52002系统错误百度服务端异常延时后重试52003未授权用户APP ID/密钥错误、IP白名单未加核对配置绑定服务器IP54001签名错误sign拼接顺序错误确认是 appidqsalt密钥54003访问频率受限标准版QPS不足串

相关新闻

最新新闻

日新闻

周新闻

月新闻