mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 11:13:43 +08:00
102 lines
3.8 KiB
Java
102 lines
3.8 KiB
Java
package vip.mate.hook;
|
||
|
||
import com.fasterxml.jackson.databind.JsonNode;
|
||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
import lombok.RequiredArgsConstructor;
|
||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||
import org.springframework.http.client.JdkClientHttpRequestFactory;
|
||
import org.springframework.stereotype.Component;
|
||
import org.springframework.web.client.RestClient;
|
||
import vip.mate.hook.action.*;
|
||
import vip.mate.hook.model.HookEntity;
|
||
|
||
import java.net.URI;
|
||
import java.net.http.HttpClient;
|
||
import java.time.Duration;
|
||
|
||
/**
|
||
* 按 {@link HookEntity} 的 {@code action_kind} + {@code action_config} 装配 {@link HookAction}。
|
||
*
|
||
* <p>解析一次后由 {@code HookRegistry} 缓存复用;HTTP RestClient 在本 factory 内持有单例
|
||
* 连接池,避免为每个 hook 重新构造。</p>
|
||
*/
|
||
@Component
|
||
@RequiredArgsConstructor
|
||
public class HookActionFactory {
|
||
|
||
private final ObjectMapper objectMapper;
|
||
private final HookProperties props;
|
||
|
||
/** 懒加载的共享 RestClient;所有 HttpAction 复用同一连接池。 */
|
||
private volatile RestClient httpRestClient;
|
||
|
||
public HookAction build(HookEntity e) {
|
||
HookAction.Kind kind = HookAction.Kind.valueOf(e.getActionKind());
|
||
JsonNode cfg = readConfig(e.getActionConfig());
|
||
long timeoutMs = e.getTimeoutMs() == null ? 3000L : e.getTimeoutMs();
|
||
|
||
HookAction action = switch (kind) {
|
||
case BUILTIN -> new BuiltinAction(
|
||
text(cfg, "op", "log.info"),
|
||
text(cfg, "arg", ""));
|
||
case HTTP -> new HttpAction(
|
||
sharedRestClient(),
|
||
text(cfg, "method", "POST"),
|
||
URI.create(required(cfg, "url")),
|
||
text(cfg, "body", null),
|
||
props.getTrustedDomains(),
|
||
timeoutMs);
|
||
case SHELL -> new ShellAction(required(cfg, "command"));
|
||
case CHANNEL_MESSAGE -> new ChannelMessageAction(
|
||
required(cfg, "channelType"),
|
||
text(cfg, "message", ""));
|
||
};
|
||
action.validate();
|
||
return action;
|
||
}
|
||
|
||
private RestClient sharedRestClient() {
|
||
var existing = this.httpRestClient;
|
||
if (existing != null) return existing;
|
||
synchronized (this) {
|
||
if (this.httpRestClient != null) return this.httpRestClient;
|
||
ClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(
|
||
HttpClient.newBuilder()
|
||
.connectTimeout(props.getHttp().getConnectTimeout())
|
||
.build());
|
||
((JdkClientHttpRequestFactory) requestFactory).setReadTimeout(props.getHttp().getReadTimeout());
|
||
this.httpRestClient = RestClient.builder()
|
||
.requestFactory(requestFactory)
|
||
.build();
|
||
return this.httpRestClient;
|
||
}
|
||
}
|
||
|
||
private JsonNode readConfig(String json) {
|
||
if (json == null || json.isBlank()) return objectMapper.createObjectNode();
|
||
try {
|
||
return objectMapper.readTree(json);
|
||
} catch (Exception e) {
|
||
throw new IllegalArgumentException("invalid action_config json: " + e.getMessage(), e);
|
||
}
|
||
}
|
||
|
||
private static String text(JsonNode n, String field, String def) {
|
||
JsonNode v = n.get(field);
|
||
return (v == null || v.isNull()) ? def : v.asText();
|
||
}
|
||
|
||
private static String required(JsonNode n, String field) {
|
||
String v = text(n, field, null);
|
||
if (v == null || v.isEmpty()) {
|
||
throw new IllegalArgumentException("missing required action config field: " + field);
|
||
}
|
||
return v;
|
||
}
|
||
|
||
/** 测试辅助:让 sharedRestClient 可注入。 */
|
||
void overrideRestClientForTest(RestClient rc) {
|
||
this.httpRestClient = rc;
|
||
}
|
||
}
|