天天看点

fly coding Api请求支持通配符“{}”调用接口

作者:三牛爱编程

此前,已经完善了Api的功能,但是,目前暂不支持“'get/{id}”此种形式,根据通配符调用接口功能。

一、解析通配符接口配置

  • 需求分析,思路梳理

为使api模型支持通配符形式解析,并且后期可以方便扩展其他形式解析,我的思路大致如下:

1、定义一个“api_type”用来存储Api类型,目前仅支持“默认”,“通配符”两种类型。

2、考虑到解析性能问题,定义一个“parse_param_config”字段,用来存储解析值,数据格式大致如下;

[{"index":4,"key":"orderNum"}]           

参数Key为映射请求参数对象的名称,后面的数字为以“/”分割后值的“index”位置。

  • 代码实现

1、定义Api规则对象

package com.flycoding.drivenlibrary.engine.api.entity.bo;

/**
* api 规则bo
*
* @author 赵屈犇
* @version 1.0
* @date 创建时间: 2023/8/3 20:08
* @Copyright(C): 2023 by 赵屈犇
*/
public class ApiMatchBO {

    /**
* 索引位置
*/
    private int index;
    /**
* 参数主键
*/
    private String key;

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}           

2、声明Api ORM映射

/**
* 服务类型
*/
@FormFieldConfig(fieldCode = "api_type", fieldName = "服务类型", elementCode = DrivenElementConstants.SELECT_ELEMENT,
                 dict = @DictConfig(dictCode = ApiType.DICTIONARY_CODE), fieldParentName = BASIC_MESSAGE_NAME)
@Column(columnName = "api_type", columnType = ColumnType.VARCHAR, length = SqlConstants.DICTIONARY_VALUE_SIZE, isNotNull = true)
private String apiType;

/**
* 解析参数配置
*/
@Column(columnName = "parse_param_config", columnType = ColumnType.VARCHAR, length = 500)
private List<ApiMatchBO> parseParamConfig;           

3、声明 apiType 字典数据

/**
 * api 类型
 */
@DictionaryConfig(dictionaryCode = ApiType.DICTIONARY_CODE, dictionaryName = "api 类型")
public static class ApiType {

	/**
	* 字典编码
	*/
   	public static final String DICTIONARY_CODE = "1042";
    /**
	 * 默认
	 */
    @DictionaryConfig(dictionaryName = "默认")
    public static final String DEFAULT = "104201";
    /**
	 * 通配符
	 */
    @DictionaryConfig(dictionaryName = "通配符")
    public static final String WILDCARD = "104202";
}           

4、解析通配符接口

String apiType = ConfigDictionaryConstants.ApiType.DEFAULT;
// 分割api 全路径
String[] apiFullUrls = (moduleUrl + "/" + api.getApiUrl()).split("/");
StringJoiner apiFullUrl = new StringJoiner("/");
if (ArrayUtils.isNotEmpty(apiFullUrls)) {
    JSONObject parseParamConfig = new JSONObject();
    for (int i = 0; i < apiFullUrls.length; i++) {
    	String fullUrl = apiFullUrls[i];
        if (!fullUrl.equals("/")) {
        	// 解析 {} 通配符
            if (RegularHelper.isMatcher(fullUrl, RegularConstants.BRACES)) {
            	Matcher matcher = RegularHelper.getMatcher(fullUrl, RegularConstants.BRACES);
                while (matcher.find()) {
                	String group = matcher.group(1);
                    parseParamConfig.put(group, i);
                    apiType = ConfigDictionaryConstants.ApiType.WILDCARD;
                }
            } else {
                apiFullUrl.add(fullUrl);
         	}
		}
	}
api.setParseParamConfig(parseParamConfig);           
  • 代码执行结果
fly coding Api请求支持通配符“{}”调用接口

二、解析支持通配符请求

  • 需求分析,思路梳理

此上,已实现结构解析并入库。接下来,我需要实现Api功能支持,通配符访问请求。要实现这种功能,我需要实现Api接口通配符形式的匹配,我有两种实现方式,一种是通过startWith直接判断,另一种是通过正则匹配。

以下是两种实现方式的优劣分析:

  • startWith
    • 性能高于正则,但是扩展性不好,仅支持get/{id}此种形式
  • 正则匹配
    • 性能低于startWith,但是扩展性好,可以支持各种形式匹配规则

以下是性能示例图:

fly coding Api请求支持通配符“{}”调用接口

可以看出,startWith远远高于正则匹配。综合考虑,我决定采用startWith方式进行匹配。

为提高检索效率,我仅准备缓存通配符类型的Api接口键值对,这样的话,也可以快速匹配索引到对应的Api地址。若未匹配到时,走之前处理逻辑即可。

  • 代码实现

1、Api匹配规则工厂

package com.flycoding.drivenlibrary.engine.api.factory;

import com.alibaba.fastjson.JSONObject;
import com.flycoding.dblibrary.executor.inter.ISqlExecutor;
import com.flycoding.drivenlibrary.engine.config.constants.dictionary.ConfigDictionaryConstants;
import com.flycoding.drivenlibrary.engine.config.entity.ModelDBConfigMessage;
import com.flycoding.drivenlibrary.engine.config.factory.DBConfigFactory;
import com.flycoding.drivenlibrary.engine.request.build.sql.QueryBuilder;
import com.flycoding.drivenlibrary.enums.dictionary.QueryType;
import com.flycoding.utillibrary.java.ArrayUtils;
import com.flycoding.utillibrary.logger.LoggerFactory;

import java.util.List;

/**
 * api 匹配工厂
 *
 * @author 赵屈犇
 * @version 1.0
 * @date 创建时间: 2023/8/3 20:26
 * @Copyright(C): 2023 by 赵屈犇
 */
public class ApiMatchFactory {

    private static final LoggerFactory logger = LoggerFactory.getLogger(ApiMatchFactory.class.getName());

    private static ApiMatchFactory instance;

    /**
     * DB配置信息的工厂
     */
    private DBConfigFactory dbConfigFactory;
    /**
     * api 匹配地址集合
     */
    private List<String> apiMatchUrls = ArrayUtils.newArrayList();

    public static ApiMatchFactory getInstance() {
        if (instance == null) {
            synchronized (ApiMatchFactory.class) {
                if (instance == null) {
                    instance = new ApiMatchFactory();
                }
            }
        }
        return instance;
    }

    private ApiMatchFactory() {
        dbConfigFactory = DBConfigFactory.getInstance();
    }

    /**
     * 初始化匹配规则
     */
    public void initMatch() {
        try {
            List<ModelDBConfigMessage> dbConfigs = DBConfigFactory.getInstance().getDBConfigs();
            if (ArrayUtils.isNotEmpty(dbConfigs)) {
                for (ModelDBConfigMessage config : dbConfigs) {
                    ISqlExecutor executor = dbConfigFactory.getModelSqlExecutor(config.getDbConfigCode());
                    List<JSONObject> datas = (List<JSONObject>) QueryBuilder.builder().executor(executor).table("Sy_Api").column("api_full_url", "apiFullUrl")
                            .whereBuilder().and("api_type", QueryType.NOT_EQUAL, ConfigDictionaryConstants.ApiType.DEFAULT)
                            .sqlBuilder().execute();
                    if (ArrayUtils.isNotEmpty(datas)) {
                        for (JSONObject data : datas) {
                            apiMatchUrls.add(data.getString("apiFullUrl"));
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("初始化匹配规则报错了,", e);
        }
    }

    /**
     * 获取api根据规则
     *
     * @param apiUrl
     * @return
     */
    public String getApiByMatch(String apiUrl) {
        for (String apiMatchUrl : apiMatchUrls) {
            if (apiUrl.startsWith(apiMatchUrl)) {
                return apiMatchUrl;
            }
        }
        return apiUrl;
    }

}
           

2、改造Api入口,实现请求参数入参

// 判断是否为通配符规则
if (ConfigDictionaryConstants.ApiType.DEFAULT != apiInfo.getApiType()) {
    List<ApiMatchBO> parseParamConfig = apiInfo.getParseParamConfig();
    if (ArrayUtils.isNotEmpty(parseParamConfig)) {
        String[] apiUrls = apiUrl.split("/");
        for (ApiMatchBO apiMatch : parseParamConfig) {
            if (apiUrls.length > apiMatch.getIndex()) {
                requestParams.put(apiMatch.getKey(), apiUrls[apiMatch.getIndex()]);
            }
        }
    }
}           
  • 代码执行结果
fly coding Api请求支持通配符“{}”调用接口

继续阅读