feat: support dubbo service memshell

This commit is contained in:
ReaJason
2026-03-21 00:51:42 +08:00
parent 25e1b3964a
commit 51b7e8e17f
32 changed files with 1233 additions and 274 deletions
@@ -134,7 +134,9 @@ export default function MainConfigCard({
const currentTargetJdk = form.getValues("targetJdkVersion") as string;
const currentJdkVersion = Number.parseInt(currentTargetJdk, 10);
const shouldRaiseJdkVersion =
(server === "SpringWebFlux" || server === "XXLJOB") &&
(server === "SpringWebFlux" ||
server === "XXLJOB" ||
server === "Dubbo") &&
currentJdkVersion <= 52;
const nextJdkVersion = shouldRaiseJdkVersion ? "52" : "50";
if (currentTargetJdk !== nextJdkVersion) {
@@ -22,6 +22,7 @@ export function BasicInfo({
generateResult,
}: Readonly<{ generateResult?: MemShellResult }>) {
const { t } = useTranslation(["memshell", "common"]);
const isDubbo = generateResult?.shellConfig.server === "Dubbo";
return (
<Card>
<CardHeader>
@@ -54,9 +55,8 @@ export function BasicInfo({
value={generateResult?.injectorConfig.urlPattern}
/>
</div>
{generateResult?.shellConfig.shellTool !== ShellToolType.Custom && (
<Separator className="my-1" />
)}
{generateResult?.shellConfig.shellTool !== ShellToolType.Custom &&
!isDubbo && <Separator className="my-1" />}
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{generateResult?.shellConfig.shellTool === ShellToolType.Behinder && (
<>
@@ -125,37 +125,38 @@ export function BasicInfo({
/>
</>
)}
{generateResult?.shellConfig.shellTool === ShellToolType.Command && (
<Fragment>
<CopyableField
hidden={generateResult?.shellConfig.shellType.includes(
"WebSocket",
)}
label={t("common:paramName")}
text={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
value={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
/>
<CopyableField
hidden={
!(
generateResult?.shellConfig.shellType ===
"BypassNginxWebSocket" ||
generateResult?.shellConfig.shellType ===
"BypassNginxJakartaWebSocket"
)
}
label={t("shellToolConfig.httpHeader")}
text={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
value={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
/>
</Fragment>
)}
{generateResult?.shellConfig.shellTool === ShellToolType.Command &&
!isDubbo && (
<Fragment>
<CopyableField
hidden={generateResult?.shellConfig.shellType.includes(
"WebSocket",
)}
label={t("common:paramName")}
text={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
value={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
/>
<CopyableField
hidden={
!(
generateResult?.shellConfig.shellType ===
"BypassNginxWebSocket" ||
generateResult?.shellConfig.shellType ===
"BypassNginxJakartaWebSocket"
)
}
label={t("shellToolConfig.httpHeader")}
text={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
value={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
/>
</Fragment>
)}
{(generateResult?.shellConfig.shellTool === ShellToolType.Suo5 ||
generateResult?.shellConfig.shellTool === ShellToolType.Suo5v2) && (
<CopyableField
@@ -3,6 +3,10 @@ import CodeViewer from "@/components/code-viewer";
import type { MemShellResult } from "@/types/memshell";
import { AgentResult } from "./agent";
import { JarResult } from "./jar-result";
import { useCallback } from "react";
import { base64ToBytes, downloadBytes, downloadContent } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { DownloadIcon } from "lucide-react";
export function ResultComponent({
packResult,
@@ -34,6 +38,27 @@ export function ResultComponent({
/>
);
}
const shellClassName = generateResult?.shellClassName;
const handleDownload = useCallback(() => {
const fileName =
shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
if (packMethod.includes("JSP")) {
const fileExtension = packMethod.includes("JSPX") ? ".jspx" : ".jsp";
const content = new Blob([packResult as string], { type: "text/plain" });
return downloadContent(content, fileName, fileExtension);
} else if (
packMethod.includes("JavaCommons") ||
packMethod.includes("Hessian")
) {
const content = new Blob([base64ToBytes(packResult as string)], {
type: "application/octet-stream",
});
return downloadContent(content, fileName, ".data");
} else if (packMethod === "Base64") {
return downloadBytes(packResult as string, shellClassName);
}
}, [packMethod, packResult, shellClassName]);
return (
<CodeViewer
@@ -46,6 +71,22 @@ export function ResultComponent({
<span className="text-muted-foreground">({packResult?.length})</span>
</div>
}
button={
packMethod.includes("JSP") ||
packMethod === "Base64" ||
packMethod.includes("JavaCommons") ||
packMethod.includes("Hessian") ? (
<Button
variant="ghost"
size="icon"
type="button"
className="h-7 w-7 [&_svg]:h-4 [&_svg]:w-4"
onClick={handleDownload}
>
<DownloadIcon className="h-4 w-4" />
</Button>
) : null
}
wrapLongLines={true}
showLineNumbers={false}
language={"text"}
+49 -2
View File
@@ -5,7 +5,7 @@ import { toast } from "sonner";
import { QuickUsage } from "@/components/memshell/quick-usage";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { downloadBytes } from "@/lib/utils";
import { cn, downloadBytes } from "@/lib/utils";
import type { MemShellResult } from "@/types/memshell";
import CodeViewer from "../code-viewer";
import { BasicInfo } from "./results/basic-info";
@@ -21,13 +21,19 @@ export default function ShellResult({
generateResult?: MemShellResult;
}>) {
const { t } = useTranslation(["common", "memshell"]);
const hasHelper =
generateResult?.injectorHelperBytesBase64Str &&
generateResult?.injectorHelperBytesBase64Str !== "";
if (!generateResult) {
return <QuickUsage />;
}
const height = 600;
return (
<Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-3">
<TabsList
className={cn("grid w-full", hasHelper ? "grid-cols-4" : "grid-cols-3")}
>
<TabsTrigger value="packResult">
{t("common:generateResult")}
</TabsTrigger>
@@ -35,6 +41,11 @@ export default function ShellResult({
<TabsTrigger value="injector">
{t("memshell:injectorClass")}
</TabsTrigger>
{hasHelper && (
<TabsTrigger value="injectorHelper">
{t("memshell:injectorHelperClass")}
</TabsTrigger>
)}
</TabsList>
<TabsContent value="packResult" className="space-y-2">
<BasicInfo generateResult={generateResult} />
@@ -110,6 +121,42 @@ export default function ShellResult({
language="text"
/>
</TabsContent>
{hasHelper && (
<TabsContent value="injectorHelper" className="mt-4">
<CodeViewer
showLineNumbers={false}
wrapLongLines={true}
header={
<div className="text-xs">
{generateResult?.shellClassName + "$1"}
</div>
}
button={
<Button
variant="ghost"
size="icon"
type="button"
className="h-7 w-7 [&_svg]:h-4 [&_svg]:w-4"
onClick={() => {
if (!generateResult?.injectorHelperBytesBase64Str) {
toast.warning(t("memshell:tips.shellBytesEmpty"));
return;
}
downloadBytes(
generateResult?.injectorHelperBytesBase64Str,
generateResult?.shellClassName + "$1",
);
}}
>
<DownloadIcon className="h-4 w-4" />
</Button>
}
height={height}
code={generateResult?.injectorHelperBytesBase64Str ?? ""}
language="text"
/>
</TabsContent>
)}
</Tabs>
);
}
@@ -42,6 +42,10 @@ export function CommandTabContent({
name: "shellType",
control: form.control,
});
const server = useWatch({
name: "server",
control: form.control,
});
const { data } = useQuery<{
encryptors: Array<string>;
implementationClasses: Array<string>;
@@ -58,31 +62,36 @@ export function CommandTabContent({
<Card>
<CardContent className="space-y-2 mt-4">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<Controller
control={form.control}
name="commandParamName"
render={({ field }) => (
<Field className="gap-1" hidden={shellType.includes("WebSocket")}>
<div className="flex items-center gap-1">
<FieldLabel>
{t("common:paramName")} {t("common:optional")}
</FieldLabel>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:paramName.description")}</p>
</TooltipContent>
</Tooltip>
</div>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
{server !== "Dubbo" && (
<Controller
control={form.control}
name="commandParamName"
render={({ field }) => (
<Field
className="gap-1"
hidden={shellType.includes("WebSocket")}
>
<div className="flex items-center gap-1">
<FieldLabel>
{t("common:paramName")} {t("common:optional")}
</FieldLabel>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:paramName.description")}</p>
</TooltipContent>
</Tooltip>
</div>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
)}
<div
className="grid grid-cols-1 md:grid-cols-2 gap-2"
hidden={