上回用注解搞定了数据字典转换后,小伙伴们把目光看向了用户名。我们业务场景要求,列表数据要展示编辑人,再加上某些业务会需要展示各种操作人,但是给客户展示用户ID或者username不太好,所以要将username转成realName,在给客户展示。这些人懒得自己写逻辑,希望我用注解方式解决下。
基本逻辑跟之前数据字典差不多,废话不多说直接上代码吧
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = UserSerializer.class)
public @interface User {
}
这里声明了一个用户注解,不需要任何属性,他的作用就是标记用户名转换
public class UserSerializer extends StdSerializer<Object> implements ContextualSerializer {
/** 用户注解 */
private User user;
public UserSerializer() {
super(Object.class);
}
public UserSerializer(User user) {
super(Object.class);
this.user = user;
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (Objects.isNull(value)) {
gen.writeObject(value);
return;
}
String label = UserDataCache.getLabel(value.toString());
gen.writeObject(value);
if (label instanceof String){
gen.writeFieldName(gen.getOutputContext().getCurrentName()+"Name");
}else {
gen.writeFieldName(gen.getOutputContext().getCurrentName()+"Object");
}
gen.writeObject(label);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty beanProperty) throws JsonMappingException {
if (Objects.isNull(beanProperty)){
return prov.findValueSerializer(beanProperty.getType(), beanProperty);
}
User user = beanProperty.getAnnotation(User.class);
if (Objects.nonNull(user)){
return this;
}
return prov.findNullValueSerializer(null);
}
}
createContextual 方法主要作用是字段的注解,因为字段的上下文信息在运行期不会改变,所以只会在第一次序列化字段时调用,因此不用担心影响性能。
代码中逻辑可编写为,当字段为拥有@User注解时,就取出注解中的value值,这样在serialize方法中便可以得到这个value值了。
serialize主要逻辑是,根据传入的value值,调用方法获取realName,将结果写到Json中。我的逻辑是原先的字段不动,我再加一个字段,用来存放realName。例,原先字段为updateUser,我会添加一个字段:updateUserName,存放中文名。
public class UserDataCache {
public static String getLabel(String username) {
// 首先从字典加载中获取
UserDataOptions dataOptions = SpringUtil.getBean(UserDataHandler.class).getRealname(username);
if(Objects.nonNull(dataOptions)) {
return dataOptions.getRealname();
}
return "";
}
}
这里就是调用获取的逻辑
@Component
public class UserDataHandler extends AbstractUserHandler {
@Autowired
private UserHandlerAware userHandlerAware;
@Autowired
private RedisTemplate redisTemplate;
public UserDataHandler() {
cacheData = CacheUtil.newFIFOCache(10000,1000*60*60*24);
}
@Override
public UserDataOptions getRealname(String username) {
if(Objects.nonNull(cacheData.get(username))){
return cacheData.get(username);
}
Boolean aBoolean = redisTemplate.hasKey("user:" + username);
if(!aBoolean){
UserDataOptions userDataOptions = getUserDataHandler().getRealnameByUsername(username);
put(username,userDataOptions);
}
Object o = redisTemplate.opsForValue().get("user:" + username);
cacheData.put(username,(UserDataOptions)o);
return (UserDataOptions)o;
}
private IUserDataHandler getUserDataHandler(){
return userHandlerAware.getUserHandler();
}
}
这里跟之前不同的是,添加了本地缓存。之前数据字典,是直接从Redis获取,获取不到直接查数据库,后来提出一个效率问题,一个json串中有N个注解就要查N次Redis,有M条数据就要查N*M次Redis,这样设计会浪费一部分性能,导致查询时间变长。
后来添加了本地缓存,暂时规定大小10000,有效时间一天。先从本地缓存获取,没有在查Redis等,明显感觉使用本地缓存后查询效率增加了。但同时也会存在数据更新不及时问题,我修改了用户信息,但查询的数据仍然没变,这个我暂时没有处理,因为我们的用户基本很少会修改,具体看项目取舍吧。
public class UserHandlerAware implements ApplicationContextAware {
//默认实现
private IUserDataHandler userHandler = new DefaultUserDataHandler();
/**
* 功能描述: 获取应用上下文并获取相应的接口实现类
* @param applicationContext 上下文
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 根据抽象类,返回字典实现
Map<String, AbstractUserDataHandler> userMap = applicationContext.getBeansOfType(AbstractUserDataHandler.class);
if(MapUtil.isNotEmpty(userMap)) {
userHandler = userMap.entrySet().iterator().next().getValue();
}
}
}
public class DefaultUserDataHandler extends AbstractUserDataHandler{
@Override
public UserDataOptions getRealnameByUsername(String username) {
return new UserDataOptions();
}
}
这一部分主要是注册查询接口类,我提供了接口,也提供了默认实现,默认返回空,避免报错影响逻辑。微服务架构下,用户解析等可以放到common下,但数据获取只能在本服务中,本服务调用用户服务获取到数据后返回。
以上为全部内容,新手写文,欢迎指正。