在项目中,我们通常通过硬编码的方式定义对外统一的 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; }
public class InterfaceDefinition { private String name; private String path; private String method; private ActionConfig action; private Map<String, FieldMapping> fieldMappings; }
public class ActionConfig { private String type; private String queue; private String url; }
public class FieldMapping { private String targetName; private Map<String, String> valueMapping;
}
|
动态接口生成器
核心类 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() { 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) { } }
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())) { 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 和性能优化。
更复杂的字段转换:
目前的字段转换逻辑相对简单, 我们可以扩展为支持更复杂的转换规则, 如正则表达式匹配、条件转换等。
配置验证:
添加配置验证逻辑, 确保配置文件的正确性, 提高系统的健壮性。