框架:springboot 2.3.3
项目交付以后本来后端响应的消息统一响应的是code没有附带消息,消息前端通过code进行匹配多种语言,然后前端三哥突然打算让我直接返回对应的语言信息,好吧,只能临时更改了
恰巧前一天琢磨了一下@RestControllerAdvice
的用法,今天恰巧排上了用场
配置类
有了依赖以后我们创建一个配置类,具体功能在里面解释
@Configuration
public class LocaleConfig {
/**
* 默认解析器 其中locale表示默认语言,当请求中未包含语种信息,则设置默认语种
* 当前默认为CHINA,zh_CN
*/
@Bean
public SessionLocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
// 这里配置了在没有指定语言的默认情况下匹配的语言种类,你也可以追进去看看每个语言的tag是什么
// tag就是你语言的代号,下面lang值对应的参数,这是一个全局变量。
localeResolver.setDefaultLocale(Locale.ENGLISH);
return localeResolver;
}
/**
* 默认拦截器 其中lang表示切换语言的参数名
* 拦截请求,获取请求参数lang种包含的语种信息并重新注册语种信息
*/
@Bean
public WebMvcConfigurer localeInterceptor() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
// 这里指定一个从前端form-data或者url参数获取语言tag的key值,比如这里是lang
// 前端就需要传参?lang=zh_CN。zh是语言tag,对应全局变量中的语言,CN是国家代号,这里似乎没用上最好还是设置上吧
localeInterceptor.setParamName("lang");
registry.addInterceptor(localeInterceptor);
}
};
}
}
配置语言包
在resources下的language包里增加如下文件
yml配置文件添加内容
spring:
messages:
#指定了语言包所在位置
basename: language.message
cache-duration: 3600
encoding: UTF-8
首先创建一个首要语言包message.properties
,不需要什么内容,可以为空,后续我们通过拓展包对语种进行归类
然后我们分别创建其他语言的语言包,注意格式message****
语言包内容[同一个语义对应的同一个key]
使用
@Autowired
private MessageSource messageSource;
@GetMapping("/info")
public Result<User> getInfo(){
User user = new User();
return new Result<>(true,CodeEnum.SUCCESS.getCode(),
messageSource.getMessage(
"success", // 这里是语言包中properties对应的key值
null,// 占位参数,一般不设置
LocaleContextHolder.getLocale() // 区域选择,这里选择了当前语言环境,也就是根据前端参数设置的语言
),user);
}
解决当前问题
当前情况是我已经全部统一通过Result返回了,并且只返回了code并没有msg,我现在在Result里新增一个成员属性msg
现在我们返回的结果就是
{
flag:true
code:20
msg:null
data:object
}
我们创建一个接口层增强器@RestControllerAdvice
统一处理响应[也可以在拦截器中处理]
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper; // 用来向前端直接写出Json
@Autowired
private MessageSource messageSource;// 语言转换对象
@Autowired
private HttpServletResponse response;// 响应对象
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
/**
* 在响应前对结果进行处理。这里一般配合异常处理进行使用,否则出现异常的时候依然会被封装进来
* @param o 原始响应体
* @param methodParameter 请求方法参数
* @param mediaType
* @param aClass
* @param serverHttpRequest 请求
* @param serverHttpResponse 响应
* @return 处理过的响应体
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
int status = response.getStatus();
// 这里为了避免出现失败的时候依然封装为成功
if (status!=200)
return Result.fail(status,null);
if (o instanceof String) {
// 如果是字符串就以Json写出
return objectMapper.writeValueAsString(Result.success("成功", o));
}
if (o instanceof Result){
// 如果是结果实体,就调用下面的方法取出code作为key匹配消息进行填充msg
return languageFormat(o);
}
// 如果都不符合证明返回的是其他实体。我们将他封装进data进行响应
return Result.success("成功", o);
}
/**
* 语言转换方法
* @param o
* @return
*/
public Object languageFormat(Object o){
// 由于调用方法前使用了instanceof确定了具体类型,我们这里可以直接强转
Result<?> result = (Result<?>) o;
// 强转以后取出code
String key = result.getCode().toString();
// 设置msg,用code作为key获取参数
result.setMsg(messageSource.getMessage(key,null, LocaleContextHolder.getLocale()));
return result;
}
}
前端请求以及响应
注意这里的param必须和你的配置文件后缀一致
如果不想统一响应可以去除最后的那段,如果加了那个:当匹配不上的时候就包装起来响应,那我们不处理的话就只是检测到是Result就取出code并匹配填入
后续补充:
支持多个数据包匹配
我们只需要在配置文件中将这两个包都写入配置文件中即可
server:
port: 8080
servlet:
context-path: /learn
spring:
messages:
#指定了语言包所在位置
basename:
language.message,
language.scop
cache-duration: 3600
encoding: UTF-8
需要注意的一点就是,这个只是相当于扩容一样,只是方便管理,key还是会出现冲突的
由于是按照配置文件中配置的顺序进行匹配
的,所以如果出现key值冲突,会优先显示最先匹配到的那个
关于占位符
也就是在提取的时候,传入第二个参数,自动替换占位符
占位符在模板中的写法
单个占位符:
{0}: 表示第一个参数。
{1}: 表示第二个参数。
依此类推。
带有格式化选项的占位符:
{0,number}: 表示第一个参数为数字类型。
{1,date}: 表示第二个参数为日期类型。
{2,time}: 表示第三个参数为时间类型。
例如可以写作
msg = 你好{0},感谢您的注册!
msg2 = 你好{0,string}感谢您的注册您是第{1,number}个注册的用户
使用占位符
param = [1,2]
messageSource.getMessage(
result.getCode(),
param,
LocaleContextHolder.getLocale()
)
注意如果只提供了一个占位符,比如: 你好{0}
然后我们传递的参数又是一个数组,比如:[‘张三’,‘李四’,‘王五’],
由于是按顺序替换,所以最终只有张三会被替换进去,得到结果,“你好张三”
如果想都显示进去,可以手动转换成字符串传入
占位符指定数组第几个参数
msg=你好{0[2]}
这样的写法就能指定使用参数数组的第几个参数
实际使用记录
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 这里为了避免出现失败的时候依然封装为成功,这里只会拦截未经处理的,我们手动抛出的非200异常并不会受影响,因为那个是在这个之后才处理;
int status = response.getStatus();
if (status != 200)
return o;
if (o instanceof String) {
// 如果是字符串就用objectMapper以Json写出
return objectMapper.writeValueAsString(
new Result<>(true, 20000, translationTools.get("20000"), o));
}
if (o instanceof Result) {
// 如果是结果实体,就调用下面的方法取出code作为key匹配消息进行填充msg,并且原路返回
return languageFormat(o);
}
// 避免swagger失效
if (Arrays.asList(exclude).contains(methodParameter.getDeclaringClass().getSimpleName())) {
return o;
}
log.info("有响应结果跳过了Result封装,请求方法为:{},class:{},o:{}", methodParameter.getDeclaringClass().getSimpleName(), aClass, o);
// 如果都不符合证明返回的是其他实体。我们将他封装进data进行响应
return new Result<>(true, 20000, translationTools.get("20000"), o);
}
补充
可以自定义地区对象
import java.util.Locale;
new Locale("en_US")
String body = messageSource.getMessage(msgCode, null,new Locale("zh_CN"));