0%

基于 Spring 实现灵活的动态接口生成器

在项目中,我们通常通过硬编码的方式定义对外统一的 API 接口,这种方式在对于多对接方、每个对接方的字段不一致的场景下显得不够灵活。为了解决这个问题,我们设计了一个动态接口生成系统,它具有以下特点:

  • 配置驱动: 通过简单的配置文件修改,即可动态生成或更新 REST 接口。
  • 行为可配置: 能够根据配置选择接口的后续行为,如发送消息队列或调用 API。
  • 数据转换: 支持动态的字段名映射和字段值转换。

这个系统的核心目标是提高 API 开发和维护的效率,同时保持系统的灵活性和可扩展性。

动态 API 配置

首先,我们先用配置文件的形式来配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interfaces:
- name: interface1
path: /api/v1/interface1
method: POST
action:
type: mq
queue: queue1
fieldMappings:
name:
targetName: nameEn
valueMapping:
Y: "1"
N: "0"
- name: interface2
path: /api/v2/interface2
method: GET
action:
type: http
url: http://external-api.com/endpoint

对应的 Java 配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
@ConfigurationProperties(prefix = "interfaces")
public class InterfaceConfig {
private List<InterfaceDefinition> interfaces;
// getters and setters
}

public class InterfaceDefinition {
private String name;
// 定义接口的 URL 路径
private String path;
private String method;
// 定义接口的后续行为
private ActionConfig action;
// 定义字段映射规则。这允许在处理请求时动态转换字段名和字段值
// 原字段名作为 key
private Map<String, FieldMapping> fieldMappings;
// getters and setters
}

public class ActionConfig {
private String type;
private String queue;
private String url;
// getters and setters
}

public class FieldMapping {
// 指定转换后的字段名
private String targetName;
// (可选)定义值的映射规则
private Map<String, String> valueMapping;

// getters and setters
}
动态接口生成器

核心类 DynamicInterfaceGenerator 负责生成和处理动态接口。动态注册 RequestMapping
我们使用 RequestMappingHandlerMapping 来动态注册接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Component
public class DynamicInterfaceGenerator {

@Autowired
private InterfaceConfig config;

@Autowired
private ApplicationContext context;

@Autowired
private ObjectMapper objectMapper;

@PostConstruct
public void generateInterfaces() {
// 遍历所有配置的接口并注册到 Spring 的 RequestMappingHandlerMapping 中
for (InterfaceDefinition def : config.getInterfaces()) {
registerMapping(def);
}
}

private void registerMapping(InterfaceDefinition def) {
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
try {
mapping.registerMapping(
new RequestMappingInfo.BuilderConfiguration()
.paths(def.getPath())
.methods(RequestMethod.valueOf(def.getMethod()))
.build(),
this,
DynamicInterfaceGenerator.class.getDeclaredMethod("handleRequest", HttpServletRequest.class, InterfaceDefinition.class)
);
} catch (NoSuchMethodException e) {
// 处理异常
}
}

// 所有动态生成的接口的实际处理方法。它会根据配置决定是发送 MQ 还是调用外部接口
public ResponseEntity<?> handleRequest(HttpServletRequest request, InterfaceDefinition def) throws IOException {
// 读取请求体
String body = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);

// 转换字段
String convertedBody = convertFields(body, def.getFieldMappings());
// 此处可以优化为策略模式
if ("mq".equals(def.getAction().getType())) {
// 发送 MQ
sendToMQ(def.getAction().getQueue(), convertedBody);
} else if ("http".equals(def.getAction().getType())) {
// 调用外部接口
return callExternalAPI(def.getAction().getUrl(), convertedBody);
}
return ResponseEntity.ok().build();
}

// 其他方法...
}
动态字段转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private String convertFields(String jsonBody, Map<String, FieldMapping> fieldMappings) throws IOException {
if (fieldMappings == null || fieldMappings.isEmpty()) {
return jsonBody;
}

JsonNode rootNode = objectMapper.readTree(jsonBody);
ObjectNode convertedNode = (ObjectNode) rootNode;

for (Map.Entry<String, FieldMapping> entry : fieldMappings.entrySet()) {
String sourceField = entry.getKey();
FieldMapping mapping = entry.getValue();

if (convertedNode.has(sourceField)) {
JsonNode sourceValue = convertedNode.get(sourceField);

// 转换字段名
convertedNode.remove(sourceField);

// 转换字段值
JsonNode convertedValue = convertFieldValue(sourceValue, mapping.getValueMapping());

convertedNode.set(mapping.getTargetName(), convertedValue);
}
}

return objectMapper.writeValueAsString(convertedNode);
}

private JsonNode convertFieldValue(JsonNode sourceValue, Map<String, String> valueMapping) {
if (valueMapping == null || valueMapping.isEmpty()) {
return sourceValue;
}

String stringValue = sourceValue.asText();
String mappedValue = valueMapping.getOrDefault(stringValue, stringValue);

return new TextNode(mappedValue);
}
可扩展的内容
  • 数据库配置:
    目前配置是从 YAML 文件读取的, 我们可以扩展为从数据库读取配置。这样可以实现配置的动态更新, 无需重启应用。

  • 缓存机制:
    对于频繁访问的接口, 我们可以引入缓存机制来提高性能。

  • 权限控制:
    可以在动态生成的接口上添加权限控制逻辑, 确保接口的安全性。

  • 监控和日志:
    添加详细的日志记录和监控指标, 以便于 troubleshooting 和性能优化。

  • 更复杂的字段转换:
    目前的字段转换逻辑相对简单, 我们可以扩展为支持更复杂的转换规则, 如正则表达式匹配、条件转换等。

  • 配置验证:
    添加配置验证逻辑, 确保配置文件的正确性, 提高系统的健壮性。