ThingsBoard - APP密码输入错误提示改成中文

问题描述

在APP中,密码输入错误,显示英文。

问题分析

APP部分已经全面翻译,出现英文应该是服务端的反馈。看来需要彻底解决电脑端翻译问题。

1.搜索已编译代码

由于资源管理器搜索能力有限,使用Visual Code进行搜索,先将相关jar文件解压。在D盘建立一个临时目录test,将thingsboard.jar拷入

  • 进入下面目录,执行对应命令进行解压
    D:\test\thingsboard\lib>jar xf thingsboard.jar
    D:\test\thingsboard\lib\BOOT-INF\lib>jar xf ui-ngx-4.1.0.jar
  • 用Visual Code打开D:\test\thingsboard目录,搜索Invalid username or password
    没有搜到。
    发现D:\test\thingsboard\lib\BOOT-INF\lib\public\assets\locale有翻译文件,但是都是完全翻译了的。

该方法没有结果。

2.搜索源代码

下载源代码,搜索,有三个文件包含Invalid username or password

java 复制代码
//1.\thingsboard-master\application\src\main\java\org\thingsboard\server\exception\ThingsboardErrorResponseHandler.java
public class SwaggerConfiguration {
    ...
    private static ApiResponses loginErrorResponses() {
        ApiResponses apiResponses = new ApiResponses();

        apiResponses.addApiResponse("401", errorResponse("Unauthorized",
                Map.of(
                        "bad-credentials", errorExample("Bad credentials",
                                ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
                        "token-expired", errorExample("JWT token expired",
                                ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)),
                        "account-disabled", errorExample("Disabled account",
                                ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
                        "account-locked", errorExample("Locked account",
                                ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
                        "authentication-failed", errorExample("General authentication error",
                                ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED))
                )
        ));
        ...
    }
    ...
}    

//2.\thingsboard-master\application\src\main\java\org\thingsboard\server\exception\ThingsboardErrorResponseHandler.java

public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler, ErrorController {
    ....
    private void handleAuthenticationException(AuthenticationException authenticationException, HttpServletResponse response) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        if (authenticationException instanceof BadCredentialsException || authenticationException instanceof UsernameNotFoundException) {
            JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
        } else if (authenticationException instanceof DisabledException) {
            JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
        } else if (authenticationException instanceof LockedException) {
            JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
        } else if (authenticationException instanceof JwtExpiredTokenException) {
            JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED));
        } else if (authenticationException instanceof AuthMethodNotSupportedException) {
            JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of(authenticationException.getMessage(), ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
        } else if (authenticationException instanceof UserPasswordExpiredException expiredException) {
            String resetToken = expiredException.getResetToken();
            JacksonUtil.writeValue(response.getWriter(), ThingsboardCredentialsExpiredResponse.of(expiredException.getMessage(), resetToken));
        } else if (authenticationException instanceof UserPasswordNotValidException expiredException) {
            JacksonUtil.writeValue(response.getWriter(), ThingsboardCredentialsViolationResponse.of(expiredException.getMessage()));
        } else if (authenticationException instanceof CredentialsExpiredException credentialsExpiredException) {
            JacksonUtil.writeValue(response.getWriter(), ThingsboardCredentialsViolationResponse.of(credentialsExpiredException.getMessage(), ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
        } else {
            JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
        }
    }
    ...
}

//3.\thingsboard-master\application\src\test\java\org\thingsboard\server\controller\AuthControllerTest.java
public class AuthControllerTest extends AbstractControllerTest {
    ....
    public void testFailedLogin() throws Exception {
        int maxFailedLoginAttempts = 3;
        loginSysAdmin();
        updateSecuritySettings(securitySettings -> {
            securitySettings.setMaxFailedLoginAttempts(maxFailedLoginAttempts);
        });
        loginTenantAdmin();

        for (int i = 0; i < maxFailedLoginAttempts; i++) {
            String error = getErrorMessage(doPost("/api/auth/login",
                    new LoginRequest(CUSTOMER_USER_EMAIL, "IncorrectPassword"))
                    .andExpect(status().isUnauthorized()));
            assertThat(error).containsIgnoringCase("invalid username or password");
        }
        ...
    }
    ...
}

3.三个文件的作用

  1. SwaggerConfiguration.java - API文档配置
    作用:生成Swagger/OpenAPI文档中的错误响应示例
  • 这是纯文档说明,不参与实际业务逻辑
  • 告诉API使用者:当用户名或密码错误时,会返回这个错误信息
  • 用于自动生成API文档(你在Swagger UI上看到的示例)
  • 修改这里只影响文档显示,不影响实际错误返回
  1. ThingsboardErrorResponseHandler.java - 错误处理核心
    作用:实际处理认证异常并返回错误响应的核心处理器
  • 这是真正的业务逻辑所在!
  • 当BadCredentialsException或UsernameNotFoundException发生时
  • 会实际构造并返回"Invalid username or password"错误响应
  • 这是必须修改的地方才能改变实际返回的错误信息
  • 位置:handleAuthenticationException方法中
  1. AuthControllerTest.java - 单元测试
    作用:自动化测试用例,验证登录失败场景
  • 这是测试代码,只在测试时运行
  • 模拟用户输入错误密码的场景
  • 断言(assert)返回的错误信息包含"invalid username or password"
  • 确保错误处理逻辑正确工作
  • 修改这里只影响测试验证逻辑
4.ThingsBoard 的国际化现状

由此可以看出ThingsBoard 的国际化现状:

  1. 前端有国际化
  • UI 有完整的 i18n 支持:/ui-ngx/src/assets/i18n/ 下有多种语言文件
  1. 后端几乎无国际化
  • 错误信息都是硬编码英文
  • 后端异常消息直接返回给前端
  • 前后端语言不一致的隐患
5.修改优先级
文件 重要性 是否需要修改 影响范围
ThingsboardErrorResponseHandler.java ⭐⭐⭐⭐⭐ 必须修改 实际用户看到的错误
SwaggerConfiguration.java ⭐⭐⭐ 建议修改 API文档显示
AuthControllerTest.java 可选修改 测试用例通过性

问题解决

  1. 修改源代码并编译
    ...
  2. 直接在JAR文件中修改硬编码的字符串
    ...
相关推荐
皮皮林55110 小时前
利用闲置 Mac 从零部署 OpenClaw 教程 !
java
华仔啊15 小时前
挖到了 1 个 Java 小特性:var,用完就回不去了
java·后端
SimonKing15 小时前
SpringBoot整合秘笈:让Mybatis用上Calcite,实现统一SQL查询
java·后端·程序员
日月云棠1 天前
各版本JDK对比:JDK 25 特性详解
java
用户8307196840821 天前
Spring Boot 项目中日期处理的最佳实践
java·spring boot
JavaGuide1 天前
Claude Opus 4.6 真的用不起了!我换成了国产 M2.5,实测真香!!
java·spring·ai·claude code
IT探险家1 天前
Java 基本数据类型:8 种原始类型 + 数组 + 6 个新手必踩的坑
java
花花无缺1 天前
搞懂new 关键字(构造函数)和 .builder() 模式(建造者模式)创建对象
java
用户908324602731 天前
Spring Boot + MyBatis-Plus 多租户实战:从数据隔离到权限控制的完整方案
java·后端
桦说编程1 天前
实战分析 ConcurrentHashMap.computeIfAbsent 的锁冲突问题
java·后端·性能优化