Skip to content

Commit 21fe248

Browse files
committed
[fit] 实现国际化消息处理器,完善测试
1 parent 26a293e commit 21fe248

File tree

12 files changed

+496
-14
lines changed

12 files changed

+496
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
3+
* This file is a part of the ModelEngine Project.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
package modelengine.fitframework.validation;
8+
9+
import modelengine.fitframework.util.LocaleContextHolder;
10+
11+
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
12+
13+
import jakarta.validation.MessageInterpolator;
14+
15+
import java.util.Locale;
16+
17+
/**
18+
* 检验消息处理包装类。
19+
* <p>
20+
* 从 {@link LocaleContextHolder} 中获取当前线程设置的 {@link Locale} 并委托 {@link MessageInterpolator} 去处理消息。
21+
* </p>
22+
*
23+
* @author 阮睿
24+
* @since 2025-07-31
25+
*/
26+
public class LocaleContextMessageInterpolator implements MessageInterpolator {
27+
28+
private final MessageInterpolator targetInterpolator;
29+
30+
public LocaleContextMessageInterpolator(MessageInterpolator targetInterpolator) {
31+
this.targetInterpolator = targetInterpolator;
32+
}
33+
34+
public LocaleContextMessageInterpolator() {
35+
this.targetInterpolator = new ParameterMessageInterpolator();
36+
}
37+
38+
@Override
39+
public String interpolate(String messageTemplate, Context context) {
40+
return this.targetInterpolator.interpolate(messageTemplate, context, LocaleContextHolder.getLocale());
41+
}
42+
43+
@Override
44+
public String interpolate(String messageTemplate, Context context, Locale locale) {
45+
return this.targetInterpolator.interpolate(messageTemplate, context, locale);
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package modelengine.fitframework.validation;
2+
3+
import modelengine.fit.http.annotation.PostMapping;
4+
import modelengine.fit.http.annotation.RequestBody;
5+
import modelengine.fit.http.annotation.RequestMapping;
6+
import modelengine.fitframework.annotation.Component;
7+
import modelengine.fitframework.validation.data.Company;
8+
9+
import jakarta.validation.Valid;
10+
11+
/**
12+
* 地区验证控制器 - 测试ValidationHandler与LocaleContextMessageInterpolator的集成
13+
*
14+
* @author 阮睿
15+
* @since 2025-01-27
16+
*/
17+
@Component
18+
@RequestMapping(path = "/validation/locale", group = "地区验证测试接口")
19+
@Validated
20+
public class LocaleValidationController {
21+
22+
/**
23+
* 测试验证消息的地区化 - 使用简单参数
24+
*/
25+
@PostMapping(path = "/simple", description = "测试简单参数的地区化验证消息")
26+
public void validateSimpleParam(@RequestBody @Valid Company company) {}
27+
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package modelengine.fitframework.validation;
2+
3+
import modelengine.fit.http.client.HttpClassicClientResponse;
4+
import modelengine.fit.http.entity.Entity;
5+
import modelengine.fit.http.entity.ObjectEntity;
6+
import modelengine.fitframework.annotation.Fit;
7+
import modelengine.fitframework.test.annotation.MvcTest;
8+
import modelengine.fitframework.test.domain.mvc.MockMvc;
9+
import modelengine.fitframework.test.domain.mvc.request.MockMvcRequestBuilders;
10+
import modelengine.fitframework.test.domain.mvc.request.MockRequestBuilder;
11+
import modelengine.fitframework.validation.data.Company;
12+
13+
import org.junit.jupiter.api.AfterEach;
14+
import org.junit.jupiter.api.DisplayName;
15+
import org.junit.jupiter.api.Test;
16+
17+
import java.io.IOException;
18+
import java.util.Map;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
/**
23+
* {@link LocaleValidationController} 的测试集。
24+
*
25+
* @author 阮睿
26+
* @since 2025-07-18
27+
*/
28+
@MvcTest(classes = {LocaleValidationController.class})
29+
@DisplayName("测试地区化验证消息功能")
30+
public class LocaleValidationControllerTest {
31+
32+
@Fit
33+
private MockMvc mockMvc;
34+
35+
private HttpClassicClientResponse<?> response;
36+
37+
@AfterEach
38+
void teardown() throws IOException {
39+
if (this.response != null) {
40+
this.response.close();
41+
}
42+
}
43+
44+
@Test
45+
@DisplayName("测试法文地区的验证消息")
46+
void shouldReturnChineseValidationMessage() {
47+
Company invalidCompany = new Company(null);
48+
49+
MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/locale/simple")
50+
.header("Accept-Language", "fr")
51+
.jsonEntity(invalidCompany)
52+
.responseType(Map.class);
53+
54+
this.response = this.mockMvc.perform(requestBuilder);
55+
// 获取JSON格式的错误信息
56+
String errorMessage = "";
57+
if (this.response.entity().isPresent()) {
58+
Entity entity = this.response.entity().get();
59+
if (entity instanceof ObjectEntity) {
60+
ObjectEntity<?> objectEntity = (ObjectEntity<?>) entity;
61+
Object errorObj = objectEntity.object();
62+
if (errorObj instanceof Map) {
63+
Map<String, Object> errorMap = (Map<String, Object>) errorObj;
64+
errorMessage =
65+
errorMap.get("error") != null ? errorMap.get("error").toString() : errorMap.toString();
66+
} else {
67+
errorMessage = errorObj.toString();
68+
}
69+
}
70+
}
71+
72+
assertThat(errorMessage).isEqualTo("validateSimpleParam.company.employees: ne doit pas être nul");
73+
assertThat(this.response.statusCode()).isEqualTo(500);
74+
}
75+
76+
@Test
77+
@DisplayName("测试英文地区的验证消息")
78+
void shouldReturnEnglishValidationMessage() {
79+
Company invalidCompany = new Company(null);
80+
81+
MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/locale/simple")
82+
.header("Accept-Language", "en-us")
83+
.jsonEntity(invalidCompany)
84+
.responseType(Map.class);
85+
86+
this.response = this.mockMvc.perform(requestBuilder);
87+
// 获取JSON格式的错误信息
88+
String errorMessage = "";
89+
if (this.response.entity().isPresent()) {
90+
Entity entity = this.response.entity().get();
91+
if (entity instanceof ObjectEntity) {
92+
ObjectEntity<?> objectEntity = (ObjectEntity<?>) entity;
93+
Object errorObj = objectEntity.object();
94+
if (errorObj instanceof Map) {
95+
Map<String, Object> errorMap = (Map<String, Object>) errorObj;
96+
errorMessage =
97+
errorMap.get("error") != null ? errorMap.get("error").toString() : errorMap.toString();
98+
} else {
99+
errorMessage = errorObj.toString();
100+
}
101+
}
102+
}
103+
104+
assertThat(errorMessage).isEqualTo("validateSimpleParam.company.employees: must not be null");
105+
assertThat(this.response.statusCode()).isEqualTo(500);
106+
}
107+
108+
@Test
109+
@DisplayName("测试URL参数指定地区")
110+
void shouldUseLocaleFromUrlParam() {
111+
Company invalidCompany = new Company(null);
112+
113+
MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/locale/simple")
114+
.param("locale", "en-US")
115+
.jsonEntity(invalidCompany)
116+
.responseType(Map.class);
117+
118+
this.response = this.mockMvc.perform(requestBuilder);
119+
120+
// 获取JSON格式的错误信息
121+
String errorMessage = "";
122+
if (this.response.entity().isPresent()) {
123+
Entity entity = this.response.entity().get();
124+
if (entity instanceof ObjectEntity) {
125+
ObjectEntity<?> objectEntity = (ObjectEntity<?>) entity;
126+
Object errorObj = objectEntity.object();
127+
if (errorObj instanceof Map) {
128+
Map<String, Object> errorMap = (Map<String, Object>) errorObj;
129+
errorMessage =
130+
errorMap.get("error") != null ? errorMap.get("error").toString() : errorMap.toString();
131+
} else {
132+
errorMessage = errorObj.toString();
133+
}
134+
}
135+
}
136+
137+
assertThat(errorMessage).isEqualTo("validateSimpleParam.company.employees: must not be null");
138+
assertThat(this.response.cookies().get("locale").isPresent());
139+
assertThat(this.response.cookies().get("locale").get().value()).isEqualTo("en-US");
140+
assertThat(this.response.statusCode()).isEqualTo(500);
141+
}
142+
}
143+
144+
145+
146+
147+
148+
149+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package modelengine.fitframework.validation.data;
2+
3+
import modelengine.fit.http.util.i18n.LocaleResolveFilter;
4+
import modelengine.fitframework.annotation.Bean;
5+
import modelengine.fitframework.annotation.Component;
6+
7+
@Component
8+
public class localeResolveConfig {
9+
@Bean
10+
public LocaleResolveFilter localeResolveFilter() {
11+
return new LocaleResolveFilter();
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
3+
* This file is a part of the ModelEngine Project.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
package modelengine.fitframework.validation;
8+
9+
import modelengine.fitframework.util.LocaleContextHolder;
10+
11+
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
12+
13+
import javax.validation.MessageInterpolator;
14+
15+
import java.util.Locale;
16+
17+
/**
18+
* 检验消息处理包装类。
19+
* <p>
20+
* 从 {@link LocaleContextHolder} 中获取当前线程设置的 {@link Locale} 并委托 {@link MessageInterpolator} 去处理消息。
21+
* </p>
22+
*
23+
* @author 阮睿
24+
* @since 2025-07-31
25+
*/
26+
public class LocaleContextMessageInterpolator implements MessageInterpolator {
27+
28+
private final MessageInterpolator targetInterpolator;
29+
30+
public LocaleContextMessageInterpolator(MessageInterpolator targetInterpolator) {
31+
this.targetInterpolator = targetInterpolator;
32+
}
33+
34+
public LocaleContextMessageInterpolator() {
35+
this.targetInterpolator = new ParameterMessageInterpolator();
36+
}
37+
38+
@Override
39+
public String interpolate(String messageTemplate, Context context) {
40+
return this.targetInterpolator.interpolate(messageTemplate, context, LocaleContextHolder.getLocale());
41+
}
42+
43+
@Override
44+
public String interpolate(String messageTemplate, Context context, Locale locale) {
45+
return this.targetInterpolator.interpolate(messageTemplate, context, locale);
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package modelengine.fitframework.validation;
2+
3+
import javax.validation.Valid;
4+
import javax.validation.constraints.NotBlank;
5+
import javax.validation.constraints.Min;
6+
7+
import modelengine.fit.http.util.i18n.LocaleResolveFilter;
8+
import modelengine.fitframework.annotation.Bean;
9+
import modelengine.fitframework.annotation.Component;
10+
import modelengine.fitframework.util.LocaleContextHolder;
11+
import modelengine.fitframework.validation.data.Company;
12+
import modelengine.fitframework.validation.data.Employee;
13+
import modelengine.fit.http.annotation.PostMapping;
14+
import modelengine.fit.http.annotation.RequestBody;
15+
import modelengine.fit.http.annotation.RequestMapping;
16+
17+
import java.util.Locale;
18+
19+
/**
20+
* 地区验证控制器 - 测试ValidationHandler与LocaleContextMessageInterpolator的集成
21+
*
22+
* @author 阮睿
23+
* @since 2025-01-27
24+
*/
25+
@Component
26+
@RequestMapping(path = "/validation/locale", group = "地区验证测试接口")
27+
@Validated
28+
public class LocaleValidationController {
29+
30+
/**
31+
* 测试验证消息的地区化 - 使用简单参数
32+
*/
33+
@PostMapping(path = "/simple", description = "测试简单参数的地区化验证消息")
34+
public void validateSimpleParam(@RequestBody @Valid Company company) {}
35+
36+
}

0 commit comments

Comments
 (0)