All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.stupdit1t.jackson.expand.serializer.ExpandSerializer Maven / Gradle / Ivy

There is a newer version: 1.1.1
Show newest version
package com.github.stupdit1t.jackson.expand.serializer;

import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.thread.lock.LockUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.github.stupdit1t.jackson.expand.annotation.Expand;
import com.github.stupdit1t.jackson.expand.cache.CovertCache;
import com.github.stupdit1t.jackson.expand.config.JacksonExpandProperties;
import com.github.stupdit1t.jackson.expand.domain.ExpandStrategy;
import com.github.stupdit1t.jackson.expand.domain.SerializerParam;
import com.github.stupdit1t.jackson.expand.domain.SpringUtil;
import com.github.stupdit1t.jackson.expand.handler.params.ParamsHandler;
import com.github.stupdit1t.jackson.expand.handler.rsp.ResponseHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.locks.StampedLock;
import java.util.stream.Collectors;

public class ExpandSerializer extends JsonSerializer implements ContextualSerializer {

    private static final Logger LOG = LoggerFactory.getLogger(ExpandSerializer.class);

    /**
     * 成功数据
     */
    public static final String OK = "OK";

    /**
     * 失败数据
     */
    public static final String FAIL = "FAIL";

    /**
     * 缓存
     */
    private static CovertCache cache;

    /**
     * 配置
     */
    private static JacksonExpandProperties jacksonExpandProperties;

    /**
     * 本地锁缓存,防止同时查询
     */
    private static final TimedCache lockCache = new TimedCache<>(5000);

    /**
     * 远程调用服务原始calss
     */
    private String loadServiceSourceClassName;

    /**
     * 远程调用服务
     */
    private Object loadService;

    /**
     * 方法
     */
    private String method;

    /**
     * 注解参数处理
     */
    private SerializerParam params;

    /**
     * 返回结果处理类
     */
    private ParamsHandler paramsHandler;

    /**
     * 返回结果处理类
     */
    private ResponseHandler responseHandler;

    /**
     * 远程服务前缀
     */
    private String prefix;

    public ExpandSerializer() {
        super();
        if (cache == null) {
            synchronized (ExpandSerializer.class) {
                cache = SpringUtil.getBean(CovertCache.class);
                jacksonExpandProperties = SpringUtil.getBean(JacksonExpandProperties.class);
            }
        }
    }

    public ExpandSerializer(String loadService, String method, SerializerParam params, ParamsHandler paramsHandler, ResponseHandler otherResponseHandler) {
        this();
        this.loadServiceSourceClassName = loadService;
        this.loadService = SpringUtil.getBean(loadService);
        this.method = method;
        this.params = params;
        this.responseHandler = otherResponseHandler;
        this.paramsHandler = paramsHandler;
        prefix = loadServiceSourceClassName;
    }

    @Override
    public void serialize(Object bindData, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        String writeFieldPath = getFieldPath(gen.getOutputContext());
        // 是否展开
        boolean expand;
        // 动态展开开启,判断是否展开
        boolean dynamicExpand = jacksonExpandProperties.isDynamicExpand();
        if (dynamicExpand) {
            Set needExpandField = getParam(jacksonExpandProperties.getDynamicExpandParameterName());
            // 如果代码里设置不展开,动态展开也不生效
            expand = needExpandField.contains(writeFieldPath) && params.isOpen();
        } else {
            expand = params.isOpen();
        }
        if (!expand) {
            gen.writeObject(bindData);
            return;
        }

        // 判断要写入的字段
        String writeField = gen.getOutputContext().getCurrentName();
        if (jacksonExpandProperties.getExpandStrategy() == ExpandStrategy.COVER) {
            writeField = gen.getOutputContext().getCurrentName();
        } else if (jacksonExpandProperties.getExpandStrategy() == ExpandStrategy.COPY) {
            writeField = String.format(jacksonExpandProperties.getCopyStrategyFormat(), gen.getOutputContext().getCurrentName());
        }

        // 自定义要写入的优先级最高
        if (StrUtil.isNotBlank(params.getWriteField())) {
            writeField = params.getWriteField();
        }

        // 设置理论上的响应类型,要不要使用取决于 ResponseHandler 要不要处理,比如只能写入数据对象存在的对象,默认是忽略存不存在
        Class writeClass = null;
        if (params.getWriteField() != null && StrUtil.isNotBlank(params.getWriteField())) {
            Field field = ReflectUtil.getField(gen.getCurrentValue().getClass(), params.getWriteField());
            if (field != null) {
                writeClass = field.getType();
            }
        }

        // 关闭不存在字段扩展,被写入的字段类型找不到,不扩展
        if(!jacksonExpandProperties.isCanExpandToNotExistField() && writeClass == null){
            gen.writeObject(bindData);
            return;
        }


        // 翻译为非当前字段,先写入当前字段值再翻译
        boolean currField = gen.getOutputContext().getCurrentName().equals(writeField);
        if (!currField) {
            gen.writeObject(bindData);
            gen.writeFieldName(writeField);
        }
        if (bindData == null || loadService == null) {
            gen.writeObject(bindData);
            return;
        }

        // 获取缓存KEY
        Object[] args = params.getRemoteParams();
        int argsLength = args == null ? 0 : args.length;
        String cacheKey = jacksonExpandProperties.getCachePrefix() + prefix + ":" + method + ":%s:" + toMd5Hex(paramsHandler.getCacheKey(bindData, args));
        Object result = getCacheInfo(cacheKey);
        if (result != null) {
            LOG.info("{} Expand cache 命中: {}", prefix, result);
            gen.writeObject(result);
            return;
        }

        StampedLock lock = lockCache.get(cacheKey, true, LockUtil::createStampLock);
        // 写锁避免同一业务ID重复查询
        long stamp = lock.writeLock();
        try {
            // 多参数组装
            Object[] objectParams = new Object[argsLength + 1];
            objectParams[0] = paramsHandler.handleVal(bindData);
            for (int i = 0; i < argsLength; i++) {
                objectParams[i + 1] = args[i];
            }

            // 请求翻译结果
            Object loadResult = ReflectUtil.invoke(loadService, method, objectParams);

            if (loadResult != null) {
                result = this.responseHandler.handle(this.loadServiceSourceClassName, method, loadResult, writeClass, objectParams);
                cache.put(String.format(cacheKey, OK), result, params.getCacheTime());
            } else {
                LOG.error("【{}】 Expand失败,未找到:{}", prefix, bindData);
                cache.put(String.format(cacheKey, FAIL), bindData, params.getCacheTime());
                result = bindData;
            }

        } catch (Exception e) {
            LOG.error("【{}】 Expand异常:{}", prefix, e);
            cache.put(String.format(cacheKey, FAIL), bindData, params.getCacheTime());
            result = bindData;
        } finally {
            lock.unlockWrite(stamp);
        }
        gen.writeObject(result);
    }

    /**
     * 获取当前字段的path路径
     *
     * @param outputContext
     * @return
     */
    private String getFieldPath(JsonStreamContext outputContext) {
        List path = new ArrayList<>(2);
        String currentName = outputContext.getCurrentName();
        while (currentName != null) {
            path.add(currentName);
            outputContext = outputContext.getParent();
            currentName = outputContext.getCurrentName();
        }
        Collections.reverse(path);
        return String.join(".", path);
    }

    /**
     * 获取厍信息
     *
     * @param cacheKey 缓存的KEY
     * @return
     */
    private Object getCacheInfo(String cacheKey) {
        Object result = cache.get(String.format(cacheKey, OK));
        if (result == null) {
            result = cache.get(String.format(cacheKey, FAIL));
        }
        return result;
    }

    @Override
    public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        if (property != null) {
            Expand load = property.getAnnotation(Expand.class);
            if (load == null) {
                throw new RuntimeException("未注解相关 @Expand 注解");
            }
            String bean = load.bean();
            Class paramsHandlerClass = load.paramsHandler();
            Class responseHandlerClass = load.responseHandler();
            String method = load.method();
            try {
                ParamsHandler paramsHandler = paramsHandlerClass.getDeclaredConstructor().newInstance();
                ResponseHandler responseHandler = responseHandlerClass.getDeclaredConstructor().newInstance();
                int cacheTime = load.cacheTime();
                // 额外参数处理
                SerializerParam params = paramsHandler.handleAnnotation(property);
                if (params.getCacheTime() == null && cacheTime != -1) {
                    params.setCacheTime(cacheTime);
                }
                if (params.isOpen() == null) {
                    params.setExpand(load.expand());
                }
                return new ExpandSerializer(bean, method, params, paramsHandler, responseHandler);
            } catch (Exception e) {
                LOG.error("@Expand error: ", e);
            }
        }
        return prov.findNullValueSerializer(null);
    }

    /**
     * 清楚目前的本地缓存
     */
    public static void clearCache() {
        if (cache != null) {
            cache.clear();
        }
    }

    /**
     * MD5
     *
     * @param input
     * @return
     */
    private static String toMd5Hex(String input) {
        try {
            // 创建 MessageDigest 对象
            MessageDigest md = MessageDigest.getInstance("MD5");

            // 计算哈希值并转换为 BigInteger 对象
            byte[] hash = md.digest(input.getBytes());
            BigInteger bi = new BigInteger(1, hash);

            // 将哈希值转换为 16 进制字符串
            return String.format("%0" + (hash.length << 1) + "x", bi);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取展开参数
     *
     * @param key
     * @return
     */
    public static Set getParam(String key) {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return Collections.emptySet();
        }
        ServletRequest request = ((ServletRequestAttributes) attributes).getRequest();
        String[] parameterValues = request.getParameterValues(key);
        if (parameterValues == null) {
            return Collections.emptySet();
        }
        return Arrays.stream(parameterValues).flatMap(o -> Arrays.stream(o.split(","))).collect(Collectors.toSet());
    }
}