背景:
项目中经常会遇到需要把某些字典值翻译给前端,我们所有的字典值往往存在一个单独的表里,在业务表中只存储对应的code码,需要使用时则根据code码去查找对应字典项的名称。例如:我们的字典表为dict,表结构如下:
其中code就是字典项code码,name是字典项对应的名称,type是该字典值类型。
我们在业务表person中有个字段sex,代表性别,我们在该表中只存储其code值,需要用时才会根据code 值和type值查询其对应的名称,如下所示:
person表
dict表:
如果每次都手动查询dict表返回给前端太麻烦,有时项目中会缓存全量的字典值,需要用时从缓存中取值,这样做一方面会损耗系统性能,另一方面也不具有实时性。所以,我们需要对其进行改造,实现以下效果:
1、每次返回给前端code值时会自动去数据库查询对应的name值,而不需要手动处理。
2、支持通过注解设置返回给前端的字段名。
3、可以指定配置生效的controller,而对于未配置的controller不会生效。
解决方案:
1、创建自定义注解@DictField,
@JacksonAnnotationsInside
@JsonSerialize(using = DictSerializer.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface DictField {
//返回字段的字段名
String name() default "";
//字段在字典表中的类型
String value();
}2、编写序列化处理逻辑
public class DictSerializer extends JsonSerializer<String> implements ContextualSerializer {
private DictField dictField;
private BeanProperty beanProperty;
public DictSerializer(DictField dictField, BeanProperty beanProperty) {
this.dictField = dictField;
this.beanProperty = beanProperty;
}
public DictSerializer() {
}
public static String getDictName(String code,String type) {
TransitSupportDictService transitSupportDictService = ApplicationContextUtil.getBean(TransitSupportDictService.class);
return transitSupportDictService.getTypeName(code,type);
}
@Override
//数据序列化时,会执行此方法,value是标注@DictField注解的字段值,即code码,通过getDictName(value,dictField.value())方法
//根据字段类型dictField.value()和code码查询出该字段名称,设置到字段名dictField.name()上。
public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(value);
String dictName = getDictName(value,dictField.value());
if (StringUtils.isNotEmpty(dictName)) {
String name = dictField.name();
if (name.length() == 0) {
name = beanProperty.getName() + "_mc";
}
jsonGenerator.writeStringField(name, dictName);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
DictField dictField = beanProperty.getAnnotation(DictField.class);
if (dictField != null) {
return new DictSerializer(dictField, beanProperty);
} else {
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
}
return serializerProvider.findNullValueSerializer(null);
}
}3、使用自定义注解
4.指定生效controller
由于我们项目中底层做了重写,默认使用fastjson,所以需要重新做拦截,针对指定的controller 改用jackjson我们之前的配置才会生效。
@ControllerAdvice
public class TransitSupportDictResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private final MappingJackson2HttpMessageConverter converter;
public TransitSupportDictResponseBodyAdvice() {
converter = buildConverter();
}
private MappingJackson2HttpMessageConverter buildConverter() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
ObjectMapper objectMapper = builder
.indentOutput(true)
.simpleDateFormat("yyyy-MM-dd HH:mm:ss")
.timeZone(TimeZone.getTimeZone("GMT+08:00"))
.mixIn(ResponseMessage.class, ResponseMessageIgnore.class)
.build();
return new MappingJackson2HttpMessageConverter(objectMapper);
}
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
//只有配置了的controller 才会生效
return (returnType.getDeclaringClass().getPackage().getName().startsWith(transitSupportCommonProperties.getBasePackages())
&& returnType.getMethod().getReturnType() == ResponseMessage.class)
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body == null) {
return null;
}
try {
converter.write(body, returnType.getGenericParameterType(), MediaType.APPLICATION_JSON_UTF8, response);
} catch (IOException ignore) {
}
return null;
}5、最终效果
至此,即可实现自动化翻译项目中的字典值。