Hyperf 百度翻译接口实现方案

保留 HTML/XML 标签结构,仅翻译文本内容,避免破坏富文本格式。采用「HTML 解析 → 文本提取 → 批量翻译 → 回填」的流程。

百度翻译集成方案:富文本内容翻译系统
HTML 解析 + 百度翻译 API 集成

文件结构

app/
├── Controller/
│   └── TranslationController.php
├── Service/
│   ├── BaiduTranslator.php
│   └── HtmlParser.php
├── Job/
│   └── TranslateContentJob.php
├── Model/
│   └── Article.php
config/
│   └── autoload/
│       └── translation.php

代码实现

  1. 配置文件 config/autoload/translation.php
<?phpreturn ['baidu' => ['appid' => env('BAIDU_TRANSLATE_APPID', ''),'secret' => env('BAIDU_TRANSLATE_SECRET', ''),'api_url' => 'https://fanyi-api.baidu.com/api/trans/vip/fieldtranslate','chunk_size' => 30, // 每次翻译的文本段落数'max_length' => 5000, // 单次请求最大字节数'preserve_tags' => 'p,div,span,h1,h2,h3,h4,h5,h6,ul,ol,li,table,tr,td,th,img,video,a,strong,em,b,i,u','ignore_tags' => 'code,pre,script,style',],
];
  1. 模型 app/Model/Article.php
<?phpdeclare(strict_types=1);namespace App\Model;use Hyperf\DbConnection\Model\Model;/*** @property int $id * @property string $title * @property string $content * @property string $en_content * @property int $translation_status 0-未翻译 1-翻译中 2-翻译完成 3-翻译失败* @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at */
class Article extends Model
{const STATUS_PENDING = 0;const STATUS_PROCESSING = 1;const STATUS_COMPLETED = 2;const STATUS_FAILED = 3;protected ?string $table = 'articles';protected array $fillable = ['title', 'content', 'en_content', 'translation_status'];protected array $casts = ['id' => 'integer','translation_status' => 'integer','created_at' => 'datetime','updated_at' => 'datetime'];
}
  1. HTML 解析器 app/Service/HtmlParser.php
<?phpdeclare(strict_types=1);namespace App\Service;use voku\helper\HtmlDomParser;class HtmlParser
{public function extractTextNodes(string $html): array{$dom = HtmlDomParser::str_get_html($html);$textNodes = [];// 遍历所有元素$dom->filter('*')->each(function ($node) use (&$textNodes) {// 跳过忽略标签$ignoreTags = explode(',', config('translation.baidu.ignore_tags', 'code,pre,script,style'));if (in_array($node->tag, $ignoreTags)) {return;}// 只处理没有子元素的文本节点if ($node->text() && !$node->hasChildren()) {$textNodes[] = ['node' => $node,'text' => $node->text(),'outer_html' => $node->outerhtml];}// 处理图片alt属性if ($node->tag === 'img' && $node->alt) {$textNodes[] = ['node' => $node,'text' => $node->alt,'is_attr' => 'alt','outer_html' => $node->outerhtml];}// 处理视频标题if ($node->tag === 'video' && $node->title) {$textNodes[] = ['node' => $node,'text' => $node->title,'is_attr' => 'title','outer_html' => $node->outerhtml];}});return $textNodes;}public function replaceTranslatedText(array $originalNodes, array $translatedTexts): string{$html = '';$index = 0;foreach ($originalNodes as $nodeInfo) {if (isset($nodeInfo['is_attr'])) {// 属性翻译$nodeInfo['node']->setAttribute($nodeInfo['is_attr'], $translatedTexts[$index] ?? $nodeInfo['text']);} else {// 文本节点翻译$nodeInfo['node']->innertext = htmlspecialchars($translatedTexts[$index] ?? $nodeInfo['text']);}$html .= $nodeInfo['node']->outerhtml;$index++;}return $html;}
}
  1. 百度翻译服务 app/Service/BaiduTranslator.php
<?phpdeclare(strict_types=1);namespace App\Service;use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Guzzle\ClientFactory;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Codec\Json;class BaiduTranslator
{#[Inject]private ClientFactory $clientFactory;#[Inject]private HtmlParser $htmlParser;public function translate(string $html): string{// 1. 提取需要翻译的文本节点$textNodes = $this->htmlParser->extractTextNodes($html);if (empty($textNodes)) {return $html;}// 2. 提取纯文本内容用于翻译$texts = array_column($textNodes, 'text');// 3. 分批翻译文本$chunks = array_chunk($texts, config('translation.baidu.chunk_size', 30));$translatedChunks = [];foreach ($chunks as $chunk) {$translatedChunks[] = $this->translateChunk($chunk);}$translatedTexts = array_merge(...$translatedChunks);// 4. 将翻译结果替换回HTMLreturn $this->htmlParser->replaceTranslatedText($textNodes, $translatedTexts);}private function translateChunk(array $texts): array{$client = $this->clientFactory->create();$appId = config('translation.baidu.appid');$secret = config('translation.baidu.secret');// 生成签名$salt = time();$combinedText = implode("\n", $texts);$sign = md5($appId . $combinedText . $salt . $secret);// 构造请求参数$params = ['q' => $combinedText,'from' => 'zh','to' => 'en','appid' => $appId,'salt' => $salt,'sign' => $sign,'tag_handling' => '1', // 开启HTML标签处理'ignore_tags' => config('translation.baidu.ignore_tags', 'code,pre,script,style'),'preserve_tags' => config('translation.baidu.preserve_tags', 'p,div,span')];try {$response = $client->post(config('translation.baidu.api_url'), ['form_params' => $params,'timeout' => 15]);$result = Json::decode($response->getBody()->getContents());if (isset($result['error_code'])) {throw new \RuntimeException("百度翻译错误: {$result['error_code']} - {$result['error_msg']}");}$translated = $result['trans_result'] ?? [];return array_map(function ($item) {return $item['dst'] ?? '';}, $translated);} catch (\Throwable $e) {// 记录错误并返回原文logger()->error('百度翻译失败: ' . $e->getMessage(), ['texts' => $texts,'exception' => $e]);return $texts; // 返回原文作为降级处理}}
}
  1. 异步任务 app/Job/TranslateContentJob.php

declare(strict_types=1);namespace App\Job;use App\Model\Article;
use App\Service\BaiduTranslator;
use Hyperf\AsyncQueue\Job;class TranslateContentJob extends Job
{public int $articleId;public function __construct(int $articleId){$this->articleId = $articleId;}public function handle(){$article = Article::find($this->articleId);if (!$article) {logger()->warning("文章不存在: {$this->articleId}");return;}try {// 更新状态为翻译中$article->translation_status = Article::STATUS_PROCESSING;$article->save();// 执行翻译$translator = make(BaiduTranslator::class);$enContent = $translator->translate($article->content);// 更新结果$article->en_content = $enContent;$article->translation_status = Article::STATUS_COMPLETED;$article->save();} catch (\Throwable $e) {$article->translation_status = Article::STATUS_FAILED;$article->save();logger()->error("文章翻译失败: {$e->getMessage()}", ['article_id' => $this->articleId,'exception' => $e]);}}
}
  1. 控制器 app/Controller/TranslationController.php
<?phpdeclare(strict_types=1);namespace App\Controller;use App\Job\TranslateContentJob;
use App\Model\Article;
use App\Service\BaiduTranslator;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Annotation\PostMapping;
use Hyperf\AsyncQueue\Driver\DriverFactory;
use Hyperf\Utils\ApplicationContext;/*** @AutoController()*/
class TranslationController
{#[Inject]private BaiduTranslator $translator;/*** 实时翻译接口* * @PostMapping(path="/translate")*/public function translate(){$html = $this->request->post('content', '');if (empty($html)) {return $this->response->json(['code' => 400,'message' => '内容不能为空']);}try {$start = microtime(true);$translatedContent = $this->translator->translate($html);$time = round(microtime(true) - $start, 3);return $this->response->json(['code' => 0,'message' => '翻译成功','data' => ['content' => $translatedContent,'time' => $time . 's']]);} catch (\Throwable $e) {return $this->response->json(['code' => 500,'message' => '翻译失败: ' . $e->getMessage()]);}}/*** 创建文章并异步翻译* * @PostMapping(path="/article")*/public function createArticle(){$title = $this->request->post('title', '');$content = $this->request->post('content', '');if (empty($title) || empty($content)) {return $this->response->json(['code' => 400,'message' => '标题和内容不能为空']);}try {// 创建文章$article = Article::create(['title' => $title,'content' => $content,'translation_status' => Article::STATUS_PENDING]);// 加入翻译队列$queue = ApplicationContext::getContainer()->get(DriverFactory::class)->get('default');$queue->push(new TranslateContentJob($article->id));return $this->response->json(['code' => 0,'message' => '文章创建成功,翻译任务已提交','data' => ['id' => $article->id,'translation_status' => $article->translation_status]]);} catch (\Throwable $e) {return $this->response->json(['code' => 500,'message' => '文章创建失败: ' . $e->getMessage()]);}}/*** 查询文章翻译状态* * @GetMapping(path="/article/status/{id}")*/public function getTranslationStatus($id){$article = Article::find($id);if (!$article) {return $this->response->json(['code' => 404,'message' => '文章不存在']);}$statusMap = [Article::STATUS_PENDING => '等待翻译',Article::STATUS_PROCESSING => '翻译中',Article::STATUS_COMPLETED => '翻译完成',Article::STATUS_FAILED => '翻译失败'];return $this->response->json(['code' => 0,'data' => ['id' => $article->id,'status' => $article->translation_status,'status_text' => $statusMap[$article->translation_status] ?? '未知状态','en_content' => $article->en_content]]);}
}
  1. 环境配置 .env
BAIDU_TRANSLATE_APPID=your_app_id
BAIDU_TRANSLATE_SECRET=your_secret_key

完结!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93234.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93234.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

字节跳动 VeOmni 框架开源:统一多模态训练效率飞跃!

资料来源&#xff1a;火山引擎-开发者社区 多模态时代的训练痛点&#xff0c;终于有了“特效药” 当大模型从单一语言向文本 图像 视频的多模态进化时&#xff0c;算法工程师们的训练流程却陷入了 “碎片化困境”&#xff1a; 当业务要同时迭代 DiT、LLM 与 VLM时&#xff0…

配置docker pull走http代理

之前写了一篇自建Docker镜像加速器服务的博客&#xff0c;需要用到境外服务器作为代理&#xff0c;但是一般可能没有境外服务器&#xff0c;只有http代理&#xff0c;所以如果本地使用想走代理可以用以下方式 临时生效&#xff08;只对当前终端有效&#xff09; 设置环境变量…

OpenAI 开源模型 gpt-oss 本地部署详细教程

OpenAI 最近发布了其首个开源的开放权重模型gpt-oss&#xff0c;这在AI圈引起了巨大的轰动。对于广大开发者和AI爱好者来说&#xff0c;这意味着我们终于可以在自己的机器上&#xff0c;完全本地化地运行和探索这款强大的模型了。 本教程将一步一步指导你如何在Windows和Linux…

力扣-5.最长回文子串

题目链接 5.最长回文子串 class Solution {public String longestPalindrome(String s) {boolean[][] dp new boolean[s.length()][s.length()];int maxLen 0;String str s.substring(0, 1);for (int i 0; i < s.length(); i) {dp[i][i] true;}for (int len 2; len …

Apache Ignite超时管理核心组件解析

这是一个非常关键且设计精巧的 定时任务与超时管理组件 —— GridTimeoutProcessor&#xff0c;它是 Apache Ignite 内核中负责 统一调度和处理所有异步超时事件的核心模块。&#x1f3af; 一、核心职责统一管理所有需要“在某个时间点触发”的任务或超时逻辑。它相当于 Ignite…

DAY 42 Grad-CAM与Hook函数

知识点回顾回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例# 定义一个存储梯度的列表 conv_gradients []# 定义反向钩子函数 def backward_hook(module, grad_input, grad_output):# 模块&#xff1a;当前应用钩子的模块# grad_input&#xff1a;模块输入的梯度…

基于 NVIDIA 生态的 Dynamo 风格分布式 LLM 推理架构

网罗开发&#xff08;小红书、快手、视频号同名&#xff09;大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等方…

《吃透 C++ 类和对象(中):拷贝构造函数与赋值运算符重载深度解析》

&#x1f525;个人主页&#xff1a;草莓熊Lotso &#x1f3ac;作者简介&#xff1a;C研发方向学习者 &#x1f4d6;个人专栏&#xff1a; 《C语言》 《数据结构与算法》《C语言刷题集》《Leetcode刷题指南》 ⭐️人生格言&#xff1a;生活是默默的坚持&#xff0c;毅力是永久的…

Python 环境隔离实战:venv、virtualenv 与 conda 的差异与最佳实践

那天把项目部署到测试环境&#xff0c;结果依赖冲突把服务拉崩了——本地能跑&#xff0c;线上不能跑。折腾半天才发现&#xff1a;我和同事用的不是同一套 site-packages&#xff0c;版本差异导致运行时异常。那一刻我彻底明白&#xff1a;虚拟环境不是可选项&#xff0c;它是…

[ 数据结构 ] 时间和空间复杂度

1.算法效率算法效率分析分为两种 : ①时间效率, ②空间效率 时间效率即为 时间复杂度 , 时间复杂度主要衡量一个算法的运行速度空间效率即为 空间复杂度 , 空间复杂度主要衡量一个算法所需要的额外空间2.时间复杂度2.1 时间复杂度的概念定义 : 再计算机科学中 , 算法的时间复杂…

一,设计模式-单例模式

目的设计单例模式的目的是为了解决两个问题&#xff1a;保证一个类只有一个实例这种需求是需要控制某些资源的共享权限&#xff0c;比如文件资源、数据库资源。为该实例提供一个全局访问节点相较于通过全局变量保存重要的共享对象&#xff0c;通过一个封装的类对象&#xff0c;…

AIStarter修复macOS 15兼容问题:跨平台AI项目管理新体验

AIStarter是全网唯一支持Windows、Mac和Linux的AI管理平台&#xff0c;为开发者提供便捷的AI项目管理体验。近期&#xff0c;熊哥在视频中分享了针对macOS 15系统无法打开AIStarter的修复方案&#xff0c;最新版已完美兼容。本文基于视频内容&#xff0c;详解修复细节与使用技巧…

LabVIEW 纺织检测数据传递

基于 LabVIEW 实现纺织检测系统中上位机&#xff08;PC 机&#xff09;与下位机&#xff08;单片机&#xff09;的串口数据传递&#xff0c;成功应用于煮茧机温度测量系统。通过采用特定硬件架构与软件设计&#xff0c;实现了温度数据的高效采集、传输与分析&#xff0c;操作简…

ECCV-2018《Variational Wasserstein Clustering》

核心思想 该论文提出了一个基于最优传输(optimal transportation) 理论的新型聚类方法&#xff0c;称为变分Wasserstein聚类(Variational Wasserstein Clustering, VWC)。其核心思想有三点&#xff1a;建立最优传输与k-means聚类的联系&#xff1a;作者指出k-means聚类问题本质…

部署 Docker 应用详解(MySQL + Tomcat + Nginx + Redis)

文章目录一、MySQL二、Tomcat三、Nginx四、Redis一、MySQL 搜索 MySQL 镜像下载 MySQL 镜像创建 MySQL 容器 docker run -i -t/d -p 3307:3306 --namec_mysql -v $PWD/conf:/etc/mysql/conf.d -v $PWD/logs:/logs -v $PWD/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD123456 m…

VR全景导览在大型活动中的应用实践:优化观众体验与现场管理

大型演出赛事往往吸引海量观众&#xff0c;但复杂的场馆环境常带来诸多困扰&#xff1a;如何快速找到座位看台区域&#xff1f;停车位如何规划&#xff1f;附近公交地铁站在哪&#xff1f;这些痛点直接影响观众体验与现场秩序。VR全景技术为解决这些问题提供了有效方案。通过在…

OpenJDK 17 JIT编译器堆栈分析

##堆栈(gdb) bt #0 PhaseOutput::safepoint_poll_table (this0x7fffd0bfb950) at /home/yym/openjdk17/jdk17-master/src/hotspot/share/opto/output.hpp:173 #1 0x00007ffff689634e in PhaseOutput::fill_buffer (this0x7fffd0bfb950, cb0x7fffd0bfb970, blk_starts0x7fffb0…

功能测试中常见的面试题-二

二、测试设计与用例编写题解释等价类划分 (Equivalence Partitioning) 和边界值分析 (Boundary Value Analysis)&#xff1f;并举例说明。等价类划分 (EP)&#xff1a; 将输入域划分为若干组&#xff08;等价类&#xff09;&#xff0c;假设同一组内的数据对揭露程序错误具有等…

SOLi-LABS Page-4 (Challenges)--54-65关

sql-54 翻译一下页面&#xff0c;得知我们只有十次机会。id参数是单引号闭合。 ?id-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()-- 我得到的表名是igsyiz2p7z。&#xff08;每个人得到的应该都不一样&#…

docker代码如何在vscod上修改

基于 docker-compose.yml文件&#xff08;包含 ​​emqx​​&#xff08;MQTT服务&#xff09;、​​backend​​&#xff08;后端服务&#xff09;、​​mysql​​&#xff08;数据库&#xff09;&#xff09;的详细运行、调试、增改删操作说明&#xff0c;结合流程图示意&…