问题描述
在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.三个文件的作用
- SwaggerConfiguration.java - API文档配置
作用:生成Swagger/OpenAPI文档中的错误响应示例
- 这是纯文档说明,不参与实际业务逻辑
- 告诉API使用者:当用户名或密码错误时,会返回这个错误信息
- 用于自动生成API文档(你在Swagger UI上看到的示例)
- 修改这里只影响文档显示,不影响实际错误返回
- ThingsboardErrorResponseHandler.java - 错误处理核心
作用:实际处理认证异常并返回错误响应的核心处理器
- 这是真正的业务逻辑所在!
- 当BadCredentialsException或UsernameNotFoundException发生时
- 会实际构造并返回"Invalid username or password"错误响应
- 这是必须修改的地方才能改变实际返回的错误信息
- 位置:handleAuthenticationException方法中
- AuthControllerTest.java - 单元测试
作用:自动化测试用例,验证登录失败场景
- 这是测试代码,只在测试时运行
- 模拟用户输入错误密码的场景
- 断言(assert)返回的错误信息包含"invalid username or password"
- 确保错误处理逻辑正确工作
- 修改这里只影响测试验证逻辑
4.ThingsBoard 的国际化现状
由此可以看出ThingsBoard 的国际化现状:
- 前端有国际化
- UI 有完整的 i18n 支持:/ui-ngx/src/assets/i18n/ 下有多种语言文件
- 后端几乎无国际化
- 错误信息都是硬编码英文
- 后端异常消息直接返回给前端
- 前后端语言不一致的隐患
5.修改优先级
| 文件 | 重要性 | 是否需要修改 | 影响范围 |
|---|---|---|---|
| ThingsboardErrorResponseHandler.java | ⭐⭐⭐⭐⭐ | 必须修改 | 实际用户看到的错误 |
| SwaggerConfiguration.java | ⭐⭐⭐ | 建议修改 | API文档显示 |
| AuthControllerTest.java | ⭐ | 可选修改 | 测试用例通过性 |
问题解决
- 修改源代码并编译
... - 直接在JAR文件中修改硬编码的字符串
...