refactor: simplify config api

This commit is contained in:
ReaJason
2026-08-03 23:54:06 +08:00
parent 82bbd2cf0a
commit 8f2024917c
7 changed files with 102 additions and 76 deletions
@@ -6,6 +6,7 @@ import com.reajason.javaweb.memshell.ServerFactory;
import com.reajason.javaweb.memshell.config.CommandConfig; import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.server.AbstractServer; import com.reajason.javaweb.memshell.server.AbstractServer;
import com.reajason.javaweb.packer.Packers; import com.reajason.javaweb.packer.Packers;
import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -22,7 +23,12 @@ import java.util.*;
@CrossOrigin("*") @CrossOrigin("*")
public class ConfigController { public class ConfigController {
/**
* @deprecated use {@link #config()} for memshell configuration and
* {@link #getProbeResponseBodyServers()} for probe ResponseBody servers.
*/
@RequestMapping("/servers") @RequestMapping("/servers")
@Deprecated(since = "2.9.0", forRemoval = false)
public Map<String, List<String>> getServers() { public Map<String, List<String>> getServers() {
Map<String, List<String>> servers = new LinkedHashMap<>(); Map<String, List<String>> servers = new LinkedHashMap<>();
List<String> supportedServers = ServerFactory.getSupportedServers(); List<String> supportedServers = ServerFactory.getSupportedServers();
@@ -34,7 +40,11 @@ public class ConfigController {
return servers; return servers;
} }
/**
* @deprecated use {@link #getPackerTree()} for parent/child packer metadata.
*/
@RequestMapping("/packers") @RequestMapping("/packers")
@Deprecated(since = "2.9.0", forRemoval = false)
public List<String> getPackers() { public List<String> getPackers() {
return Arrays.stream(Packers.values()) return Arrays.stream(Packers.values())
.filter(packers -> packers.getParentPacker() == null) .filter(packers -> packers.getParentPacker() == null)
@@ -56,9 +66,14 @@ public class ConfigController {
.toList(); .toList();
} }
@RequestMapping("/probe/response-body/servers")
public List<String> getProbeResponseBodyServers() {
return ResponseBodyGenerator.getSupportedServers();
}
@RequestMapping @RequestMapping
public Map<String, Map<?, ?>> config() { public Map<String, Map<?, ?>> config() {
Map<String, Map<?, ?>> coreMap = new HashMap<>(16); Map<String, Map<?, ?>> coreMap = new LinkedHashMap<>(16);
List<String> supportedServers = ServerFactory.getSupportedServers(); List<String> supportedServers = ServerFactory.getSupportedServers();
for (String supportedServer : supportedServers) { for (String supportedServer : supportedServers) {
AbstractServer server = ServerFactory.getServer(supportedServer); AbstractServer server = ServerFactory.getServer(supportedServer);
@@ -82,4 +97,4 @@ public class ConfigController {
commandConfigVO.setImplementationClasses(Arrays.stream(CommandConfig.ImplementationClass.values()).toList()); commandConfigVO.setImplementationClasses(Arrays.stream(CommandConfig.ImplementationClass.values()).toList());
return commandConfigVO; return commandConfigVO;
} }
} }
@@ -1,9 +1,12 @@
package com.reajason.javaweb.boot.controller; package com.reajason.javaweb.boot.controller;
import org.junit.jupiter.api.Test; import com.reajason.javaweb.memshell.ServerFactory;
import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClient;
@@ -22,6 +25,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ConfigControllerIntegrationTest { public class ConfigControllerIntegrationTest {
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE = new ParameterizedTypeReference<>() {
};
private static final ParameterizedTypeReference<List<String>> STRING_LIST_TYPE = new ParameterizedTypeReference<>() {
};
@LocalServerPort @LocalServerPort
private int port; private int port;
@@ -36,31 +45,44 @@ public class ConfigControllerIntegrationTest {
@Test @Test
public void testConfigEndpoint() { public void testConfigEndpoint() {
ResponseEntity<Map> response = restClient.get() ResponseEntity<Map<String, Object>> response = restClient.get()
.uri("/api/config") .uri("/api/config")
.retrieve() .retrieve()
.toEntity(Map.class); .toEntity(MAP_TYPE);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
assertEquals(ServerFactory.getSupportedServers(), List.copyOf(response.getBody().keySet()));
} }
@Test @Test
public void testConfigServersEndpoint() { public void testConfigServersEndpoint() {
ResponseEntity<Map> response = restClient.get() ResponseEntity<Map<String, Object>> response = restClient.get()
.uri("/api/config/servers") .uri("/api/config/servers")
.retrieve() .retrieve()
.toEntity(Map.class); .toEntity(MAP_TYPE);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals(ServerFactory.getSupportedServers(), List.copyOf(response.getBody().keySet()));
}
@Test
public void testConfigPackersEndpoint() {
ResponseEntity<List<String>> response = restClient.get()
.uri("/api/config/packers")
.retrieve()
.toEntity(STRING_LIST_TYPE);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
} }
@Test @Test
public void testConfigPackersEndpoint() { public void testConfigProbeResponseBodyServersEndpoint() {
ResponseEntity<List> response = restClient.get() ResponseEntity<List<String>> response = restClient.get()
.uri("/api/config/packers") .uri("/api/config/probe/response-body/servers")
.retrieve() .retrieve()
.toEntity(List.class); .toEntity(STRING_LIST_TYPE);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
assertEquals(ResponseBodyGenerator.getSupportedServers(), response.getBody());
} }
} }
@@ -23,6 +23,10 @@ import org.apache.commons.lang3.StringUtils;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.named;
@@ -32,10 +36,16 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
* @since 2025/6/29 * @since 2025/6/29
*/ */
public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyConfig> { public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyConfig> {
private static final Map<String, Class<?>> WRITER_CLASSES = createWriterClasses();
public ResponseBodyGenerator(ProbeConfig probeConfig, ResponseBodyConfig probeContentConfig) { public ResponseBodyGenerator(ProbeConfig probeConfig, ResponseBodyConfig probeContentConfig) {
super(probeConfig, probeContentConfig); super(probeConfig, probeContentConfig);
} }
public static List<String> getSupportedServers() {
return new ArrayList<>(WRITER_CLASSES.keySet());
}
@Override @Override
protected DynamicType.Builder<?> build(ByteBuddy buddy) { protected DynamicType.Builder<?> build(ByteBuddy buddy) {
Class<?> getDataFromReqInterceptor = getDataFromReqInterceptor.class; Class<?> getDataFromReqInterceptor = getDataFromReqInterceptor.class;
@@ -82,38 +92,32 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
} }
private Class<?> getWriterClass() { private Class<?> getWriterClass() {
switch (probeContentConfig.getServer()) { Class<?> writerClass = WRITER_CLASSES.get(probeContentConfig.getServer());
case Server.SpringWebMvc: if (writerClass == null) {
return SpringWebMvcWriter.class; throw new GenerationException("responseBody not supported for server: " + probeContentConfig.getServer());
case Server.Jetty:
case Server.Jetty5:
return JettyWriter.class;
case Server.Tomcat:
case Server.JBoss:
case Server.BES:
return TomcatWriter.class;
case Server.TongWeb:
return TongWebWriter.class;
case Server.Resin:
return ResinWriter.class;
case Server.Resin2:
return Resin2Writer.class;
case Server.Undertow:
return UndertowWriter.class;
case Server.GlassFish:
case Server.InforSuite:
return GlassFishWriter.class;
case Server.WebSphere:
return WebSphereWriter.class;
case Server.WebLogic:
return WebLogicWriter.class;
case Server.Apusic:
return ApusicWriter.class;
case Server.Struts2:
return Struts2Writer.class;
default:
throw new GenerationException("responseBody not supported for server: " + probeContentConfig.getServer());
} }
return writerClass;
}
private static Map<String, Class<?>> createWriterClasses() {
Map<String, Class<?>> writerClasses = new LinkedHashMap<>();
writerClasses.put(Server.Tomcat, TomcatWriter.class);
writerClasses.put(Server.Jetty, JettyWriter.class);
writerClasses.put(Server.Jetty5, JettyWriter.class);
writerClasses.put(Server.Undertow, UndertowWriter.class);
writerClasses.put(Server.JBoss, TomcatWriter.class);
writerClasses.put(Server.Resin, ResinWriter.class);
writerClasses.put(Server.Resin2, Resin2Writer.class);
writerClasses.put(Server.WebLogic, WebLogicWriter.class);
writerClasses.put(Server.WebSphere, WebSphereWriter.class);
writerClasses.put(Server.GlassFish, GlassFishWriter.class);
writerClasses.put(Server.TongWeb, TongWebWriter.class);
writerClasses.put(Server.BES, TomcatWriter.class);
writerClasses.put(Server.InforSuite, GlassFishWriter.class);
writerClasses.put(Server.Apusic, ApusicWriter.class);
writerClasses.put(Server.SpringWebMvc, SpringWebMvcWriter.class);
writerClasses.put(Server.Struts2, Struts2Writer.class);
return writerClasses;
} }
static class getDataFromReqInterceptor { static class getDataFromReqInterceptor {
@@ -181,5 +185,3 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
public @interface ValueAnnotation { public @interface ValueAnnotation {
} }
} }
@@ -25,7 +25,7 @@ import {
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Tabs } from "@/components/ui/tabs"; import { Tabs } from "@/components/ui/tabs";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { type MainConfig, type ServerConfig, ShellToolType } from "@/types/memshell"; import { type MainConfig, ShellToolType } from "@/types/memshell";
import { Spinner } from "../ui/spinner"; import { Spinner } from "../ui/spinner";
import { JREVersionFormField } from "./jreversion-field"; import { JREVersionFormField } from "./jreversion-field";
@@ -35,11 +35,9 @@ import { ProxyTabContent } from "./tabs/proxy-tab";
export default function MainConfigCard({ export default function MainConfigCard({
mainConfig, mainConfig,
form, form,
servers,
}: Readonly<{ }: Readonly<{
mainConfig: MainConfig | undefined; mainConfig: MainConfig | undefined;
form: UseFormReturn<MemShellFormSchema>; form: UseFormReturn<MemShellFormSchema>;
servers?: ServerConfig;
}>) { }>) {
const { t } = useTranslation(["common", "memshell"]); const { t } = useTranslation(["common", "memshell"]);
@@ -60,7 +58,7 @@ export default function MainConfigCard({
return mainConfig[server]; return mainConfig[server];
}, [mainConfig, server]); }, [mainConfig, server]);
const serverOptions = useMemo(() => Object.keys(servers ?? {}), [servers]); const serverOptions = useMemo(() => Object.keys(mainConfig ?? {}), [mainConfig]);
const shellTools = useMemo(() => { const shellTools = useMemo(() => {
if (!serverToolMap) { if (!serverToolMap) {
@@ -71,11 +69,11 @@ export default function MainConfigCard({
}, [serverToolMap]); }, [serverToolMap]);
const customShellTypes = useMemo(() => { const customShellTypes = useMemo(() => {
if (!server) { if (!serverToolMap) {
return []; return [];
} }
return servers?.[server] ?? []; return Array.from(new Set(Object.values(serverToolMap).flat()));
}, [server, servers]); }, [serverToolMap]);
const shellTypes = useMemo(() => { const shellTypes = useMemo(() => {
if (!serverToolMap || !server) { if (!serverToolMap || !server) {
@@ -1,4 +1,3 @@
import type { ServerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema"; import type { ProbeShellFormSchema } from "@/types/schema";
import { InfoIcon, ServerIcon } from "lucide-react"; import { InfoIcon, ServerIcon } from "lucide-react";
@@ -62,10 +61,10 @@ const DEFAULT_FORM_VALUES = {
interface MainConfigCardProps { interface MainConfigCardProps {
readonly form: UseFormReturn<ProbeShellFormSchema>; readonly form: UseFormReturn<ProbeShellFormSchema>;
readonly servers?: ServerConfig; readonly responseBodyServers?: string[];
} }
export default function MainConfigCard({ form, servers }: MainConfigCardProps) { export default function MainConfigCard({ form, responseBodyServers }: MainConfigCardProps) {
const { t } = useTranslation(["common", "probeshell"]); const { t } = useTranslation(["common", "probeshell"]);
const watchedProbeMethod = form.watch("probeMethod"); const watchedProbeMethod = form.watch("probeMethod");
const watchedProbeContent = form.watch("probeContent"); const watchedProbeContent = form.watch("probeContent");
@@ -155,13 +154,11 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
<SelectValue data-placeholder={t("placeholders.select")} /> <SelectValue data-placeholder={t("placeholders.select")} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{Object.keys(servers ?? {}) {responseBodyServers?.map((server) => (
.filter((s) => s !== "SpringWebFlux" && s !== "XXLJOB") <SelectItem key={server} value={server}>
.map((server: string) => ( {server}
<SelectItem key={server} value={server}> </SelectItem>
{server} ))}
</SelectItem>
))}
</SelectContent> </SelectContent>
</Select> </Select>
{fieldState.error && <FieldError errors={[fieldState.error]} />} {fieldState.error && <FieldError errors={[fieldState.error]} />}
+1 -9
View File
@@ -18,7 +18,6 @@ import {
type MemShellGenerateResponse, type MemShellGenerateResponse,
type MemShellResult, type MemShellResult,
type PackerConfig, type PackerConfig,
type ServerConfig,
ShellToolType, ShellToolType,
} from "@/types/memshell"; } from "@/types/memshell";
import { import {
@@ -65,18 +64,11 @@ const fetchJson = async <T,>(url: string): Promise<T> => {
return response.json() as Promise<T>; return response.json() as Promise<T>;
}; };
const fetchServerConfig = () => fetchJson<ServerConfig>(`${env.API_URL}/api/config/servers`);
const fetchMainConfig = () => fetchJson<MainConfig>(`${env.API_URL}/api/config`); const fetchMainConfig = () => fetchJson<MainConfig>(`${env.API_URL}/api/config`);
const fetchPackerConfig = () => fetchJson<PackerConfig>(`${env.API_URL}/api/config/packers/tree`); const fetchPackerConfig = () => fetchJson<PackerConfig>(`${env.API_URL}/api/config/packers/tree`);
export default function MemShellPage() { export default function MemShellPage() {
const { data: serverConfig } = useQuery<ServerConfig>({
queryKey: ["serverConfig"],
queryFn: fetchServerConfig,
});
const { data: mainConfig } = useQuery<MainConfig>({ const { data: mainConfig } = useQuery<MainConfig>({
queryKey: ["mainConfig"], queryKey: ["mainConfig"],
queryFn: fetchMainConfig, queryFn: fetchMainConfig,
@@ -145,7 +137,7 @@ export default function MemShellPage() {
<div className="max-w-8xl container mx-auto p-6"> <div className="max-w-8xl container mx-auto p-6">
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6 xl:flex-row"> <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6 xl:flex-row">
<div className="flex w-full flex-col gap-2 xl:w-1/2"> <div className="flex w-full flex-col gap-2 xl:w-1/2">
<MainConfigCard servers={serverConfig} mainConfig={mainConfig} form={form} /> <MainConfigCard mainConfig={mainConfig} form={form} />
<PackageConfigCard packerConfig={packerConfig} form={form} /> <PackageConfigCard packerConfig={packerConfig} form={form} />
<Button className="w-full" type="submit" disabled={form.formState.isSubmitting}> <Button className="w-full" type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? ( {form.formState.isSubmitting ? (
+6 -6
View File
@@ -1,4 +1,4 @@
import type { APIErrorResponse, PackerConfig, ServerConfig } from "@/types/memshell"; import type { APIErrorResponse, PackerConfig } from "@/types/memshell";
import type { ProbeShellGenerateResponse, ProbeShellResult } from "@/types/probeshell"; import type { ProbeShellGenerateResponse, ProbeShellResult } from "@/types/probeshell";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -22,13 +22,13 @@ import {
} from "@/types/schema"; } from "@/types/schema";
import { transformToProbePostData } from "@/utils/transformer"; import { transformToProbePostData } from "@/utils/transformer";
import { baseOptions } from "../lib/layout.shared"; import { baseOptions } from "@/lib/layout.shared";
export default function ProbeShellGenerator() { export default function ProbeShellGenerator() {
const { data: serverConfig } = useQuery<ServerConfig>({ const { data: responseBodyServers } = useQuery<string[]>({
queryKey: ["serverConfig"], queryKey: ["probeResponseBodyServers"],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`${env.API_URL}/api/config/servers`); const response = await fetch(`${env.API_URL}/api/config/probe/response-body/servers`);
return await response.json(); return await response.json();
}, },
}); });
@@ -102,7 +102,7 @@ export default function ProbeShellGenerator() {
<div className="max-w-8xl container mx-auto p-6"> <div className="max-w-8xl container mx-auto p-6">
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6 xl:flex-row"> <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6 xl:flex-row">
<div className="flex w-full flex-col gap-2 xl:w-1/2"> <div className="flex w-full flex-col gap-2 xl:w-1/2">
<MainConfigCard form={form} servers={serverConfig} /> <MainConfigCard form={form} responseBodyServers={responseBodyServers} />
<PackageConfigCard form={form} packerConfig={packerConfig} /> <PackageConfigCard form={form} packerConfig={packerConfig} />
<Button className="w-full" type="submit" disabled={form.formState.isSubmitting}> <Button className="w-full" type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? ( {form.formState.isSubmitting ? (