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

Spring Boot 实现多语言国际化拦截器

Spring Boot 实现多语言国际化拦截器 - 完整教程

本文将详细介绍如何在 Spring Boot 项目中实现一套完整的多语言国际化(i18n)拦截器方案,包括请求拦截器和响应拦截器的双重设计,实现零侵入、灵活切换的多语言支持。


📋 目录

  1. 方案概述
  2. 核心设计思路
  3. 技术实现
  4. 前端集成
  5. 最佳实践
  6. 常见问题

方案概述

💡 设计目标

  • 零侵入:业务代码使用工具类,自动翻译
  • 灵活切换:前端通过请求头动态切换语言
  • 安全可控:精准控制翻译范围,避免重复翻译
  • 标准兼容:基于 Spring i18n 标准实现

🎯 支持的语言

  • 🇨🇳 zh-CN:简体中文(默认)
  • 🇺🇸 en-US:英文
  • 🇭🇰 zh-HK:繁體中文

🏗️ 整体架构

┌─────────────────────────────────────────────────────────────────┐
│                        HTTP 请求                                 │
│                 Header: X-Language: en-US                       │
└───────────────────────────┬─────────────────────────────────────┘│▼
┌─────────────────────────────────────────────────────────────────┐
│              LanguageInterceptor(请求拦截)                      │
│  1. 从请求头获取 X-Language                                       │
│  2. 解析为 Locale 对象(en-US → Locale.US)                       │
│  3. 设置到 LocaleContextHolder                                   │
└───────────────────────────┬─────────────────────────────────────┘│▼
┌─────────────────────────────────────────────────────────────────┐
│                    Controller 层                                 │
│  MessageUtils.message("error.not.found")                        │
│    └─> 从 LocaleContextHolder 获取当前 Locale                    │
│    └─> 从 messages_en_US.properties 读取翻译                     │
│    └─> 返回 "Not found"                                          │
└───────────────────────────┬─────────────────────────────────────┘│▼
┌─────────────────────────────────────────────────────────────────┐
│           I18nResponseBodyAdvice(响应拦截)                      │
│  1. 检查返回值是否为统一响应对象                                    │
│  2. 翻译框架固定消息(如 "成功" → "Success")                       │
│  3. 业务错误消息已在 Controller 翻译,不再处理                      │
└───────────────────────────┬─────────────────────────────────────┘│▼
┌─────────────────────────────────────────────────────────────────┐
│                       HTTP 响应                                  │
│  { "code": 200, "message": "Success", "data": {...} }           │
└─────────────────────────────────────────────────────────────────┘

核心设计思路

1. 双重拦截器设计

为什么需要两个拦截器?
拦截器职责拦截点
LanguageInterceptor解析请求头中的语言设置HandlerInterceptor.preHandle
I18nResponseBodyAdvice翻译响应中的固定消息ResponseBodyAdvice

设计理由

  • 职责分离:请求处理和响应处理分离,符合单一职责原则
  • 执行时机:请求拦截器在 Controller 执行前设置 Locale,响应拦截器在返回前翻译
  • 灵活控制:可以独立启用/禁用某个拦截器

2. 翻译策略:白名单模式

关键决策:只翻译框架固定消息,不翻译业务消息

原因

  1. 避免重复翻译:业务错误消息已在异常处理器中翻译
  2. 避免误判:不会将业务消息当作消息键去查询
  3. 性能优化:不需要每次都查询 MessageSource

流程对比

❌ 全部翻译模式(不推荐)
Controller: MessageUtils.message("error.not.found") → "Not found"↓
ResponseBodyAdvice: translateMessage("Not found") → 查询 MessageSource↓
性能浪费 + 可能误判✅ 白名单模式(推荐)
Controller: MessageUtils.message("error.not.found") → "Not found"↓
ResponseBodyAdvice: "Not found" 不在白名单 → 不翻译↓
精准控制 + 性能优化

技术实现

第一步:配置 Spring 国际化

1.1 添加配置
# application.yml
spring:messages:basename: i18n/messagesencoding: UTF-8cache-duration: 3600  # 生产环境缓存1小时# cache-duration: -1  # 开发环境不缓存
1.2 创建资源文件

文件结构

src/main/resources/└─ i18n/├─ messages_zh_CN.properties  # 简体中文├─ messages_en_US.properties  # 英文└─ messages_zh_HK.properties  # 繁體中文

messages_zh_CN.properties

# 通用消息
operation.success=操作成功
operation.failed=操作失败# 错误消息
error.not.found=数据不存在
error.unauthorized=没有权限
error.server.internal=服务器内部错误# 验证消息
validation.required={0}不能为空
validation.invalid={0}格式不正确

messages_en_US.properties

# General messages
operation.success=Operation successful
operation.failed=Operation failed# Error messages
error.not.found=Not found
error.unauthorized=Unauthorized
error.server.internal=Internal server error# Validation messages
validation.required={0} is required
validation.invalid={0} format is invalid

messages_zh_HK.properties

# 通用消息
operation.success=操作成功
operation.failed=操作失敗# 錯誤消息
error.not.found=數據不存在
error.unauthorized=沒有權限
error.server.internal=服務器內部錯誤# 驗證消息
validation.required={0}不能為空
validation.invalid={0}格式不正確

第二步:实现 MessageUtils 工具类

package com.example.utils;import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Locale;/*** 国际化消息工具类*/
@Slf4j
@Component
public class MessageUtils {@Resourceprivate MessageSource messageSource;private static MessageSource staticMessageSource;@PostConstructpublic void init() {staticMessageSource = messageSource;}/*** 获取国际化消息* @param code 消息键* @param args 参数* @return 翻译后的消息*/public static String message(String code, Object... args) {try {Locale locale = LocaleContextHolder.getLocale();return staticMessageSource.getMessage(code, args, locale);} catch (Exception e) {log.warn("获取国际化消息失败: code={}, args={}", code, args, e);return code;}}/*** 获取国际化消息(指定默认值)* @param code 消息键* @param defaultMessage 默认消息* @param args 参数* @return 翻译后的消息*/public static String message(String code, String defaultMessage, Object... args) {try {Locale locale = LocaleContextHolder.getLocale();return staticMessageSource.getMessage(code, args, defaultMessage, locale);} catch (Exception e) {log.warn("获取国际化消息失败: code={}, defaultMessage={}", code, defaultMessage, e);return defaultMessage;}}
}

第三步:实现 LanguageInterceptor(请求拦截器)

package com.example.interceptor;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.support.RequestContextUtils;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;/*** 多语言拦截器* 从请求头中获取语言参数,设置当前线程的 Locale*/
@Slf4j
@Component
public class LanguageInterceptor implements HandlerInterceptor {/*** 请求头中的语言参数名*/private static final String LANGUAGE_HEADER = "X-Language";/*** 默认语言(简体中文)*/private static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {try {// 从请求头获取语言参数String language = request.getHeader(LANGUAGE_HEADER);// 解析 LocaleLocale locale = DEFAULT_LOCALE;if (StringUtils.hasText(language)) {locale = parseLocale(language);log.debug("多语言拦截器 - 请求头语言: {}, 解析后的Locale: {}", language, locale);} else {log.debug("未指定语言,使用默认语言: {}", locale);}// 设置 LocaleLocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);if (localeResolver != null) {localeResolver.setLocale(request, response, locale);}} catch (Exception e) {log.error("语言拦截器异常,使用默认语言", e);// 异常不阻塞请求}return true;}/*** 解析语言字符串为 Locale 对象* @param language 语言字符串(如 en-US、zh-CN)* @return Locale 对象*/private Locale parseLocale(String language) {switch (language.toLowerCase()) {case "en-us":return Locale.US;case "zh-cn":return Locale.SIMPLIFIED_CHINESE;case "zh-hk":return new Locale("zh", "HK");  // 繁體中文default:log.warn("不支持的语言: {}, 使用默认语言", language);return DEFAULT_LOCALE;}}
}

第四步:实现 I18nResponseBodyAdvice(响应拦截器)

package com.example.interceptor;import com.example.domain.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.lang.reflect.Field;
import java.util.Locale;/*** 多语言响应拦截器* 自动翻译统一响应对象中的框架固定消息*/
@Slf4j
@RestControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)  // 最后执行,确保在其他拦截器之后
public class I18nResponseBodyAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType,Class<? extends HttpMessageConverter<?>> converterType) {// 只处理统一响应对象return returnType.getParameterType().equals(Result.class) ||returnType.getParameterType().getName().endsWith(".Result");}@Overridepublic Object beforeBodyWrite(Object body,MethodParameter returnType,MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType,ServerHttpRequest request,ServerHttpResponse response) {if (body == null) {return null;}try {// 获取 message 字段String message = getMessageFromResult(body);if (message == null || message.isEmpty()) {return body;}// 翻译消息(只翻译白名单中的固定消息)String translatedMessage = translateMessage(message);// 如果翻译后的消息与原消息不同,则更新if (!message.equals(translatedMessage)) {setMessageToResult(body, translatedMessage);log.debug("多语言翻译 - 原消息: {}, 翻译后: {}", message, translatedMessage);}} catch (Exception e) {log.error("多语言翻译失败", e);// 异常不影响业务}return body;}/*** 翻译消息(白名单模式)* 只翻译框架固定消息,不翻译业务消息*/private String translateMessage(String message) {Locale locale = LocaleContextHolder.getLocale();String language = locale.toString();// 白名单:只翻译这些固定消息switch (message) {case "成功":case "操作成功":return language.startsWith("en") ? "Success" : language.startsWith("zh_HK") ? "成功" : "成功";case "失败":case "操作失败":return language.startsWith("en") ? "Failed" : language.startsWith("zh_HK") ? "失敗" : "失败";default:// 其他消息不翻译(已在业务层翻译)return message;}}/*** 从结果对象中获取 message 字段*/private String getMessageFromResult(Object result) {try {// 方式1:直接获取字段Field messageField = result.getClass().getDeclaredField("message");messageField.setAccessible(true);return (String) messageField.get(result);} catch (Exception e) {// 方式2:使用 getter 方法try {return (String) result.getClass().getMethod("getMessage").invoke(result);} catch (Exception ex) {log.debug("无法获取message字段", ex);return null;}}}/*** 设置结果对象的 message 字段*/private void setMessageToResult(Object result, String message) {try {// 方式1:直接设置字段Field messageField = result.getClass().getDeclaredField("message");messageField.setAccessible(true);messageField.set(result, message);} catch (Exception e) {// 方式2:使用 setter 方法try {result.getClass().getMethod("setMessage", String.class).invoke(result, message);} catch (Exception ex) {log.debug("无法设置message字段", ex);}}}
}

第五步:注册拦截器

package com.example.config;import com.example.interceptor.LanguageInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;import javax.annotation.Resource;
import java.util.Locale;/*** Web MVC 配置*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Resourceprivate LanguageInterceptor languageInterceptor;/*** 配置 LocaleResolver*/@Beanpublic LocaleResolver localeResolver() {SessionLocaleResolver resolver = new SessionLocaleResolver();resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);return resolver;}/*** 注册拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(languageInterceptor).addPathPatterns("/**")  // 拦截所有请求.excludePathPatterns("/static/**", "/public/**");  // 排除静态资源}
}

第六步:在业务代码中使用

6.1 统一响应对象
package com.example.domain;import lombok.Data;/*** 统一响应对象*/
@Data
public class Result<T> {private Integer code;private String message;private T data;public static <T> Result<T> success() {Result<T> result = new Result<>();result.setCode(200);result.setMessage("成功");  // 这个会被 I18nResponseBodyAdvice 翻译return result;}public static <T> Result<T> success(T data) {Result<T> result = success();result.setData(data);return result;}public static <T> Result<T> failed(String message) {Result<T> result = new Result<>();result.setCode(500);result.setMessage(message);return result;}
}
6.2 Controller 层使用
package com.example.controller;import com.example.domain.Result;
import com.example.utils.MessageUtils;
import org.springframework.web.bind.annotation.*;/*** 示例 Controller*/
@RestController
@RequestMapping("/api/demo")
public class DemoController {/*** 成功响应示例*/@GetMapping("/success")public Result<String> success() {// 返回成功,message 会被自动翻译return Result.success("Hello World");}/*** 错误响应示例*/@GetMapping("/error")public Result<Void> error() {// 使用 MessageUtils 翻译业务消息return Result.failed(MessageUtils.message("error.not.found"));}/*** 带参数的消息示例*/@GetMapping("/validation")public Result<Void> validation(@RequestParam String field) {// 带占位符的消息return Result.failed(MessageUtils.message("validation.required", field));}
}
6.3 全局异常处理器
package com.example.handler;import com.example.domain.Result;
import com.example.exception.BusinessException;
import com.example.utils.MessageUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*** 全局异常处理器*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 业务异常处理*/@ExceptionHandler(BusinessException.class)public Result<Void> handleBusinessException(BusinessException e) {log.warn("业务异常: {}", e.getMessage());// 异常消息应该是消息键,这里进行翻译String message = MessageUtils.message(e.getMessage(), e.getMessage());return Result.failed(message);}/*** 系统异常处理*/@ExceptionHandler(Exception.class)public Result<Void> handleException(Exception e) {log.error("系统异常", e);return Result.failed(MessageUtils.message("error.server.internal"));}
}

前端集成

1. Axios 全局配置(推荐)

// request.js
import axios from 'axios';const service = axios.create({baseURL: '/api',timeout: 10000
});// 请求拦截器
service.interceptors.request.use(config => {// 从本地存储获取语言设置const language = localStorage.getItem('language') || 'zh-CN';config.headers['X-Language'] = language;return config;},error => {return Promise.reject(error);}
);export default service;

2. 语言切换组件

Vue 3 示例
<template><div class="language-switcher"><select v-model="currentLanguage" @change="changeLanguage"><option value="zh-CN">简体中文</option><option value="en-US">English</option><option value="zh-HK">繁體中文</option></select></div>
</template><script setup>
import { ref, onMounted } from 'vue';const currentLanguage = ref('zh-CN');onMounted(() => {// 从本地存储读取语言设置currentLanguage.value = localStorage.getItem('language') || 'zh-CN';
});const changeLanguage = () => {// 保存到本地存储localStorage.setItem('language', currentLanguage.value);// 刷新页面以应用新语言window.location.reload();
};
</script><style scoped>
.language-switcher select {padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;font-size: 14px;
}
</style>
React 示例
import { useState, useEffect } from 'react';function LanguageSwitcher() {const [language, setLanguage] = useState('zh-CN');useEffect(() => {// 从本地存储读取语言设置const savedLanguage = localStorage.getItem('language') || 'zh-CN';setLanguage(savedLanguage);}, []);const changeLanguage = (newLanguage) => {setLanguage(newLanguage);localStorage.setItem('language', newLanguage);// 刷新页面以应用新语言window.location.reload();};return (<div className="language-switcher"><select value={language} onChange={(e) => changeLanguage(e.target.value)}><option value="zh-CN">简体中文</option><option value="en-US">English</option><option value="zh-HK">繁體中文</option></select></div>);
}export default LanguageSwitcher;

3. 单个请求指定语言

// 临时使用英文请求
axios.get('/api/demo/success', {headers: {'X-Language': 'en-US'}
});

最佳实践

1. 消息键命名规范

推荐格式模块.类型.具体内容

# ✅ 推荐(清晰、易维护)
error.not.found=数据不存在
error.unauthorized=没有权限
validation.required={0}不能为空
operation.success=操作成功# ❌ 不推荐(不清晰)
err001=数据不存在
msg1=成功
error=错误

2. 资源文件管理

分类组织

# messages_zh_CN.properties# ========== 通用消息 ==========
operation.success=操作成功
operation.failed=操作失败# ========== 错误消息 ==========
error.not.found=数据不存在
error.unauthorized=没有权限
error.server.internal=服务器内部错误# ========== 验证消息 ==========
validation.required={0}不能为空
validation.invalid={0}格式不正确
validation.length={0}长度必须在{1}到{2}之间

3. 翻译质量保障

使用专业翻译工具

  • 📖 DeepL:质量最高(推荐)
  • 📖 Google Translate:快速翻译
  • 📖 专业翻译人员:重要文案

翻译检查清单

  • ✅ 术语统一
  • ✅ 语气一致
  • ✅ 占位符位置正确
  • ✅ 长度适配 UI

4. 开发流程

新增消息的标准流程

  1. 在资源文件中添加消息键(所有语言)
  2. 在代码中使用 MessageUtils.message()
  3. 测试所有语言
# 测试简体中文
curl -H "X-Language: zh-CN" http://localhost:8080/api/demo/error# 测试英文
curl -H "X-Language: en-US" http://localhost:8080/api/demo/error# 测试繁体中文
curl -H "X-Language: zh-HK" http://localhost:8080/api/demo/error

5. 性能优化

资源文件缓存

# 生产环境:缓存资源文件
spring:messages:cache-duration: 3600  # 1小时# 开发环境:不缓存,方便修改
spring:messages:cache-duration: -1

避免频繁查询

// ❌ 不推荐:循环中查询
for (Item item : items) {String msg = MessageUtils.message("template") + item.getName();
}// ✅ 推荐:循环外查询
String template = MessageUtils.message("template");
for (Item item : items) {String msg = template + item.getName();
}

常见问题

Q1:前端不传 X-Language 会怎样?

A:使用默认语言(简体中文)

// 响应示例
{"code": 200,"message": "成功","data": null
}

Q2:支持动态切换语言吗?

A:支持,每次请求都可以不同

// 请求1:使用简体中文
axios.get('/api/demo/success', { headers: { 'X-Language': 'zh-CN' } });
// 响应:{ "message": "成功" }// 请求2:使用英文
axios.get('/api/demo/success', { headers: { 'X-Language': 'en-US' } });
// 响应:{ "message": "Success" }

实现原理:Locale 存储在 LocaleContextHolder(ThreadLocal),每个请求独立。

Q3:为什么业务消息不在 I18nResponseBodyAdvice 中翻译?

A:避免重复翻译

执行流程

Controller 抛异常↓
GlobalExceptionHandler 捕获└─ MessageUtils.message("error.not.found")  // ✅ 第一次翻译└─ 返回 Result.failed("Not found")↓
I18nResponseBodyAdvice 拦截└─ "Not found" 不在白名单└─ 不翻译  // ✅ 避免重复翻译↓
返回给前端

Q4:如何添加新的语言支持?

步骤

  1. 添加资源文件:messages_ja_JP.properties
  2. 添加解析规则:
private Locale parseLocale(String language) {switch (language.toLowerCase()) {case "zh-cn": return Locale.SIMPLIFIED_CHINESE;case "en-us": return Locale.US;case "zh-hk": return new Locale("zh", "HK");case "ja-jp": return Locale.JAPAN;  // 新增default: return DEFAULT_LOCALE;}
}
  1. 更新白名单翻译规则(如果需要)

Q5:支持占位符参数吗?

A:完全支持

# messages_zh_CN.properties
user.greeting=您好,{0}!您有{1}条新消息。# messages_en_US.properties
user.greeting=Hello, {0}! You have {1} new messages.
String message = MessageUtils.message("user.greeting", "张三", 5);
// 简体中文:您好,张三!您有5条新消息。
// 英文:Hello, 张三! You have 5 new messages.

Q6:如何调试拦截器?

步骤1:启用日志

# application.yml
logging:level:com.example.interceptor: DEBUG

步骤2:发送请求

curl -H "X-Language: en-US" http://localhost:8080/api/demo/error

步骤3:查看日志

[DEBUG] LanguageInterceptor - 多语言拦截器 - 请求头语言: en-US, 解析后的Locale: en_US
[DEBUG] I18nResponseBodyAdvice - 多语言翻译 - 原消息: 成功, 翻译后: Success

Q7:可以在 Service 层获取当前语言吗?

A:可以

@Service
public class NotificationService {public void sendEmail(Long userId) {Locale locale = LocaleContextHolder.getLocale();String language = locale.toString();if (language.startsWith("en")) {sendEnglishEmail(userId);} else {sendChineseEmail(userId);}}
}

注意

  • 同步调用:可用
  • ⚠️ 异步调用:需手动传递 Locale

总结

本文介绍了如何在 Spring Boot 中实现一套完整的多语言国际化拦截器方案:

核心组件

  1. LanguageInterceptor:解析请求头中的语言参数
  2. I18nResponseBodyAdvice:翻译响应中的固定消息
  3. MessageUtils:业务代码中获取翻译消息

设计亮点

  • 双重拦截器:请求拦截 + 响应拦截
  • 白名单模式:只翻译框架固定消息,避免重复翻译
  • 零侵入:业务代码使用工具类,自动翻译
  • 灵活切换:前端通过请求头动态切换
  • 异常保护:翻译失败不影响业务

适用场景

  • ✅ 多语言后台管理系统
  • ✅ 国际化 SaaS 平台
  • ✅ 面向海外用户的 API 服务
  • ✅ 需要动态切换语言的应用

完整示例代码

完整的示例代码已上传至 GitHub(示例地址),包括:

  • ✅ 完整的拦截器实现
  • ✅ 前端集成示例(Vue 3 + React)
  • ✅ 单元测试和集成测试
  • ✅ Docker 部署配置

希望这篇教程对你有帮助!如果有任何问题,欢迎在评论区讨论。


作者:[南风]
发布时间:2025-11-05
标签Spring Boot 国际化 i18n 拦截器 多语言

http://www.dtcms.com/a/573003.html

相关文章:

  • 神经网络—— 人工神经网络导论
  • 实时云渲染平台 LarkXR:2D/3D 应用云推流的高效解决方案
  • 厦门市建设局网站摇号如何自己搭建一个企业网站
  • 郑州市科协网站游戏推广员到底犯不犯法
  • create_map外部函数
  • 中跃建设集团网站湖北省建设厅官方网站
  • 【028】乐器租赁管理系统
  • 散列文件的使用与分析
  • JavaEE初阶——多线程(6)定时器和线程池
  • 【Go语言爬虫】为什么要用Go语言写爬虫?
  • 网络安全培训
  • DNN 预测手术机器人姿态并做补偿包工程样本(2025.09)
  • 13. Qt 绘图-Graphics View
  • php构建网站如何开始展览设计
  • 金仓KingbaseES数据库:迁移、运维与成本优化的全面解析
  • AI推理硬件选型指南:CPU 与 GPU 的抉择
  • 手刃一个爬虫小案例
  • DMFNet代码讲解
  • 论文阅读:《A Universal Model for Human Mobility Prediction》
  • C++ ODR
  • 好看的网站颜色搭配制作网站高手
  • 手机端网站开发框架苏州专业网站seo推广
  • apimonitor工具使用
  • 门禁系统入门:原理、核心组成及工程量计算
  • 制作营销网站公司做网站的流程方法
  • C++进阶:(六)深入浅出分析AVL树:原理与实现
  • N1刷机/救砖用U盘注意事项
  • 数据确权
  • 2025年RAG技术在不同行业的应用场景有哪些
  • 光接入网(OAN)介绍