此前,已经完善了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);
- 代码执行结果
二、解析支持通配符请求
- 需求分析,思路梳理
此上,已实现结构解析并入库。接下来,我需要实现Api功能支持,通配符访问请求。要实现这种功能,我需要实现Api接口通配符形式的匹配,我有两种实现方式,一种是通过startWith直接判断,另一种是通过正则匹配。
以下是两种实现方式的优劣分析:
- startWith
- 性能高于正则,但是扩展性不好,仅支持get/{id}此种形式
- 正则匹配
- 性能低于startWith,但是扩展性好,可以支持各种形式匹配规则
以下是性能示例图:
可以看出,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()]);
}
}
}
}
- 代码执行结果