涉及知识点:


ExpressionParser


PropertyPlaceholderHelper


核心类:

package org.demo.property;

import cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.Method;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.PropertyPlaceholderHelper;

import javax.annotation.Resource;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Component
public class DynamicUrl {

    private static final String VAR_PREFIX = "${";
    private static final String VAR_SUFFIX = "}";
    private static final String POINT = ".";
    private static final String EQUAL = "=";
    private static final String WELL = "#";
    private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\{([^}]*)}");

    // 允许为空的参数
    @Value("${valueMayBeEmptyParams:a,b,c}")
    private String valueMayBeEmptyParams;

    @Resource
    private ApplicationContext applicationContext;

    private final PropertyPlaceholderHelper placeholderHelper = new PropertyPlaceholderHelper(VAR_PREFIX, VAR_SUFFIX);
    private final ExpressionParser parser = new SpelExpressionParser(SpringElUtil.CONFIG);


    /**
     * 处理单个广告商事件上报
     *
     * @param advertisersConfig 广告配置
     * @param reportConfig      上报配置
     * @param extendInfo        扩展信息
     * @param msgJson           消息体
     */
    public void dealOneAdvertiserReport(TAdvertisersConfig advertisersConfig,
                                         TAdvertisersReportConfig reportConfig,
                                         Map extendInfo, JSONObject msgJson) {
        Map<String, Object> delayHeaders = new HashMap<>(2);

        String custNo = msgJson.getString("custNo");
        String advertiserName = advertisersConfig.getAdvertisersName();
        int eventType = reportConfig.getEventType();
        String eventDesc = EventType.transfer(eventType).getTypeDesc();
        String reportUrl = reportConfig.getReportUrl();

        msgJson.put("advertiserId", advertisersConfig.getId());

        reportUrl = replaceReportUrl(reportUrl, eventType, extendInfo, msgJson);
        log.info("--------------------------------------------------");
        log.info("开始执行用户[{}]事件[{}]上报到广告商[{}]", custNo, eventDesc, advertiserName);
        try {
            // 1、构建请求头
            String requestHeader = reportConfig.getRequestHeader();
            Map<String, String> headers = new HashMap<>(4);
            if (StringUtils.isNotEmpty(requestHeader)) {
                JSONObject requestHeaderJson = JSON.parseObject(requestHeader);
                // 移除需要在解析完body后执行解析的头部字段 延后执行
                // 如果以后有更多字段 可以使用map
                requestHeaderJson.entrySet().removeIf(header -> {
                    if (header.getValue().toString().contains("${requestBody}")) {
                        delayHeaders.put(header.getKey(), header.getValue());
                        return true;
                    }
                    return false;
                });
                headers = buildHeaders(requestHeaderJson, eventType, extendInfo, msgJson);
            }

            String requestConfig = reportConfig.getRequestConfig();
            String responseStr;
            Object object = JSON.parse(requestConfig);
            HttpRequest httpRequest = new HttpRequest(reportUrl).addHeaders(headers).timeout(10000);
            if (object instanceof JSONObject) {
                // 2、构建上报参数
                JSONObject requestJson = (JSONObject) object;
                Map<String, Object> form = parseParams(requestJson, eventType, extendInfo, msgJson);
                log.info("执行用户[{}]事件[{}]上报到广告商[{}],入参:{}", custNo, eventDesc, advertiserName, form);

                // 写入${requestBody}
                if (!delayHeaders.isEmpty()) {
                    msgJson.put("requestBody", new JSONObject(form).toJSONString());
                    httpRequest.addHeaders(buildHeaders(new JSONObject(delayHeaders), eventType, extendInfo, msgJson));
                }

                // 3、执行上报
                if (RequestType.GET.getTypeCode() == reportConfig.getRequestType()) {
                    responseStr = httpRequest.method(Method.GET).form(form).execute().body();
                } else {
                    String header = httpRequest.header("Content-Type");
                    if (header != null && header.contains("application/json")) {
                        responseStr = httpRequest.method(Method.POST).body(JSON.toJSONString(form)).execute().body();
                    } else {
                        responseStr = httpRequest.method(Method.POST).form(form).execute().body();
                    }
                }
            } else if (object instanceof JSONArray) {
                JSONArray jsonArray = (JSONArray) object;
                List<Map<String, Object>> form = new ArrayList<>(jsonArray.size());
                jsonArray.forEach(obj -> form.add(parseParams((JSONObject) obj, eventType, extendInfo, msgJson)));

                // 写入${requestBody}
                if (!delayHeaders.isEmpty()) {
                    msgJson.put("requestBody", new JSONArray(Collections.singletonList(form)).toJSONString());
                    httpRequest.addHeaders(buildHeaders(new JSONObject(delayHeaders), eventType, extendInfo, msgJson));
                }

                // 3、执行上报
                if (RequestType.GET.getTypeCode() == reportConfig.getRequestType()) {
                    responseStr = httpRequest.method(Method.GET).body(JSON.toJSONString(form)).execute().body();
                } else {
                    responseStr = httpRequest.method(Method.POST).body(JSON.toJSONString(form)).execute().body();
                }
            } else {
                log.error("无法解析请求参数配置:{}", reportConfig);
                return;
            }

            // 4、解析上报结果
            String responseConfig = reportConfig.getResponseConfig();
            if (checkReportResult(responseStr, responseConfig)) {
                // 5、保存上报结果
                log.info("上报用户[{}]事件[{}]到广告商[{}]成功", custNo, eventDesc, advertiserName);
            } else {
                log.error("上报用户[{}]事件[{}]到广告商[{}]失败,返回:[{}]", custNo, eventDesc, advertiserName, responseStr);
                // 可以写调用失败的逻辑
            }
        } catch (Exception e) {
            log.warn("上报用户[{}]事件[{}]到广告商[{}]异常:[{}]", custNo, eventDesc, advertiserName, e.getMessage());
        }
    }


    private String replaceReportUrl(String content, int eventType, Map extendInfo, JSONObject msgJson) {
        return placeholderHelper.replacePlaceholders(content, placeholderName -> {
            VariableParser parser = VariableParseSelector.selectParser(placeholderName);
            if (parser == null) {
                throw new RuntimeException(placeholderName + "找不到对应的变量解析器。");
            }
            Object variable = parser.parseVariable(placeholderName, eventType, extendInfo, msgJson);
            if (variable == null || StringUtils.isEmpty(variable.toString())) {
                throw new RuntimeException(placeholderName + "解析变量值失败。");
            }
            log.info("解析参数[{}]对应的表达式[{}]值为[{}].", placeholderName, "${" + placeholderName + "}", variable);
            return String.valueOf(variable);
        });
    }


    /**
     * 解析并构建头部
     */
    private Map<String, String> buildHeaders(JSONObject requestHeaderJson, int eventType, Map extendInfo,
                                             JSONObject msgJson) {
        Map<String, String> headers = new HashMap<>(4);
        Map<String, Object> headerParamMap = parseParams(requestHeaderJson, eventType, extendInfo, msgJson);
        if (MapUtil.isNotEmpty(headerParamMap)) {
            headerParamMap.forEach((key, value) -> {
                if (value != null && StringUtils.isNoneBlank(value.toString())) {
                    headers.put(key, value.toString());
                }
            });
        }
        return headers;
    }

    /**
     * 解析请求参数,把Json配置转换成Map
     *
     * @param config     入参配置
     * @param eventType  事件
     * @param extendInfo 扩展信息
     * @param msgJson    消息体
     * @return 参数
     */
    private Map<String, Object> parseParams(JSONObject config, int eventType, Map extendInfo,
                                            JSONObject msgJson) {
        Map<String, Object> paramMap = new HashMap<>(config.size());
        EvaluationContext ctx;
        try {
            ctx = SpringElUtil.createContext();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("创建SpringEL表达式上下文异常。");
        }

        config.forEach((paramKey, paramValueExpress) -> {
            Object key;
            // 字段名也支持表达式 但暂时不打算支持任意位置的,如果以后支持字段名任意位置的表达式 把条件去掉
            if (paramKey.charAt(0) == '$') {
                // 字段名也支持表达式
                key = parseValue(ctx, eventType, extendInfo, msgJson, paramKey);
            } else {
                key = paramKey;
            }

            if (paramValueExpress instanceof JSONObject) {
                Map<String, Object> paramValue = parseParams((JSONObject) paramValueExpress, eventType, extendInfo, msgJson);
                paramMap.put(String.valueOf(key), paramValue);
            } else if (paramValueExpress instanceof JSONArray) {
                List<Map<String, Object>> paramValueList = new ArrayList<>(((JSONArray) paramValueExpress).size());
                ((JSONArray) paramValueExpress).forEach(
                        subParamValueExpress -> paramValueList.add(parseParams((JSONObject) subParamValueExpress,
                                eventType, extendInfo, msgJson)));
                paramMap.put(String.valueOf(key), paramValueList);
            } else {
                try {
                    Object paramValue = parseValue(ctx, eventType, extendInfo, msgJson, paramValueExpress);
                    paramMap.put(String.valueOf(key), paramValue);
                    log.info("解析参数[{}]对应的表达式[{}]值为[{}].", key, paramValueExpress, paramValue);
                } catch (Exception e) {
                    log.info("解析参数[{}]对应的表达式[{}]跳过.", key, paramValueExpress);
                }
            }
        });
        return paramMap;
    }


    /**
     * 解析参数值的表达式
     *
     * @param eventType    事件类型
     * @param extendInfo   扩展信息
     * @param msgJson      消息体
     * @param valueExpress 表达式
     * @return 参数值
     */
    private Object parseValue(EvaluationContext ctx, int eventType, Map extendInfo, JSONObject msgJson, Object valueExpress) {
        String varExpress = String.valueOf(valueExpress);
        if (varExpress.contains(VAR_PREFIX) && varExpress.contains(VAR_SUFFIX)) {
            List<String> varList = this.getVariableNames(varExpress);
            for (String varName : varList) {
                // ctx没有对应的varName,则需要执行 ctx.setVariable,以便在下面的“动态获取参数值”可以拿到对应的值
                if (ctx.lookupVariable(varName) == null) {
                    VariableParser variableParser = VariableParseSelector.selectParser(varName);
                    if (variableParser == null) {
                        throw new RuntimeException(varName + "找不到对应的变量解析器。");
                    }
                    Object varValue = variableParser.parseVariable(varName, eventType, extendInfo, msgJson);
                    if (varName.indexOf(valueMayBeEmptyParams) != -1
                            && (varValue == null
                            || StringUtils.isBlank(varValue.toString()))) {
                        throw new RuntimeException(varName + "解析变量值失败。");
                    }
                    ctx.setVariable(varName, varValue);
                }
                varExpress = varExpress.replaceAll("\\$\\{" + varName + "}", "\\#" + varName);   // 效果如将 "#aes(${oppoPhoneIdValue},'XGAXicVG5GMBsx5bueOe4w==')" 替换成 #aes(#oppoPhoneIdValue,'XGAXicVG5GMBsx5bueOe4w==')
            }
        }
        if (varExpress.contains(WELL)) {
            // 动态获取的参数值,如#name,包括ctx里面设置好的一些函数
            return parser.parseExpression(varExpress).getValue(ctx);
        } else {
            // 写死的参数值
            return valueExpress;
        }
    }


    /**
     * 检查上报结果是否正确
     *
     * @param responseStr    响应体
     * @param responseConfig 响应配置
     * @return 是否成功
     */
    private boolean checkReportResult(String responseStr, String responseConfig) {
        try {
            if (responseConfig.contains(EQUAL)) {
                String[] configs = StringUtils.split(responseConfig, EQUAL);
                String resultKey = configs[0];
                String expectValue = configs[1];
                String[] keys = StringUtils.split(resultKey, POINT);
                String resultValue = getResultValue(JSON.parseObject(responseStr), keys, 0);
                return expectValue.equalsIgnoreCase(resultValue);
            } else {
                return responseConfig.equalsIgnoreCase(responseStr);
            }
        } catch (Exception e) {
            log.error("根据出参配置[{}]解析响应结果[{}]产生异常[{}]。", responseConfig, responseStr, e.getMessage(), e);
            return false;
        }
    }


    private String getResultValue(JSONObject resultJson, String[] keys, int index) {
        if (index >= keys.length) {
            log.error("根据[{}]无法获取返回值", Arrays.toString(keys));
            return "";
        }
        Object value = resultJson.get(keys[index]);
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "";
        }
        if (value instanceof JSONObject) {
            resultJson = (JSONObject) value;
            return getResultValue(resultJson, keys, index + 1);
        } else {
            return value.toString();
        }
    }

    /**
     * 获取表达式中${}中的值
     *
     * @param content 变量表达式
     * @return 变量列表
     */
    private List<String> getVariableNames(String content) {
        Matcher matcher = VAR_PATTERN.matcher(content);
        List<String> variableList = new ArrayList<>();
        while (matcher.find()) {
            variableList.add(matcher.group(1));
        }
        return variableList;
    }


}

参数解析器:

/**
 * Description: 变量解析接口
 *
 */
public interface VariableParser {

    /**
     * 变量配置解析
     *
     * @param variableName 变量名
     * @param eventType    事件类型
     * @param extendInfo   用户扩展信息
     * @param msgJson      消息体
     * @return 变量
     */
    Object parseVariable(String variableName, int eventType, Map extendInfo, JSONObject msgJson);
}


package org.demo.property;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class VariableParseSelector {

    private static final Map<String, VariableParser> PARSER_MAP = new ConcurrentHashMap<>();

    public static void registerParser(String variableName, VariableParser variableParser) {
        PARSER_MAP.put(variableName, variableParser);
    }

    public static VariableParser selectParser(String variableName) {
        return PARSER_MAP.get(variableName);
    }
}


package org.demo.property;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * Description:
 */
@Component
public class OppoExposeParser implements VariableParser, InitializingBean {


    @Override
    public Object parseVariable(String variableName, int eventType, Map extendInfo, JSONObject msgJson) {
        switch (variableName) {
            case "oppoPhoneIdName": {
                if (true) {
                    return "oppoPhoneIdName";
                }
                return "companyName";
            }
            case "oppoPhoneIdValue": {
                return msgJson.get("oppoPhoneIdValue");
            }
            case "oppoExposeTimestamp": {
                return msgJson.get("oppoExposeTimestamp");
            }
            case "oppoAdId": {
                return msgJson.get("oppoAdId");
            }
            case "oppoType": {
                return msgJson.get("oppoType");
            }
            default:
                return "";
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        VariableParseSelector.registerParser("oppoPhoneIdName", this);
        VariableParseSelector.registerParser("oppoPhoneIdValue", this);
        VariableParseSelector.registerParser("oppoExposeTimestamp", this);
        VariableParseSelector.registerParser("oppoAdId", this);
        VariableParseSelector.registerParser("oppoType", this);
    }
}


SpringEl工具类:


package org.demo.property;

import cn.hutool.crypto.digest.MD5;
import lombok.extern.slf4j.Slf4j;
import org.demo.property.function.*;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;

import java.util.HashMap;
import java.util.Map;

/**
 * Description: SpringEl获取Method
 */
@Slf4j
public class SpringElUtil {

    /**
     * IMMEDIATE - 在即时模式下,尽快编译表达式。这通常是在第一次解释评估之后。
     * 如果编译的表达式失败(通常是由于类型改变,如上所述),则表达式评估的调用者将收到异常。
     */
    public static final SpelParserConfiguration CONFIG = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
            SpringElUtil.class.getClassLoader());

    /**
     * 对公共method的进行缓存 防止每次都getDeclaredMethod
     */
    private static final Map<String, Object> METHOD_CACHE = new HashMap<String, Object>() {{
        try {
            put("ceil", MathFunction.class.getDeclaredMethod("ceil", double.class));
            put("md5", MD5.class.getDeclaredMethod("digestHex16", String.class));
            put("hmac", HmacSha1Function.class.getDeclaredMethod("encrypt", String.class, String.class));
            put("aes", AesFunction.class.getDeclaredMethod("strEncodeBase64", String.class, String.class));
            put("currentTimeMillis", DateFunction.class.getDeclaredMethod("currentTimeMillis"));
            put("currentTimeSeconds", DateFunction.class.getDeclaredMethod("currentTimeSeconds"));
            put("currentStringDate", DateFunction.class.getDeclaredMethod("currentStringDate"));
        } catch (NoSuchMethodException e) {
            log.error("SpringEl获取方法失败", e);
        }
    }};

    public static EvaluationContext createContext() throws NoSuchMethodException {
        //这里因为安全 设置了只读模式,如需其他模式可以自行更改
        EvaluationContext ctx = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        METHOD_CACHE.forEach(ctx::setVariable);
        return ctx;
    }


    public static void main(String[] args) throws NoSuchMethodException {
        ExpressionParser parser = new SpelExpressionParser(SpringElUtil.CONFIG);
        EvaluationContext ctx;
        ctx = SpringElUtil.createContext();
 // 主要分两步,1,ctx.setVariable;2,parser.parseExpression("#aes(#oppoPhoneIdValue,'XGAXicVG5GMBsx5bueOe4w==')").getValue(ctx),
//整个动态执行http的参数获取都是基于这两步
        ctx.setVariable("oppoPhoneIdValue", 123);
        Object ob = parser.parseExpression("#aes(#oppoPhoneIdValue,'XGAXicVG5GMBsx5bueOe4w==')").getValue(ctx);
        System.out.println(ob);
    }
}
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;

/**
 * AES加密
 */
public class AesFunction {

    private static final String AES_ECB_PKCS5PADDING = "AES/ECB/PKCS5Padding";

    public static String strEncodeBase64(String data, String base64Key) {
        final Key dataKey = new SecretKeySpec(Base64.getDecoder().decode(base64Key), "AES");
        try {
            Cipher cipher = Cipher.getInstance(AES_ECB_PKCS5PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, dataKey);
            byte[] encryptData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptData).replaceAll("\r", "").replaceAll("\n", "");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}


/**
 * Description: 时间函数
 */
public class DateFunction {

    /**
     * 返回当前时间,毫秒
     *
     * @return 毫秒
     */
    public static long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    /**
     * 返回当前时间,秒
     *
     * @return 秒
     */
    public static long currentTimeSeconds() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * 返回当前日期,格式:yyyyMMdd
     *
     * @return 当前日期
     */
    public static String currentStringDate() {
        return DateFormatUtils.format(new Date(), "yyyyMMdd");
    }
}


import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

/**
 * Description: HmacSHA1 加密
 */
public class HmacSha1Function {

    public static String encrypt(String encryptText, String encryptKey) throws Exception {
        byte[] data = encryptKey.getBytes(StandardCharsets.UTF_8);
        SecretKey secretKey = new SecretKeySpec(data, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(secretKey);
        byte[] text = encryptText.getBytes(StandardCharsets.UTF_8);
        byte[] str = mac.doFinal(text);
        StringBuilder hexString = new StringBuilder();
        for (byte b : str) {
            String shaHex = Integer.toHexString(b & 0xFF);
            if (shaHex.length() < 2) {
                hexString.append(0);
            }
            hexString.append(shaHex);
        }
        return hexString.toString();
    }
}



public class MathFunction {
    public static int ceil(double x) {
        return (int) Math.ceil(x);
    }
}


import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;

/**
 * AES加密
 */
public class AesFunction {

    private static final String AES_ECB_PKCS5PADDING = "AES/ECB/PKCS5Padding";

    public static String strEncodeBase64(String data, String base64Key) {
        final Key dataKey = new SecretKeySpec(Base64.getDecoder().decode(base64Key), "AES");
        try {
            Cipher cipher = Cipher.getInstance(AES_ECB_PKCS5PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, dataKey);
            byte[] encryptData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptData).replaceAll("\r", "").replaceAll("\n", "");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}


/**
 * Description: 时间函数
 */
public class DateFunction {

    /**
     * 返回当前时间,毫秒
     *
     * @return 毫秒
     */
    public static long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    /**
     * 返回当前时间,秒
     *
     * @return 秒
     */
    public static long currentTimeSeconds() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * 返回当前日期,格式:yyyyMMdd
     *
     * @return 当前日期
     */
    public static String currentStringDate() {
        return DateFormatUtils.format(new Date(), "yyyyMMdd");
    }
}

其他:

package org.demo.property;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum EventType {

    /**
     * 注册
     */
    REG(1, "注册"),

    /**
     * 登录
     */
    LOGIN(2, "激活"),

    /**
     * 完件
     */
    COMPLETE(3, "完件"),

    /**
     * 授信
     */
    CREDIT(4, "授信成功"),

    /**
     * 借款
     */
    LOAN(5, "借款成功"),


    /**
     * 省钱卡
     */
    MONEY_SAVING_CARD(6, "省钱卡"),

    /**
     * 未知
     */
    UNKNOWN(-99, "未知");

    /**
     * 类型编码
     */
    private final int typeCode;

    /**
     * 事件描述
     */
    private String typeDesc;

    public static EventType transfer(int typeCode) {
        for (EventType eventType : EventType.values()) {
            if (typeCode == eventType.getTypeCode()) {
                return eventType;
            }
        }
        return UNKNOWN;
    }
}


import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * Description:请求类型
 */
@AllArgsConstructor
@Getter
public enum RequestType {

    /**
     * Get请求
     */
    GET(1),

    /**
     * Post请求
     */
    POST(2);

    private int typeCode;
}




package org.demo.property;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class TAdvertisersConfig implements Serializable {
    /**
     * ID
     */
    private String id;

    /**
     * ID哈希码
     */
    private String idHash;

    /**
     * 广告商名称
     */
    private String advertisersName;

    /**
     * 检查方式 1:根据extend_info 2:根据qd
     */
    private Integer checkType;

    /**
     * 渠道编号
     */
    private String channelCode;

    /**
     * 扩展类型
     */
    private String extendType;

    /**
     * 注册渠道(qd)
     */
    private String regChannel;

    /**
     * 活动状态 1:开启 2:关闭
     */
    private Integer liveStatus;

    /**
     * 备注
     */
    private String remark;

    /**
     * 创建者
     */
    private String createdBy;

    /**
     * 创建时间
     */
    private Date createDatetime;

    /**
     * 修改者
     */
    private String updatedBy;

    /**
     * 修改时间
     */
    private Date updateDatetime;

    private static final long serialVersionUID = 1L;

}



package org.demo.property;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;


@Data
public class TAdvertisersReportConfig implements Serializable {
    /**
     * 主键ID
     */
    private String id;

    /**
     * 广告商ID
     */
    private String advertisersId;

    /**
     * 上报的事件
     */
    private Integer eventType;

    /**
     * 上报URL
     */
    private String reportUrl;

    /**
     * 请求类型 1、Get 2、Post
     */
    private Integer requestType;

    /**
     * 请求头配置
     */
    private String requestHeader;

    /**
     * 请求配置
     */
    private String requestConfig;

    /**
     * 响应配置
     */
    private String responseConfig;

    /**
     * 状态 1:开启 2、关闭
     */
    private Integer liveStatus;

    /**
     * 备注
     */
    private String remark;

    /**
     * 创建人
     */
    private String createdBy;

    /**
     * 创建时间
     */
    private Date createDatetime;

    /**
     * 修改人
     */
    private String updatedBy;

    /**
     * 修改时间
     */
    private Date updateDatetime;

    /**
     * 个性化上报数据
     */
    private String reportSpecData;

}

单测:

package org.example;

import com.alibaba.fastjson.JSONObject;
import org.demo.property.DynamicUrl;
import org.demo.property.TAdvertisersConfig;
import org.demo.property.TAdvertisersReportConfig;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DynamicUrlTest extends BaseTest {

    @Autowired
    private DynamicUrl dynamicUrl;

    @Test
    public void test() {
        TAdvertisersConfig advertisersConfig = new TAdvertisersConfig();
        advertisersConfig.setAdvertisersName("oppo");
        TAdvertisersReportConfig reportConfig = new TAdvertisersReportConfig();
        reportConfig.setReportUrl("https://api.ads.heytapmobi.com/api/uploadActiveData");
// 上面提到的整体逻辑分两步,${}修饰的参数就是第一步,即动态的把参数对应的值放入表达式容器里(ctx.setVariable),这里涉及的参数解析器即从spring的bean去获取参数对应的实际值
// 第二步就是把${}修饰的参数转化(没有$修饰的不需要转)成#修饰从表达式容器里拿到具体的值
// 如(parser.parseExpression("#aes(#oppoPhoneIdValue,'XGAXicVG5GMBsx5bueOe4w==')").getValue(ctx);)
        reportConfig.setRequestConfig("{\n" +
                "\t\"${oppoPhoneIdName}\": \"#aes(${oppoPhoneIdValue},'XGAXicVG5GMBsx5bueOe4w==')\",\n" +
                "\t\"timestamp \": \"${oppoExposeTimestamp}\",\n" +
                "\t\"pkg\": \"com.credit\",\n" +
                "\t\"dataType\":2,\n" +
                "\t\"channel\":1,\n" +
                "\t\"type\":\"${oppoType}\",\n" +
                "\t\"ascribeType\":1,\n" +
                "\t\"adId\":\"${oppoAdId}\"\n" +
                "}");
        reportConfig.setRequestHeader("{\n" +
                "\t\"Content-Type\": \"application/json;charset=UTF-8\",\n" +
                "\t\"signature\": \"#md5(${requestBody}+${oppoExposeTimestamp}+'e0u6fnlag06lc3pl')\",\n" +
                "\t\"timestamp\": \"${oppoExposeTimestamp}\"\n" +
                "}");
        reportConfig.setRequestType(2);
        reportConfig.setResponseConfig("ret=0");
        reportConfig.setEventType(1);


        Map extendInfo = new HashMap();
        JSONObject msgJson = new JSONObject();
        msgJson.put("custNo", "123");
        msgJson.put("oppoPhoneIdName", "123");
        msgJson.put("oppoPhoneIdValue", "123");
        msgJson.put("oppoExposeTimestamp", new Date());
        msgJson.put("oppoAdId", "123");
        msgJson.put("oppoType", "123");

        dynamicUrl.dealOneAdvertiserReport(advertisersConfig, reportConfig, extendInfo, msgJson);


    }


}