spring boot项目整合百度翻译

发布于:2025-06-28 ⋅ 阅读:(19) ⋅ 点赞:(0)

本片文章教大家怎样在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;
//    }

    // 内部类表示单个翻译结果
    @Data
    public 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 = new
                HttpEntity<>(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) {

        // 构建带参数的URL
        UriComponentsBuilder 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中。

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


网站公告

今日签到

点亮在社区的每一天
去签到