当前位置: 首页 > news >正文

spring boot项目整合百度翻译

本片文章教大家怎样在spring boot项目中引入百度翻译,并且优雅的使用百度翻译。

首先,我们要了解为什么要使用翻译插件。为了支持多语言的国际化;

目前市面上最常见的后端国际化就是在resource资源目录下设置多个语言文档,这些文档中是一些key、value类型的键值对,然后我们展示的时候语言不直接写死,而是通过使用key的形式来进行引用,从而实现国际化的方式。但是这种方式有很大的局限性。我们要做的国际化只能是一些提前写死的键值。这就很不友好了;

我们公司的业务需求是,我只要点击切换语言之后。中文的就是中文环境,点击切换英文之后,就是英文环境了。这就需要我们至少需要多套语言环境,如果是中文,那么所有的中文数据都放在中文的数据库中,如果是英文,那么所有的英文数据都放在英文的数据库。多语言的环境直接切换出来。这个时候就需要我们就行翻译了,直接把一整条数据就行翻译,饭后插入到不同的语言环境(数据库)中。本次使用百度的翻译插件

1、进入百度翻译官网,注册账号

百度翻译开放平台

进入官网,登录、注册,并且申请相应的账号。

然后,点击产品服务,选择相应的服务类型;

最后,在管理控制台的开发者信息中找到自己额度appID和相应的密钥

2、创建spring boot项目,整合翻译接口

由于我们调用百度的翻译接口时通过网络请求来调用的,所以,我们不需要引入任何第三方的百度APi依赖。

基本的maven依赖如下:

 <!-- 依赖配置 --><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--            自动装配依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><!--自动装配依赖非必需依赖,该依赖作用是在使用IDEA编写配置文件有代码提示--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId></dependency></dependencies>

我想要实现的功能是,传入一个Object类型的对象,或者泛型T。不管这个对象中有多少属性,我们只翻译其中的String类型的属性,并且这个String类型的属性值不为null,并且不为空字符串。并且自定义了一个注解,只要在属性值上加上了这个注解,那么,这个属性值也不会被翻译了。

最后,我想要写成翻译的jar包,打到我们公司的maven私服上。只要在项目中引入了翻译的maven坐标就可以直接使用了。

自定义一个注解,这个注解的作用是,加上这个注解的属性值不会被翻译。

/*** 不进行翻译的字段注解*/
@Target(ElementType.FIELD) // 只能应用于字段
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,便于反射获取
public @interface NoTranslation {// 可选:添加属性如原因说明String reason() default "";
}

自定义属性类,用来获取到百度翻译的一些属性值。

@ConfigurationProperties("tools.translate.baidu")
@Component
@Data
public class BaiDuTranslateParams {/*** 百度翻译的appId*/private String appId;/*** 百度翻译的密钥*/private String secretKey;/*** 翻译的源语言,默认为自动检测语言*/private String from="auto";/*** 翻译的目标语言,默认为英文*/private String to="en";/*** 翻译的url*/private String url;
}

翻译的url是根据你选择的不同翻译服务来的,可以去百度翻译的官网查看相应的翻译地址。我使用的是百度的通用翻译,所以引用百度的通用翻译的url地址。

设置百度翻译的MD5工具类,这个百度翻译的MD5工具是百度翻译官方提供的,所以不是随便的一个MD5生成器就可以使用的。

/*** MD5编码相关的类* * @author wangjingtao* */
public class MD5 {// 首先初始化一个字符数组,用来存放每个16进制字符private static final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd','e', 'f' };/*** 获得一个字符串的MD5值* * @param input 输入的字符串* @return 输入字符串的MD5值* */public static String md5(String input) {if (input == null)return null;try {// 拿到一个MD5转换器(如果想要SHA1参数换成”SHA1”)MessageDigest messageDigest = MessageDigest.getInstance("MD5");// 输入的字符串转换成字节数组byte[] inputByteArray = input.getBytes(StandardCharsets.UTF_8);// inputByteArray是输入字符串转换得到的字节数组messageDigest.update(inputByteArray);// 转换并返回结果,也是字节数组,包含16个元素byte[] resultByteArray = messageDigest.digest();// 字符数组转换成字符串返回return byteArrayToHex(resultByteArray);} catch (NoSuchAlgorithmException e) {return null;}}/*** 获取文件的MD5值* * @param file* @return*/public static String md5(File file) {try {if (!file.isFile()) {System.err.println("文件" + file.getAbsolutePath() + "不存在或者不是文件");return null;}FileInputStream in = new FileInputStream(file);String result = md5(in);in.close();return result;} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}public static String md5(InputStream in) {try {MessageDigest messagedigest = MessageDigest.getInstance("MD5");byte[] buffer = new byte[1024];int read = 0;while ((read = in.read(buffer)) != -1) {messagedigest.update(buffer, 0, read);}in.close();String result = byteArrayToHex(messagedigest.digest());return result;} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}private static String byteArrayToHex(byte[] byteArray) {// new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方))char[] resultCharArray = new char[byteArray.length * 2];// 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去int index = 0;for (byte b : byteArray) {resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];resultCharArray[index++] = hexDigits[b & 0xf];}// 字符数组组合成字符串返回return new String(resultCharArray);}}

设置百度翻译的响应结果的工具类:

@Data
public class BaiduTranslationUtils{private String from;private String to;private List<TranslationResult> trans_result;//    // 获取第一个翻译结果(安全处理空列表)
//    public TranslationResult getFirstTranslation() {
//        return trans_result != null && !trans_result.isEmpty() ?
//                trans_result.getFirst() : null;
//    }// 内部类表示单个翻译结果@Datapublic static class TranslationResult {private String src;private String dst;}
}

设置HTTP的发送请求工具类;本次使用HTTPClient来继续HTTP请求的发送。

@Component
@RequiredArgsConstructor
public class HttpClientUtils {private final RestTemplate restTemplate;/*** 发送 application/x-www-form-urlencoded 格式的 POST 请求* @param url 请求地址* @param formData 表单数据 (键值对)* @param responseType 返回类型* @return ResponseEntity 包含响应体和状态码*/public <T> ResponseEntity<T> postForm(String url,MultiValueMap<String, Object> formData,Class<T> responseType) {HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);HttpEntity<MultiValueMap<String, Object>> entity = newHttpEntity<>(formData, headers);return restTemplate.exchange(url,HttpMethod.POST,entity,responseType);}/*** 发送 application/x-www-form-urlencoded 格式的 POST 请求(Map 简化版)* @param url 请求地址* @param formData 表单数据 (键值对)* @param responseType 返回类型* @return ResponseEntity 包含响应体和状态码*/public <T> ResponseEntity<T> postForm(String url,Map<String, String> formData,Class<T> responseType) {MultiValueMap<String, Object> multiValueMap =new LinkedMultiValueMap<>();formData.forEach(multiValueMap::add);return postForm(url, multiValueMap, responseType);}public <T> ResponseEntity<T> request(String url,HttpMethod method,Object requestBody,HttpHeaders headers,MultiValueMap<String, String> params,Class<T> responseType) {// 构建带参数的URLUriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);if (params != null && !params.isEmpty()) {builder.queryParams(params);}String finalUrl = builder.build().encode().toUriString();HttpEntity<Object> entity = new HttpEntity<>(requestBody, headers);return restTemplate.exchange(finalUrl, method, entity, responseType);}}

设置百度翻译的发送请求的service类,用来发送百度请求,接收一个List<String>类型的参数,接收翻译的源语言和目标语言。

@Service
@RequiredArgsConstructor
@Slf4j
public class BaiduTranslateService {private final BaiDuTranslateParams config;
private final ObjectMapper objectMapper;private  final HttpClientUtils httpClient;public List<String> translate(List<String> texts, String from, String to) throws Exception {String salt = String.valueOf(System.currentTimeMillis());String query = String.join("\n", texts);String md=config.getAppId() + query +salt +config.getSecretKey();String sign= MD5.md5(md);Map<String, Object>  params = new HashMap<>();params.put("q", query);params.put("from", from);params.put("to", to);params.put("appid", config.getAppId());params.put("salt", salt);params.put("sign", sign);MultiValueMap<String, Object> requestParams = new LinkedMultiValueMap<>();requestParams.setAll(params);ResponseEntity<Object> response =httpClient.postForm(config.getUrl(), requestParams, Object.class);Object body = response.getBody();if (body == null) {return Collections.singletonList("翻译失败");}BaiduTranslationUtils result = objectMapper.readValue(JSON.toJSONString(body), BaiduTranslationUtils.class);return result.getTrans_result().stream().map(BaiduTranslationUtils.TranslationResult::getDst).collect(Collectors.toList());}
}

设置翻译发送的工具类,用来发送翻译的请求。我们想要实现的这样;我传入一个T泛型的对象,

然后返回一个T泛型的对象。只翻译这个对象中的String类型的属性,其他属性不翻译。String类型的属性也不是说全部翻译的,String属性为null或者为空字符串,或者在String属性上加上了@NoTranslation不翻译注解。这几种情况下的String属性都不进行翻译。

工具类中有三个方法;

1、传入T 泛型对象,返回T翻译好的对象。

2、传入T泛型对象,传入翻译的源语言,传入翻译的目标语言。返回T翻译好的对象

3、传入List<T>泛型对象,传入翻译的源语言,传入翻译的目标语言。返回T翻译好的对象

要注意,百度翻译的字符数量一次大概限定在6000个字符,换算成文字大概为2000个汉字。

相应的翻译工具类如下:

/*** @Author 张乔* @Date 2025/5/29 14:21*/
@Component
@RequiredArgsConstructor
@Slf4j
public class BaiduTranslationSend {private static final Logger logger = LoggerFactory.getLogger(BaiduTranslationSend.class);private final BaiduTranslateService translateService;private final BaiDuTranslateParams baiDuTranslateParams;/*** 批量翻译对象列表中所有标记为可翻译的字段* 该方法遍历对象列表中的每个对象,识别带有@Translatable注解的字段,收集需要翻译的文本,* 通过翻译服务进行批量翻译,最后将翻译结果回写到原对象的对应字段中。** @param <T> 对象类型* @param objList 待翻译的对象列表(允许为null或空列表)* @param fromLang 原始语言代码(支持"0"/"1"/"2"数字代码自动转换)* @param toLang 目标语言代码(支持"0"/"1"/"2"数字代码自动转换)* @return 翻译后的对象列表(发生错误时返回原列表)*/public <T> List<T> translateObjectFieldsList(List<T> objList, String fromLang, String toLang) {if (objList == null || objList.isEmpty()) {logger.warn("传入对象列表为空或空列表,无法进行翻译操作。");return objList;}// 语言代码转换(复用单个对象的转换逻辑)Map<String, String> langCodeMap = Map.of("0", "zh", "1", "en", "2", "jp");fromLang = langCodeMap.getOrDefault(fromLang, fromLang);toLang = langCodeMap.getOrDefault(toLang, toLang);try {// 1. 收集所有需要翻译的文本及其元数据List<TextTranslationTask> translationTasks = new ArrayList<>();Map<Class<?>, List<Field>> classFieldCache = new HashMap<>();for (T obj : objList) {if (obj == null) continue;// 获取或缓存类字段信息List<Field> translatableFields = classFieldCache.computeIfAbsent(obj.getClass(),this::getTranslatableFields);for (Field field : translatableFields) {try {String value = (String) field.get(obj);if (isValidForTranslation(value)) {translationTasks.add(new TextTranslationTask(obj, field, value));}} catch (IllegalAccessException e) {logger.warn("对象 [{}] 字段 [{}] 访问失败: {}",obj.getClass().getSimpleName(), field.getName(), e.getMessage());}}}// 2. 如果没有需要翻译的内容,提前返回if (translationTasks.isEmpty()) {logger.info("列表中共 {} 个对象,均无可翻译字段", objList.size());return objList;}// 3. 提取所有需要翻译的文本List<String> textsToTranslate = translationTasks.stream().map(task -> task.originalText).collect(Collectors.toList());// 4. 批量翻译(支持自动分批处理大量文本)List<String> translatedTexts = translateService.translate(textsToTranslate, fromLang, toLang);// 5. 应用翻译结果到所有对象applyBatchTranslations(translationTasks, translatedTexts);logger.info("成功翻译 {} 个对象中的 {} 个字段",objList.size(), translationTasks.size());return objList;} catch (Exception e) {logger.error("批量翻译对象列表时出错: {}", e.getMessage(), e);return objList; // 出错时返回原列表}}// 翻译任务内部类(记录文本的元数据)private static class TextTranslationTask {final Object targetObject;final Field targetField;final String originalText;TextTranslationTask(Object targetObject, Field targetField, String originalText) {this.targetObject = targetObject;this.targetField = targetField;this.originalText = originalText;}}// 批量应用翻译结果private void applyBatchTranslations(List<TextTranslationTask> tasks, List<String> translatedTexts) {if (tasks.size() != translatedTexts.size()) {logger.error("翻译结果数量 {} 与任务数量 {} 不匹配",translatedTexts.size(), tasks.size());return;}for (int i = 0; i < tasks.size(); i++) {TextTranslationTask task = tasks.get(i);String translatedText = translatedTexts.get(i);try {task.targetField.set(task.targetObject, translatedText);} catch (IllegalAccessException e) {logger.error("设置对象 [{}] 字段 [{}] 翻译值失败: {}",task.targetObject.getClass().getSimpleName(),task.targetField.getName(),e.getMessage());}}}// 复用单个对象翻译的字段获取方法private List<Field> getTranslatableFields(Class<?> clazz) {return TRANSLATABLE_FIELD_CACHE.computeIfAbsent(clazz, k -> {List<Field> fields = new ArrayList<>();Class<?> current = clazz;while (current != null && current != Object.class) {for (Field field : current.getDeclaredFields()) {if (field.getType() == String.class &&!field.isAnnotationPresent(NoTranslation.class)) {field.setAccessible(true);fields.add(field);}}current = current.getSuperclass();}return Collections.unmodifiableList(fields);});}/*** 翻译对象的字符串字段。** @param <T> 对象的类型* @param obj 需要翻译的对象*/public <T> T translateObjectFields(T obj) {// 1. 空对象检查if (obj == null) {logger.warn("传入对象为空,无法进行翻译操作。");return null;}try {// 3. 获取可翻译字段(带缓存)List<Field> translatableFields = getTranslatableFields(obj.getClass());// 4. 收集需要翻译的字段值Map<Field, String> fieldValueMap = new LinkedHashMap<>();List<String> textsToTranslate = new ArrayList<>();for (Field field : translatableFields) {try {String value = (String) field.get(obj);if (isValidForTranslation(value)) {fieldValueMap.put(field, value);textsToTranslate.add(value);}} catch (IllegalAccessException e) {logger.warn("字段 [{}] 访问失败: {}", field.getName(), e.getMessage());}}// 5. 无翻译内容提前返回if (textsToTranslate.isEmpty()) {logger.debug("类型 [{}] 无可翻译字段", obj.getClass().getSimpleName());return obj;}// 6. 执行批量翻译List<String> translatedTexts = translateService.translate(textsToTranslate,baiDuTranslateParams.getFrom(), baiDuTranslateParams.getTo());// 7. 回填翻译结果(带安全检查)applyTranslations(obj, fieldValueMap, translatedTexts);logger.info("成功翻译 {} 个字段: [{}]",fieldValueMap.size(), obj.getClass().getSimpleName());return obj;} catch (Exception e) {logger.error("翻译对象 [{}] 时出错: {}",obj.getClass().getSimpleName(), e.getMessage(), e);return obj; // 出错时返回原对象而非null}}/*** 翻译对象中的字符串字段。** @param <T>      对象类型* @param obj      需要翻译的对象* @param fromLang 源语言* @param toLang   目标语言*/// 类级缓存,避免重复反射扫描private static final Map<Class<?>, List<Field>> TRANSLATABLE_FIELD_CACHE = new ConcurrentHashMap<>();public <T> T translateObjectFields(T obj, String fromLang, String toLang) {// 1. 空对象检查if (obj == null) {logger.warn("传入对象为空,无法进行翻译操作。");return null;}// 2. 语言代码转换(使用Map更灵活)Map<String, String> langCodeMap = Map.of("0", "zh", "1", "en", "2", "jp");fromLang = langCodeMap.getOrDefault(fromLang, fromLang);toLang = langCodeMap.getOrDefault(toLang, toLang);try {// 3. 获取可翻译字段(带缓存)List<Field> translatableFields = getTranslatableFields(obj.getClass());// 4. 收集需要翻译的字段值Map<Field, String> fieldValueMap = new LinkedHashMap<>();List<String> textsToTranslate = new ArrayList<>();for (Field field : translatableFields) {try {String value = (String) field.get(obj);if (isValidForTranslation(value)) {fieldValueMap.put(field, value);textsToTranslate.add(value);}} catch (IllegalAccessException e) {logger.warn("字段 [{}] 访问失败: {}", field.getName(), e.getMessage());}}// 5. 无翻译内容提前返回if (textsToTranslate.isEmpty()) {logger.debug("类型 [{}] 无可翻译字段", obj.getClass().getSimpleName());return obj;}// 6. 执行批量翻译List<String> translatedTexts = translateService.translate(textsToTranslate, fromLang, toLang);// 7. 回填翻译结果(带安全检查)applyTranslations(obj, fieldValueMap, translatedTexts);logger.info("成功翻译 {} 个字段: [{}]",fieldValueMap.size(), obj.getClass().getSimpleName());return obj;} catch (Exception e) {logger.error("翻译对象 [{}] 时出错: {}",obj.getClass().getSimpleName(), e.getMessage(), e);return obj; // 出错时返回原对象而非null}}// 判断字符串是否适合翻译private boolean isValidForTranslation(String value) {return value != null && !value.trim().isEmpty();}// 应用翻译结果到对象字段private <T> void applyTranslations(T obj, Map<Field, String> fieldMap, List<String> translations) {if (fieldMap.size() != translations.size()) {logger.error("翻译结果数量 {} 与字段数量 {} 不匹配",translations.size(), fieldMap.size());return;}int index = 0;for (Map.Entry<Field, String> entry : fieldMap.entrySet()) {Field field = entry.getKey();try {field.set(obj, translations.get(index++));} catch (IllegalAccessException e) {logger.error("设置字段 [{}] 翻译值失败: {}", field.getName(), e.getMessage());}}}}

然后,记得打成jar包的形式。

如果,不知道第三方spring boot的jar包怎样打的,可以查看一下我的这篇文章。

springboot3自定义starter(详细入门)-CSDN博客

然后,在项目中引入下相应的maven坐标。在yml配置文件中引入相应的百度翻译的属性值。

那么,现在就可以使用这个翻译配置类了。

在Test测试类中写一个测试方法:

相应的运行结果如下:

因为这个属性里面只有两个属性是String类型需要翻译,所以只翻译了两个属性。我这边源语言是百度自动识别,目标语言是小日子语。

翻译工具类的原理是,收集传入T泛型的所有需要翻译的String属性值,统一存放在一个List<String>中,然后,在通过拼接换行符\n的形式来发送翻译请求。

百度翻译相应的参数中有翻译的原始数据和翻译之后的目标数据,通过进行比对,再把翻译后的目标数据填充到相应的泛型对象T中。

以上,就是正片文章的内容了。如果觉得文章写的不错,请给博主点个赞,非常感谢!!!

http://www.lqws.cn/news/525619.html

相关文章:

  • Windows 安装 Redis8.0.2
  • JVM 中的 GC 算法演进之路!(Serial、CMS、G1 到 ZGC)
  • OceanBase向量检索在货拉拉的探索和实践
  • js截取地址详细信息(除去省市区、市市区、自治区市区)
  • python3虚拟机线程切换过程
  • 企业级混合云平台,信息安全基础技术方案
  • WinAppDriver 自动化测试:Python篇
  • Docker环境搭建和docker性能监控
  • CTF Writeup: [强网杯 2019]随便注挑战解析
  • 分布式系统 - 分布式缓存及方案实现
  • Python 数据分析与可视化 Day 7 - 可视化整合报告实战
  • 【nRF52832】【环境搭建 1】【ubuntu下搭建nRF52832开发环境】
  • 达梦数据库安装
  • 《高等数学》(同济大学·第7版)第九章 多元函数微分法及其应用第一节多元函数的基本概念
  • Fisco Bcos学习 - 搭建并行多组组网
  • SQL关键字三分钟入门:DELETE —— 删除数据
  • 定位坐标系深度研究报告
  • C++学习笔记--Chapter Two--类的定义、对象的创建和使用、构造函数
  • 《解锁前端潜力:自动化流程搭建秘籍》
  • python学智能算法(十六)|机器学习支持向量机简单示例
  • HarmonyOS5 折叠屏适配测试:验证APP在展开/折叠状态下的界面自适应,以及会出现的问题
  • 数组题解——二分查找【LeetCode】
  • 八股文——JAVA基础:说一下C++与java的区别
  • 黑马python(十六)
  • GBDT:梯度提升决策树——集成学习中的预测利器
  • 设计模式-桥接模式、组合模式
  • Selenium 二次封装通用页面基类 BasePage —— Python 实践
  • 矩阵题解——螺旋矩阵【LeetCode】
  • 大模型推理-高通qnn基础
  • PYTHON从入门到实践5-列表操作