统一处理响应实体中的ID转换

在工作中,我们经常有这样的业务情况,实体间通过id实现数据业务上的关联,比如订单和用户,订单的创建人id、商品id等,在页面查询时我们需要将对应的id信息转换成对应的中文描述,比如用户中文名称,商品中文名称等。
如果是单条数据的展示还好,但是设计到列表查询,如何高效、优雅地实现这个效果呢?

现在接口返回的数据基本都是JSON格式,比如spring中使用了jackson,在controller层对结果进行json序列化,而我们要做的就是在序列化的过程中,实现id的转换

使用方式

  1. 定义实体转换接口

由于需要对订单实体中的创建人id进行转换

public interface UserConvert {

    String USER_CACHE = "USER_CACHE";

    String userId();

    default ConvertItem getUserConvert(){
        if( userId() == null ){
            return null;
        }
        return new ConvertItem(userId(), USER_CACHE);
    }

}
  1. 定义接口转换适配器

基于上面UserConvert的处理,基于缓存实现,同时支持一个实体中多个,比如商品名称、商品分类等

public class UserConvertProvider extends CacheItemConvertAdapter {

    private static String name = UserConvert.USER_CACHE;

    public UserConvertProvider() {
        super(name, User.class);
    }

    @Override
    public boolean support(ConvertItem convertItem) {
        return convertItem != null && convertItem.getName().equals(name);
    }

    @Override
    public String convert(ConvertItem convertItem) {
        if( convertItem == null ){
            return null;
        }
        User user = (User) fromCache(convertItem.getId());
        return user != null ? user.getCaption() : null;
    }
}
  1. 需要转换的数据缓存

该实现依赖缓存,需要优先对需要转换的数据进行缓存,因此示例中添加了缓存示例

public void init(){
    Cache cache = cacheManager.getCache(UserConvert.USER_CACHE);
    if( cache != null ){
        cache.put("u1", new User("u1","Tom"));
    }
}
  1. 实体定义

实体中需要通过实现接口UserConvert,这样对多个数据项转换时可以继续扩展

public class Order implements UserConvert {

    private String id;

    private String name;

    private LocalDateTime createTime = LocalDateTime.now();

    /**
     * 创建用户
     */
    private String creator;

    @Override
    public String userId() {
        return creator;
    }
}
  1. 实现效果

可以看到,在输出json中,多了一列userConvert,也就是接口中定义的get*方法

{
    "id": "1",
    "name": "测试订单",
    "createTime": "2024-05-08T21:55:51.5747507",
    "creator": "u1",
    "userConvert": "Tom"
}

实现原理

上面说的,主要实现基于缓存,在web查询结果进行json序列化时,依赖于jackson的扩展,对输出结果匹配的类型进行转换。

@EnableCaching
@Configuration
public class JacksonCustomConfiguration{

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
        return jacksonObjectMapperBuilder -> configureMapperBuilder(jacksonObjectMapperBuilder);
    }

    private void configureMapperBuilder(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
        jackson2ObjectMapperBuilder.serializers(convertSerializer());
    }

    @Bean
    public ItemConvertSerializer convertSerializer(){
        return new ItemConvertSerializer(ConvertItem.class);
    }
}
  1. 在配置文件中基于Jackson2ObjectMapperBuilderCustomizer对jackson进行扩展
  2. 定义ItemConvertSerializerConvertItem类型的属性进行处理,该类主要继承于StdSerializer
  3. ItemConvertSerializer中基于ConvertItem的name属性来匹配对应的缓存并进行转换
  4. 注意开启spring缓存*@EnableCaching*
  5. 最后基于spring特性,定义*/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports*来实现自动注入配置

  • ConvertItem示例
@Getter
@Setter
public class ConvertItem {

    private String id;

    private String text;

    private String name;

    public ConvertItem() {

    }

    public ConvertItem(String id, String name) {
        this.id = id;
        this.name = name;
    }
}
  • ItemConvertAdapter扩展适配器,主要于ConvertItem搭配扩展
public interface ItemConvertAdapter {

    /**
     * @param convertItem
     * @return
     */
    boolean support(ConvertItem convertItem);

    /**
     *
     * @param convertItem
     * @return
     */
    String convert(ConvertItem convertItem);

}
  • ItemConvertSerializer示例
public class ItemConvertSerializer extends StdSerializer<ConvertItem> implements ApplicationContextAware {

    private List<ItemConvertAdapter> itemConvertAdapters;

    public ItemConvertSerializer(Class<ConvertItem> t) {
        super(t);
    }

    @Override
    public void serialize(ConvertItem value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        String text = "";
        if(!CollectionUtils.isEmpty(itemConvertAdapters)){
            for (ItemConvertAdapter itemConvertAdapter : itemConvertAdapters) {
                if( itemConvertAdapter.support(value) ){
                    text = itemConvertAdapter.convert(value);
                    break;
                }
            }
        }
        gen.writeString(text);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, ItemConvertAdapter> itemConvertAdapterMap
                = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ItemConvertAdapter.class, true, false);
        if( !itemConvertAdapterMap.isEmpty() ){
            itemConvertAdapters = new ArrayList<>(itemConvertAdapterMap.values());
            itemConvertAdapters.sort(OrderComparator.INSTANCE);
        }
    }
}

优缺点

  • 使用了jackson序列化的扩展,如果使用其他序列化工具,需要单独支持
  • 依赖于数据缓存,一般针对通用数据才有数据转换的需要,比如用户、部门数据等,一般这些数据更适合缓存

源代码

https://hkvr7m0roy.feishu.cn/docx/S6PddcGLkovTJOxxaZfcXuWbnuV?from=from_copylink

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值