---
title: 程序代码UML图生成器|万维易源ShowAPI
canonical_url: "https://www.showapi.com/ai/skills/code-uml-text-analysis-workflow"
last_updated: "2026-09-21T17:05:00.683Z"
meta:
  description: "解析用户上传的代码文件与自定义分析要求，智能生成UML图与深度代码文本分析报告，并将两者逻辑融合为完整的结构化交付物 免费在线使用，也支持 API / Agent 接入。"
  keywords: "程序代码UML图生成器, AI Skill, 万维易源, showapi, 大模型, ai技能, AI工具, 异步任务, Agent技能包, api接口, 在线使用, api接口, api接口大全, 免费api, 免费数据接口, 数据接口, api开放平台, 万维易源, showapi, 大模型, AI Skills"
  "og:description": "解析用户上传的代码文件与自定义分析要求，智能生成UML图与深度代码文本分析报告，并将两者逻辑融合为完整的结构化交付物 免费在线使用，也支持 API / Agent 接入。"
  "og:title": "程序代码UML图生成器 AI Skill|万维易源ShowAPI"
---

*

*

*

*

# **程序代码UML图生成器**

解析用户上传的代码文件与自定义分析要求，智能生成UML图与深度代码文本分析报告，并将两者逻辑融合为完整的结构化交付物

S**Serenoa** 2026-06-22 15:32 · 6 个步骤

约 ¥0.26/次调用，含模型推理价格

ID`6a38a9b83c51f2dc1a01de44`

### **能力组成**

**立即试一试** 在线体验此 Skill，输入参数即可看到实时效果

### **输出案例**

-
-
-
-

### **内部编排**

共 6 步 · AI 自动编排的执行流程与原始定义 收起

1代码结构智能解析

2UML图PlantUML提示词生成

3代码文本结构化深度分析

4启动UML图像生成任务

5轮询UML图像生成状态

6UML图与文本分析智能融合

**完整定义 (JSON)**154 行

```
{
  "workflow_id": "code_uml_text_analysis_workflow",
  "input": {
    "analysis_points": {
      "field_name": "analysis_points",
      "default": "",
      "description": "用户对UML图和代码分析的自定义要求/关注点，可不填",
      "example": "重点说明权限校验逻辑，忽略Getter/Setter",
      "type": "string",
      "required": false
    },
    "code": {
      "field_name": "code",
      "default": "",
      "description": "用户输入的原始代码文本，多个文件可通过特定分隔符拼接",
      "example": "/*\n * Copyright 2002-2019 Drew Noakes and contributors\n *\n *    Licensed under the Apache License, Version 2.0 (the \"License\");\n *    you may not use this file except in compliance with the License.\n *    You may obtain a copy of the License at\n *\n *        http://www.apache.org/licenses/LICENSE-2.0\n *\n *    Unless required by applicable law or agreed to in writing, software\n *    distributed under the License is distributed on an \"AS IS\" BASIS,\n *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *    See the License for the specific language governing permissions and\n *    limitations under the License.\n *\n * More information about this project is available at:\n *\n *    https://drewnoakes.com/code/exif/\n *    https://github.com/drewnoakes/metadata-extractor\n */\n\npackage com.drew.tools;\n\nimport com.adobe.internal.xmp.XMPException;\nimport com.adobe.internal.xmp.XMPIterator;\nimport com.adobe.internal.xmp.XMPMeta;\nimport com.adobe.internal.xmp.options.IteratorOptions;\nimport com.adobe.internal.xmp.properties.XMPPropertyInfo;\nimport com.drew.imaging.FileType;\nimport com.drew.imaging.FileTypeDetector;\nimport com.drew.imaging.ImageMetadataReader;\nimport com.drew.lang.StringUtil;\nimport com.drew.lang.annotations.NotNull;\nimport com.drew.lang.annotations.Nullable;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.Tag;\nimport com.drew.metadata.exif.ExifIFD0Directory;\nimport com.drew.metadata.exif.ExifSubIFDDirectory;\nimport com.drew.metadata.exif.ExifThumbnailDirectory;\nimport com.drew.metadata.file.FileSystemDirectory;\nimport com.drew.metadata.xmp.XmpDirectory;\n\nimport java.io.*;\nimport java.util.*;\n\n/**\n * @author Drew Noakes https://drewnoakes.com\n */\npublic class ProcessAllImagesInFolderUtility\n{\n    public static void main(String[] args) throws IOException\n    {\n        Locale.setDefault(new Locale(\"en\", \"US\"));\n        System.setProperty(\"user.timezone\", \"Australia/Sydney\");\n\n        List<String> directories = new ArrayList<String>();\n\n        FileHandler handler = null;\n        PrintStream log = System.out;\n\n        for (int i = 0; i < args.length; i++) {\n            String arg = args[i];\n            if (arg.equalsIgnoreCase(\"--text\")) {\n                // If \"--text\" is specified, write the discovered metadata into a sub-folder relative to the image\n                handler = new TextFileOutputHandler();\n            } else if (arg.equalsIgnoreCase(\"--markdown\")) {\n                // If \"--markdown\" is specified, write a summary table in markdown format to standard out\n                handler = new MarkdownTableOutputHandler();\n            } else if (arg.equalsIgnoreCase(\"--unknown\")) {\n                // If \"--unknown\" is specified, write CSV tallying unknown tag counts\n                handler = new UnknownTagHandler();\n            } else if (arg.equalsIgnoreCase(\"--log-file\")) {\n                if (i == args.length - 1) {\n                    printUsage();\n                    System.exit(1);\n                }\n                log = new PrintStream(new FileOutputStream(args[++i], false), true);\n            } else {\n                // Treat this argument as a directory\n                directories.add(arg);\n            }\n        }\n\n        if (directories.isEmpty()) {\n            System.err.println(\"Expects one or more directories as arguments.\");\n            printUsage();\n            System.exit(1);\n        }\n\n        if (handler == null) {\n            handler = new BasicFileHandler();\n        }\n\n        long start = System.nanoTime();\n\n        for (String directory : directories) {\n            processDirectory(new File(directory), handler, \"\", log);\n        }\n\n        handler.onScanCompleted(log);\n\n        System.out.println(String.format(\"Completed in %d ms\", (System.nanoTime() - start) / 1000000));\n\n        if (log != System.out) {\n            log.close();\n        }\n    }\n\n    private static void printUsage()\n    {\n        System.out.println(\"Usage:\");\n        System.out.println();\n        System.out.println(\"  java com.drew.tools.ProcessAllImagesInFolderUtility [--text|--markdown|--unknown] [--log-file <file-name>]\");\n    }\n\n    private static void processDirectory(@NotNull File path, @NotNull FileHandler handler, @NotNull String relativePath, PrintStream log)\n    {\n        handler.onStartingDirectory(path);\n\n        String[] pathItems = path.list();\n\n        if (pathItems == null) {\n            return;\n        }\n\n        // Order alphabetically so that output is stable across invocations\n        Arrays.sort(pathItems);\n\n        for (String pathItem : pathItems) {\n            File file = new File(path, pathItem);\n\n            if (file.isDirectory()) {\n                processDirectory(file, handler, relativePath.length() == 0 ? pathItem : relativePath + \"/\" + pathItem, log);\n            } else if (handler.shouldProcess(file)) {\n\n                handler.onBeforeExtraction(file, log, relativePath);\n\n                // Read metadata\n                final Metadata metadata;\n                try {\n                    metadata = ImageMetadataReader.readMetadata(file);\n                } catch (Throwable t) {\n                    handler.onExtractionError(file, t, log);\n                    continue;\n                }\n\n                handler.onExtractionSuccess(file, metadata, relativePath, log);\n            }\n        }\n    }\n\n    interface FileHandler\n    {\n        /** Called when the scan is about to start processing files in directory <code>path</code>. */\n        void onStartingDirectory(@NotNull File directoryPath);\n\n        /** Called to determine whether the implementation should process <code>filePath</code>. */\n        boolean shouldProcess(@NotNull File file);\n\n        /** Called before extraction is performed on <code>filePath</code>. */\n        void onBeforeExtraction(@NotNull File file, @NotNull PrintStream log, @NotNull String relativePath);\n\n        /** Called when extraction on <code>filePath</code> completed without an exception. */\n        void onExtractionSuccess(@NotNull File file, @NotNull Metadata metadata, @NotNull String relativePath, @NotNull PrintStream log);\n\n        /** Called when extraction on <code>filePath</code> resulted in an exception. */\n        void onExtractionError(@NotNull File file, @NotNull Throwable throwable, @NotNull PrintStream log);\n\n        /** Called when all files have been processed. */\n        void onScanCompleted(@NotNull PrintStream log);\n    }\n\n    abstract static class FileHandlerBase implements FileHandler\n    {\n        // TODO obtain these from FileType enum directly\n        private final Set<String> _supportedExtensions = new HashSet<String>(\n            Arrays.asList(\n                \"3fr\",\n                \"3g2\",\n                \"3gp\",\n                \"ai\",\n                \"arw\",\n                \"avi\",\n                \"avif\",\n                \"bmp\",\n                \"cam\",\n                \"cr2\",\n                \"cr3\",\n                \"crw\",\n                \"dcr\",\n                \"dng\",\n                \"eps\",\n                \"fuzzed\",\n                \"gif\",\n                \"gpr\",\n                \"heic\",\n                \"heif\",\n                \"ico\",\n                \"j2c\",\n                \"jp2\",\n                \"jpeg\",\n                \"jpf\",\n                \"jpg\",\n                \"jpm\",\n                \"jxr\",\n                \"kdc\",\n                \"m2ts\",\n                \"m2v\",\n                \"m4a\",\n                \"m4v\",\n                \"mj2\",\n                \"mov\",\n                \"mp3\",\n                \"mp4\",\n                \"mpg\",\n                \"mts\",\n                \"nef\",\n                \"orf\",\n                \"pbm\",\n                \"pcx\",\n                \"pef\",\n                \"pgm\",\n                \"png\",\n                \"pnm\",\n                \"ppm\",\n                \"psd\",\n                \"raf\",\n                \"rw2\",\n                \"rwl\",\n                \"srw\",\n                \"tif\",\n                \"tiff\",\n                \"wav\",\n                \"webp\",\n                \"x3f\"));\n\n        private int _processedFileCount = 0;\n        private int _exceptionCount = 0;\n        private int _errorCount = 0;\n        private long _processedByteCount = 0;\n\n        public void onStartingDirectory(@NotNull File directoryPath)\n        {}\n\n        public boolean shouldProcess(@NotNull File file)\n        {\n            String extension = getExtension(file);\n            return extension != null && _supportedExtensions.contains(extension.toLowerCase());\n        }\n\n        public void onBeforeExtraction(@NotNull File file, @NotNull PrintStream log, @NotNull String relativePath)\n        {\n            _processedFileCount++;\n            _processedByteCount += file.length();\n        }\n\n        public void onExtractionError(@NotNull File file, @NotNull Throwable throwable, @NotNull PrintStream log)\n        {\n            _exceptionCount++;\n            log.printf(\"\\t[%s] %s\\n\", throwable.getClass().getName(), throwable.getMessage());\n        }\n\n        public void onExtractionSuccess(@NotNull File file, @NotNull Metadata metadata, @NotNull String relativePath, @NotNull PrintStream log)\n        {\n            if (metadata.hasErrors()) {\n                log.print(file);\n                log.print('\\n');\n                for (Directory directory : metadata.getDirectories()) {\n                    if (!directory.hasErrors())\n                        continue;\n                    for (String error : directory.getErrors()) {\n                        log.printf(\"\\t[%s] %s\\n\", directory.getName(), error);\n                        _errorCount++;\n                    }\n                }\n            }\n        }\n\n        public void onScanCompleted(@NotNull PrintStream log)\n        {\n            if (_processedFileCount > 0) {\n                log.print(String.format(\n                    \"Processed %,d files (%,d bytes) with %,d exceptions and %,d file errors\\n\",\n                    _processedFileCount, _processedByteCount, _exceptionCount, _errorCount\n                ));\n            }\n        }\n\n        @Nullable\n        protected String getExtension(@NotNull File file)\n        {\n            String fileName = file.getName();\n            int i = fileName.lastIndexOf('.');\n            if (i == -1)\n                return null;\n            if (i == fileName.length() - 1)\n                return null;\n            return fileName.substring(i + 1);\n        }\n    }\n\n    /**\n     * Writes a text file containing the extracted metadata for each input file.\n     */\n    static class TextFileOutputHandler extends FileHandlerBase\n    {\n        /** Standardise line ending so that generated files can be more easily diffed. */\n        private static final String NEW_LINE = \"\\n\";\n\n        @Override\n        public void onStartingDirectory(@NotNull File directoryPath)\n        {\n            super.onStartingDirectory(directoryPath);\n\n            // Delete any existing 'metadata' folder\n            File metadataDirectory = new File(directoryPath + \"/metadata/java\");\n            if (metadataDirectory.exists())\n                deleteRecursively(metadataDirectory);\n        }\n\n        private static void deleteRecursively(@NotNull File directory)\n        {\n            if (!directory.isDirectory())\n                throw new IllegalArgumentException(\"Must be a directory.\");\n\n            if (directory.exists()) {\n                String[] list = directory.list();\n                if (list != null) {\n                    for (String item : list) {\n                        File file = new File(item);\n                        if (file.isDirectory())\n                            deleteRecursively(file);\n                        else\n                            file.delete();\n                    }\n                }\n            }\n\n            directory.delete();\n        }\n\n        @Override\n        public void onBeforeExtraction(@NotNull File file, @NotNull PrintStream log, @NotNull String relativePath)\n        {\n            super.onBeforeExtraction(file, log, relativePath);\n            log.print(file.getAbsoluteFile());\n            log.print(NEW_LINE);\n        }\n\n        @Override\n        public void onExtractionSuccess(@NotNull File file, @NotNull Metadata metadata, @NotNull String relativePath, @NotNull PrintStream log)\n        {\n            super.onExtractionSuccess(file, metadata, relativePath, log);\n\n            try {\n                PrintWriter writer = null;\n                try\n                {\n                    writer = openWriter(file);\n\n                    // Write any errors\n                    if (metadata.hasErrors()) {\n                        for (Directory directory : metadata.getDirectories()) {\n                            if (!directory.hasErrors())\n                                continue;\n                            for (String error : directory.getErrors())\n                                writer.format(\"[ERROR: %s] %s%s\", directory.getName(), error, NEW_LINE);\n                        }\n                        writer.write(NEW_LINE);\n                    }\n\n                    // Write tag values for each directory\n                    for (Directory directory : metadata.getDirectories()) {\n                        String directoryName = directory.getName();\n                        // Write the directory's tags\n                        for (Tag tag : directory.getTags()) {\n                            String tagName = tag.getTagName();\n                            String description;\n                            try {\n                                description = tag.getDescription();\n                            } catch (Exception ex) {\n                                description = \"ERROR: \" + ex.getMessage();\n                            }\n                            if (description == null)\n                                description = \"\";\n                            // Skip the file write-time as this changes based on the time at which the regression test image repository was cloned\n                            if (directory instanceof FileSystemDirectory && tag.getTagType() == FileSystemDirectory.TAG_FILE_MODIFIED_DATE)\n                                description = \"<omitted for regression testing as checkout dependent>\";\n                            writer.format(\"[%s - %s] %s = %s%s\", directoryName, tag.getTagTypeHex(), tagName, description, NEW_LINE);\n                        }\n                        if (directory.getTagCount() != 0)\n                            writer.write(NEW_LINE);\n                        // Special handling for XMP directory data\n                        if (directory instanceof XmpDirectory) {\n                            boolean wrote = false;\n                            XmpDirectory xmpDirectory = (XmpDirectory)directory;\n                            XMPMeta xmpMeta = xmpDirectory.getXMPMeta();\n                            try {\n                                IteratorOptions options = new IteratorOptions().setJustLeafnodes(true);\n                                XMPIterator iterator = xmpMeta.iterator(options);\n                                while (iterator.hasNext()) {\n                                    XMPPropertyInfo prop = (XMPPropertyInfo)iterator.next();\n                                    String ns = prop.getNamespace();\n                                    String path = prop.getPath();\n                                    String value = prop.getValue();\n\n                                    if (path == null)\n                                        continue;\n                                    if (ns == null)\n                                        ns = \"\";\n\n                                    final int MAX_XMP_VALUE_LENGTH = 512;\n                                    if (value == null)\n                                        value = \"\";\n                                    else if (value.length() > MAX_XMP_VALUE_LENGTH)\n                                        value = String.format(\"%s <truncated from %d characters>\", value.substring(0, MAX_XMP_VALUE_LENGTH), value.length());\n\n                                    writer.format(\"[XMPMeta - %s] %s = %s%s\", ns, path, value, NEW_LINE);\n                                    wrote = true;\n                                }\n                            } catch (XMPException e) {\n                                e.printStackTrace();\n                            }\n                            if (wrote)\n                                writer.write(NEW_LINE);\n                        }\n                    }\n\n                    // Write file structure\n                    writeHierarchyLevel(metadata, writer, null, 0);\n\n                    writer.write(NEW_LINE);\n                } finally {\n                    closeWriter(writer);\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n\n        private static void writeHierarchyLevel(@NotNull Metadata metadata, @NotNull PrintWriter writer, @Nullable Directory parent, int level)\n        {\n            final int indent = 4;\n\n            for (Directory child : metadata.getDirectories()) {\n                if (parent == null) {\n                    if (child.getParent() != null)\n                        continue;\n                } else if (!parent.equals(child.getParent())) {\n                    continue;\n                }\n\n                for (int i = 0; i < level*indent; i++) {\n                    writer.write(' ');\n                }\n                writer.write(\"- \");\n                writer.write(child.getName());\n                writer.write(NEW_LINE);\n                writeHierarchyLevel(metadata, writer, child, level + 1);\n            }\n        }\n\n        @Override\n        public void onExtractionError(@NotNull File file, @NotNull Throwable throwable, @NotNull PrintStream log)\n        {\n            super.onExtractionError(file, throwable, log);\n\n            try {\n                PrintWriter writer = null;\n                try {\n                    writer = openWriter(file);\n                    writer.write(\"EXCEPTION: \" + throwable.getMessage() + NEW_LINE);\n                    writer.write(NEW_LINE);\n                } finally {\n                    closeWriter(writer);\n                }\n            } catch (IOException e) {\n                log.printf(\"IO exception writing metadata file: %s%s\", e.getMessage(), NEW_LINE);\n            }\n        }\n\n        @NotNull\n        private static PrintWriter openWriter(@NotNull File file) throws IOException\n        {\n            // Create the output directory if it doesn't exist\n            File metadataDir = new File(String.format(\"%s/metadata\", file.getParent()));\n            if (!metadataDir.exists())\n                metadataDir.mkdir();\n\n            File javaDir = new File(String.format(\"%s/metadata/java\", file.getParent()));\n            if (!javaDir.exists())\n                javaDir.mkdir();\n\n            String outputPath = String.format(\"%s/metadata/java/%s.txt\", file.getParent(), file.getName());\n            Writer writer = new OutputStreamWriter(\n                new FileOutputStream(outputPath),\n                \"UTF-8\"\n            );\n            writer.write(\"FILE: \" + file.getName() + NEW_LINE);\n\n            // Detect file type\n            BufferedInputStream stream = null;\n            try {\n                stream = new BufferedInputStream(new FileInputStream(file));\n                FileType fileType = FileTypeDetector.detectFileType(stream, file.getName());\n                writer.write(String.format(\"TYPE: %s\" + NEW_LINE, fileType.toString().toUpperCase()));\n                writer.write(NEW_LINE);\n            } finally {\n                if (stream != null) {\n                    stream.close();\n                }\n            }\n\n            return new PrintWriter(writer);\n        }\n\n        private static void closeWriter(@Nullable Writer writer) throws IOException\n        {\n            if (writer != null) {\n                writer.write(\"Generated using metadata-extractor\" + NEW_LINE);\n                writer.write(\"https://drewnoakes.com/code/exif/\" + NEW_LINE);\n                writer.flush();\n                writer.close();\n            }\n        }\n    }\n\n    /**\n     * Creates a table describing sample images using Wiki markdown.\n     */\n    static class MarkdownTableOutputHandler extends FileHandlerBase\n    {\n        private final Map<String, String> _extensionEquivalence = new HashMap<String, String>();\n        private final Map<String, List<Row>> _rowListByExtension = new HashMap<String, List<Row>>();\n\n        static class Row\n        {\n            final File file;\n            final Metadata metadata;\n            @NotNull final String relativePath;\n            @Nullable private String manufacturer;\n            @Nullable private String model;\n            @Nullable private String exifVersion;\n            @Nullable private String thumbnail;\n            @Nullable private String makernote;\n\n            Row(@NotNull File file, @NotNull Metadata metadata, @NotNull String relativePath)\n            {\n                this.file = file;\n                this.metadata = metadata;\n                this.relativePath = relativePath;\n\n                ExifIFD0Directory ifd0Dir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);\n                ExifSubIFDDirectory subIfdDir = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);\n                ExifThumbnailDirectory thumbDir = metadata.getFirstDirectoryOfType(ExifThumbnailDirectory.class);\n                if (ifd0Dir != null) {\n                    manufacturer = ifd0Dir.getDescription(ExifIFD0Directory.TAG_MAKE);\n                    model = ifd0Dir.getDescription(ExifIFD0Directory.TAG_MODEL);\n                }\n                boolean hasMakernoteData = false;\n                if (subIfdDir != null) {\n                    exifVersion = subIfdDir.getDescription(ExifSubIFDDirectory.TAG_EXIF_VERSION);\n                    hasMakernoteData = subIfdDir.containsTag(ExifSubIFDDirectory.TAG_MAKERNOTE);\n                }\n                if (thumbDir != null) {\n                    Integer width = thumbDir.getInteger(ExifThumbnailDirectory.TAG_IMAGE_WIDTH);\n                    Integer height = thumbDir.getInteger(ExifThumbnailDirectory.TAG_IMAGE_HEIGHT);\n                    thumbnail = width != null && height != null\n                        ? String.format(\"Yes (%s x %s)\", width, height)\n                        : \"Yes\";\n                }\n                for (Directory directory : metadata.getDirectories()) {\n                    if (directory.getClass().getName().contains(\"Makernote\")) {\n                        makernote = directory.getName().replace(\"Makernote\", \"\").trim();\n                        break;\n                    }\n                }\n                if (makernote == null) {\n                    makernote = hasMakernoteData ? \"(Unknown)\" : \"N/A\";\n                }\n            }\n        }\n\n        public MarkdownTableOutputHandler()\n        {\n            _extensionEquivalence.put(\"jpeg\", \"jpg\");\n        }\n\n        @Override\n        public void onExtractionSuccess(@NotNull File file, @NotNull Metadata metadata, @NotNull String relativePath, @NotNull PrintStream log)\n        {\n            super.onExtractionSuccess(file, metadata, relativePath, log);\n\n            String extension = getExtension(file);\n\n            if (extension == null) {\n                return;\n            }\n\n            // Sanitise the extension\n            extension = extension.toLowerCase();\n            if (_extensionEquivalence.containsKey(extension))\n                extension = _extensionEquivalence.get(extension);\n\n            List<Row> list = _rowListByExtension.get(extension);\n            if (list == null) {\n                list = new ArrayList<Row>();\n                _rowListByExtension.put(extension, list);\n            }\n            list.add(new Row(file, metadata, relativePath));\n        }\n\n        @Override\n        public void onScanCompleted(@NotNull PrintStream log)\n        {\n            super.onScanCompleted(log);\n\n            OutputStream outputStream = null;\n            PrintStream stream = null;\n            try {\n                outputStream = new FileOutputStream(\"../wiki/ImageDatabaseSummary.md\", false);\n                stream = new PrintStream(outputStream, false);\n                writeOutput(stream);\n                stream.flush();\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                if (stream != null)\n                    stream.close();\n                if (outputStream != null)\n                    try {\n                        outputStream.close();\n                    } catch (IOException e) {\n                        e.printStackTrace();\n                    }\n            }\n        }\n\n        private void writeOutput(@NotNull PrintStream stream) throws IOException\n        {\n            Writer writer = new OutputStreamWriter(stream);\n            writer.write(\"# Image Database Summary\\n\\n\");\n\n            for (Map.Entry<String, List<Row>> entry : _rowListByExtension.entrySet()) {\n                String extension = entry.getKey();\n                writer.write(\"## \" + extension.toUpperCase() + \" Files\\n\\n\");\n\n                writer.write(\"File|Manufacturer|Model|Dir Count|Exif?|Makernote|Thumbnail|All Data\\n\");\n                writer.write(\"----|------------|-----|---------|-----|---------|---------|--------\\n\");\n\n                List<Row> rows = entry.getValue();\n\n                // Order by manufacturer, then model\n                Collections.sort(rows, new Comparator<Row>() {\n                    public int compare(Row o1, Row o2)\n                    {\n                        int c1 = StringUtil.compare(o1.manufacturer, o2.manufacturer);\n                        return c1 != 0 ? c1 : StringUtil.compare(o1.model, o2.model);\n                    }\n                });\n\n                for (Row row : rows) {\n                    writer.write(String.format(\"[%s](https://raw.githubusercontent.com/drewnoakes/metadata-extractor-images/master/%s/%s)|%s|%s|%d|%s|%s|%s|[metadata](https://raw.githubusercontent.com/drewnoakes/metadata-extractor-images/master/%s/metadata/%s.txt)\\n\",\n                            row.file.getName(),\n                            row.relativePath,\n                            StringUtil.urlEncode(row.file.getName()),\n                            row.manufacturer == null ? \"\" : row.manufacturer,\n                            row.model == null ? \"\" : row.model,\n                            row.metadata.getDirectoryCount(),\n                            row.exifVersion == null ? \"\" : row.exifVersion,\n                            row.makernote == null ? \"\" : row.makernote,\n                            row.thumbnail == null ? \"\" : row.thumbnail,\n                            row.relativePath,\n                            StringUtil.urlEncode(row.file.getName()).toLowerCase()\n                    ));\n                }\n\n                writer.write('\\n');\n            }\n            writer.flush();\n        }\n    }\n\n    /**\n     * Keeps track of unknown tags.\n     */\n    static class UnknownTagHandler extends FileHandlerBase\n    {\n        private HashMap<String, HashMap<Integer, Integer>> _occurrenceCountByTagByDirectory = new HashMap<String, HashMap<Integer, Integer>>();\n\n        @Override\n        public void onExtractionSuccess(@NotNull File file, @NotNull Metadata metadata, @NotNull String relativePath, @NotNull PrintStream log)\n        {\n            super.onExtractionSuccess(file, metadata, relativePath, log);\n\n            for (Directory directory : metadata.getDirectories()) {\n                for (Tag tag : directory.getTags()) {\n\n                    // Only interested in unknown tags (those without names)\n                    if (tag.hasTagName())\n                        continue;\n\n                    HashMap<Integer, Integer> occurrenceCountByTag = _occurrenceCountByTagByDirectory.get(directory.getName());\n                    if (occurrenceCountByTag == null) {\n                        occurrenceCountByTag = new HashMap<Integer, Integer>();\n                        _occurrenceCountByTagByDirectory.put(directory.getName(), occurrenceCountByTag);\n                    }\n\n                    Integer count = occurrenceCountByTag.get(tag.getTagType());\n                    if (count == null) {\n                        count = 0;\n                        occurrenceCountByTag.put(tag.getTagType(), 0);\n                    }\n\n                    occurrenceCountByTag.put(tag.getTagType(), count + 1);\n                }\n            }\n        }\n\n        @Override\n        public void onScanCompleted(@NotNull PrintStream log)\n        {\n            super.onScanCompleted(log);\n\n            for (Map.Entry<String, HashMap<Integer, Integer>> pair1 : _occurrenceCountByTagByDirectory.entrySet()) {\n                String directoryName = pair1.getKey();\n                List<Map.Entry<Integer, Integer>> counts = new ArrayList<Map.Entry<Integer, Integer>>(pair1.getValue().entrySet());\n                Collections.sort(counts, new Comparator<Map.Entry<Integer, Integer>>()\n                {\n                    public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2)\n                    {\n                        return o2.getValue().compareTo(o1.getValue());\n                    }\n                });\n                for (Map.Entry<Integer, Integer> pair2 : counts) {\n                    Integer tagType = pair2.getKey();\n                    Integer count = pair2.getValue();\n                    log.format(\"%s, 0x%04X, %d\\n\", directoryName, tagType, count);\n                }\n            }\n        }\n    }\n\n    /**\n     * Does nothing with the output except enumerate it in memory and format descriptions. This is useful in order to\n     * flush out any potential exceptions raised during the formatting of extracted value descriptions.\n     */\n    static class BasicFileHandler extends FileHandlerBase\n    {\n        @Override\n        public void onExtractionSuccess(@NotNull File file, @NotNull Metadata metadata, @NotNull String relativePath, @NotNull PrintStream log)\n        {\n            super.onExtractionSuccess(file, metadata, relativePath, log);\n\n            // Iterate through all values, calling toString to flush out any formatting exceptions\n            for (Directory directory : metadata.getDirectories()) {\n                directory.getName();\n                for (Tag tag : directory.getTags()) {\n                    tag.getTagName();\n                    tag.getDescription();\n                }\n            }\n        }\n    }\n}",
      "type": "string",
      "required": true
    }
  },
  "steps": [
    {
      "id": "step_1_code_structure_analyzer",
      "point_code": "",
      "result": [],
      "api_code": "code_structure_analyzer",
      "backend_type": "prompt_api",
      "status": "PENDING",
      "name": "代码结构智能解析",
      "action": "run",
      "params": {
        "analysis_points": "{{@input.analysis_points}}",
        "code": "{{@input.code}}"
      }
    },
    {
      "id": "step_2_uml_prompt_generator",
      "point_code": "",
      "result": [],
      "backend_type": "prompt_api",
      "api_code": "uml_prompt_generator",
      "status": "PENDING",
      "name": "UML图PlantUML提示词生成",
      "action": "run",
      "params": {
        "structured_code_segments": "{{@step_1_code_structure_analyzer.result.data.structured_code_segments}}",
        "language_type": "{{@step_1_code_structure_analyzer.result.data.language_type}}",
        "structured_intent": "{{@step_1_code_structure_analyzer.result.data.structured_intent}}",
        "extended_analysis_dimensions": "{{@step_1_code_structure_analyzer.result.data.extended_analysis_dimensions}}"
      },
      "depends_on": [
        "step_1_code_structure_analyzer"
      ]
    },
    {
      "id": "step_3_code_text_analyzer",
      "point_code": "",
      "result": [],
      "backend_type": "prompt_api",
      "api_code": "code_text_analyzer",
      "status": "PENDING",
      "name": "代码文本结构化深度分析",
      "action": "run",
      "params": {
        "structured_code_segments": "{{@step_1_code_structure_analyzer.result.data.structured_code_segments}}",
        "language_type": "{{@step_1_code_structure_analyzer.result.data.language_type}}",
        "structured_intent": "{{@step_1_code_structure_analyzer.result.data.structured_intent}}",
        "extended_analysis_dimensions": "{{@step_1_code_structure_analyzer.result.data.extended_analysis_dimensions}}"
      },
      "depends_on": [
        "step_1_code_structure_analyzer"
      ]
    },
    {
      "id": "step_4_generate_uml_image",
      "point_code": "1",
      "result": [],
      "backend_type": "sys_api",
      "api_code": "3373",
      "status": "PENDING",
      "name": "启动UML图像生成任务",
      "action": "run",
      "params": {
        "prompt": "{{@step_2_uml_prompt_generator.result.data.plantuml_prompt}}",
        "aspect_ratio": "1:1"
      },
      "depends_on": [
        "step_2_uml_prompt_generator"
      ]
    },
    {
      "id": "step_5_poll_uml_image_status",
      "point_code": "",
      "result": [],
      "backend_type": "sys_api",
      "api_code": "polling",
      "status": "PENDING",
      "name": "轮询UML图像生成状态",
      "action": "run",
      "params": {
        "result_url": "{{@step_4_generate_uml_image.result.data.result_url}}"
      },
      "depends_on": [
        "step_4_generate_uml_image"
      ],
      "polling": {
        "interval_seconds": 5,
        "timeout_seconds": 600,
        "success_condition": "{{@step_5_poll_uml_image_status.result.data.task_status}} === 'SUCCEEDED'"
      }
    },
    {
      "id": "step_6_analysis_report_merger",
      "point_code": "",
      "result": [],
      "backend_type": "prompt_api",
      "api_code": "analysis_report_merger",
      "status": "PENDING",
      "name": "UML图与文本分析智能融合",
      "action": "run",
      "params": {
        "architecture_summary": "{{@step_3_code_text_analyzer.result.data.architecture_summary}}",
        "key_findings": "{{@step_3_code_text_analyzer.result.data.key_findings}}",
        "uml_image_url": "{{@step_5_poll_uml_image_status.result.data.result_url}}",
        "uml_focus_description": "{{@step_2_uml_prompt_generator.result.data.uml_focus_description}}",
        "analysis_report": "{{@step_3_code_text_analyzer.result.data.analysis_report}}"
      },
      "depends_on": [
        "step_5_poll_uml_image_status",
        "step_3_code_text_analyzer"
      ]
    }
  ],
  "output": {
    "final_integrated_result": {
      "field_name": "final_integrated_result",
      "description": "融合UML图与文本分析的最终完整结构化交付物",
      "value": "{{@step_6_analysis_report_merger.result.data.final_integrated_result}}",
      "type": "object"
    },
    "uml_image_url": {
      "field_name": "uml_image_url",
      "description": "生成的UML图像URL地址",
      "value": "{{@step_5_poll_uml_image_status.result.data.result_url}}",
      "type": "string"
    },
    "consistency_check": {
      "field_name": "consistency_check",
      "description": "UML图与文本分析的一致性校验结果",
      "value": "{{@step_6_analysis_report_merger.result.data.final_integrated_result.consistency_check}}",
      "type": "object"
    }
  }
}
```

介绍

在线运行

Skill与API