sxwz2.0/src/main/java/com/kexue/skills/common/util/SkillZipParser.java
wangzhiwei 59a44f9c53 feat(skills): 新增技能包解析和标题获取功能
- 添加了CmsContentController的getTitle接口用于获取内容标题
- 实现了CmsContentService的getTitle方法支持内容标题查询
- 新增SkillZipParser工具类支持ZIP和RAR格式技能包解析
- 集成snakeyaml和sevenzipjbinding依赖处理YAML配置和压缩文件
- 实现SkillGenService的uploadSkillV2方法支持本地技能包上传
- 在SysUserController中增强token验证逻辑确保登录状态检查
- 支持从技能包中提取MD文件内容并自动生成YAML描述结构
2026-03-17 18:06:03 +08:00

843 lines
34 KiB
Java

package com.kexue.skills.common.util;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import net.sf.sevenzipjbinding.*;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
/**
* 技能包解析工具类
* 用于解析skill zip包并生成符合要求的yaml描述
*/
public class SkillZipParser {
/**
* 提取压缩包中的skillMdText
* @param filePath 压缩文件路径
* @param defaultSkillName 默认技能名称
* @return 包含skillMdText的Map
* @throws IOException 解析过程中的IO异常
* @throws SevenZipException 解析RAR文件时的异常
*/
public static Map<String, Object> extractSkillMdText(String filePath, String defaultSkillName) throws IOException, SevenZipException {
// 检查文件扩展名
String fileExtension = "";
if (filePath.contains(".")) {
int dotIndex = filePath.lastIndexOf(".");
fileExtension = filePath.substring(dotIndex).toLowerCase();
}
// 根据文件类型选择不同的解析方法
if (".rar".equals(fileExtension)) {
return extractSkillInfoFromRar(filePath, defaultSkillName);
} else {
// 默认为zip文件
try (ZipFile zipFile = new ZipFile(filePath, StandardCharsets.UTF_8)) {
return extractSkillInfo(zipFile, defaultSkillName);
}
}
}
/**
* 生成技能包的yaml描述
* @param filePath 压缩文件路径
* @param author 作者
* @param skillName 技能名称
* @param skillDescription 技能描述
* @param skillTags 技能标签
* @return 生成的yaml描述
* @throws IOException 解析过程中的IO异常
* @throws SevenZipException 解析RAR文件时的异常
*/
public static String generateYamlFromSkillInfo(String filePath, String author, String skillName, String skillDescription, List<String> skillTags) throws IOException, SevenZipException {
// 检查文件扩展名
String fileExtension = "";
if (filePath.contains(".")) {
int dotIndex = filePath.lastIndexOf(".");
fileExtension = filePath.substring(dotIndex).toLowerCase();
}
// 根据文件类型选择不同的解析方法
Map<String, Object> skillStructure;
if (".rar".equals(fileExtension)) {
skillStructure = generateSkillStructureFromRar(filePath, skillName, skillDescription, skillTags, author);
} else {
// 默认为zip文件
try (ZipFile zipFile = new ZipFile(filePath, StandardCharsets.UTF_8)) {
skillStructure = generateSkillStructure(zipFile, skillName, skillDescription, skillTags, author);
}
}
// 生成yaml
return generateYaml(skillStructure);
}
/**
* 解析skill zip包并生成yaml描述
* @param zipFilePath zip文件路径
* @param author 作者
* @param defaultSkillName 默认技能名称
* @return 包含技能信息和yaml描述的Map
* @throws IOException 解析过程中的IO异常
* @throws SevenZipException 解析RAR文件时的异常
*/
public static Map<String, Object> parseSkillZip(String zipFilePath, String author, String defaultSkillName) throws IOException, SevenZipException {
// 检查文件扩展名
String fileExtension = "";
if (zipFilePath.contains(".")) {
int dotIndex = zipFilePath.lastIndexOf(".");
fileExtension = zipFilePath.substring(dotIndex).toLowerCase();
}
// 根据文件类型选择不同的解析方法
if (".rar".equals(fileExtension)) {
return parseRarFile(zipFilePath, author, defaultSkillName);
} else {
// 默认为zip文件
try (ZipFile zipFile = new ZipFile(zipFilePath, StandardCharsets.UTF_8)) {
// 从压缩文件中提取技能信息
Map<String, Object> skillInfo = extractSkillInfo(zipFile, defaultSkillName);
String skillName = (String) skillInfo.get("name");
String skillDescription = (String) skillInfo.get("description");
List<String> skillTags = (List<String>) skillInfo.get("tags");
// 生成技能包结构
Map<String, Object> skillStructure = generateSkillStructure(zipFile, skillName, skillDescription, skillTags, author);
// 生成yaml
String yamlContent = generateYaml(skillStructure);
// 构建返回结果
Map<String, Object> result = new LinkedHashMap<>();
result.put("name", skillName);
result.put("description", skillDescription);
result.put("tags", skillTags);
result.put("yamlContent", yamlContent);
// 包含md文件的完整内容
if (skillInfo.containsKey("skillMdText")) {
result.put("skillMdText", skillInfo.get("skillMdText"));
}
return result;
}
}
}
/**
* 解析rar文件
* @param rarFilePath rar文件路径
* @param author 作者
* @param defaultSkillName 默认技能名称
* @return 技能信息
* @throws IOException 解析过程中的IO异常
* @throws SevenZipException 解析RAR文件时的异常
*/
private static Map<String, Object> parseRarFile(String rarFilePath, String author, String defaultSkillName) throws IOException, SevenZipException {
// 从rar文件中提取技能信息
Map<String, Object> skillInfo = extractSkillInfoFromRar(rarFilePath, defaultSkillName);
String skillName = (String) skillInfo.get("name");
String skillDescription = (String) skillInfo.get("description");
List<String> skillTags = (List<String>) skillInfo.get("tags");
// 生成技能包结构
Map<String, Object> skillStructure = generateSkillStructureFromRar(rarFilePath, skillName, skillDescription, skillTags, author);
// 生成yaml
String yamlContent = generateYaml(skillStructure);
// 构建返回结果
Map<String, Object> result = new LinkedHashMap<>();
result.put("name", skillName);
result.put("description", skillDescription);
result.put("tags", skillTags);
result.put("yamlContent", yamlContent);
// 包含md文件的完整内容
if (skillInfo.containsKey("skillMdText")) {
result.put("skillMdText", skillInfo.get("skillMdText"));
}
return result;
}
/**
* 从rar文件中提取技能信息
* @param rarFilePath rar文件路径
* @param defaultSkillName 默认技能名称
* @return 技能信息
* @throws IOException 解析过程中的IO异常
* @throws Exception 解析过程中的异常
*/
private static Map<String, Object> extractSkillInfoFromRar(String rarFilePath, String defaultSkillName) throws IOException, SevenZipException {
Map<String, Object> skillInfo = new LinkedHashMap<>();
boolean foundMdFile = false;
// 验证文件是否存在
File rarFile = new File(rarFilePath);
if (!rarFile.exists()) {
throw new FileNotFoundException("RAR file not found: " + rarFilePath);
}
if (!rarFile.canRead()) {
throw new IOException("Cannot read RAR file: " + rarFilePath);
}
try (RandomAccessFile randomAccessFile = new RandomAccessFile(rarFile, "r");
IInArchive archive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile))) {
// 使用简单接口
ISimpleInArchive simpleInArchive = archive.getSimpleInterface();
// 遍历所有文件条目
for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
// 检查是否是根目录下的md文件
String path = item.getPath();
if (!item.isFolder() && path.endsWith(".md") && !path.contains("/") && !path.contains("\\")) {
// 读取文件内容
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
item.extractSlow(data -> {
try {
outputStream.write(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
return data.length;
});
String content = outputStream.toString(StandardCharsets.UTF_8);
// 存储md文件的完整内容
skillInfo.put("skillMdText", content);
// 解析md内容
parseSkillsMd(content, skillInfo);
foundMdFile = true;
break; // 找到一个md文件后就停止
}
}
}
// 如果没有找到 md 文件,使用默认值
if (!foundMdFile) {
skillInfo.put("name", defaultSkillName);
skillInfo.put("description", "Skill uploaded via uploadSkillV2");
skillInfo.put("tags", Arrays.asList("10001", "10002"));
} else {
// 确保 tags 不为 null
if (!skillInfo.containsKey("tags")) {
skillInfo.put("tags", Arrays.asList("10001"));
}
}
return skillInfo;
}
/**
* 从压缩文件中提取技能信息
* @param zipFile zip文件对象
* @param defaultSkillName 默认技能名称
* @return 技能信息
* @throws IOException 解析过程中的IO异常
*/
private static Map<String, Object> extractSkillInfo(ZipFile zipFile, String defaultSkillName) throws IOException {
Map<String, Object> skillInfo = new LinkedHashMap<>();
boolean foundMdFile = false;
// 尝试从zip根目录的md文件中提取信息
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
// 检查是否是根目录下的md文件
if (!entry.isDirectory() && entry.getName().endsWith(".md") && !entry.getName().contains("/")) {
try (InputStream inputStream = zipFile.getInputStream(entry);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
// 存储md文件的完整内容
skillInfo.put("skillMdText", content.toString());
// 解析md内容
parseSkillsMd(content.toString(), skillInfo);
foundMdFile = true;
break; // 找到一个md文件后就停止
}
}
}
// 如果没有找到 md 文件,使用默认值
if (!foundMdFile) {
skillInfo.put("name", defaultSkillName);
skillInfo.put("description", "Skill uploaded ");
skillInfo.put("tags", Arrays.asList("10001", "10002"));
} else {
// 确保 tags 不为 null
if (!skillInfo.containsKey("tags")) {
skillInfo.put("tags", Arrays.asList("10001"));
}
}
return skillInfo;
}
/**
* 解析skills.md文件内容
* @param content skills.md文件内容
* @param skillInfo 技能信息Map
*/
private static void parseSkillsMd(String content, Map<String, Object> skillInfo) {
// 解析技能名称
Pattern namePattern = Pattern.compile("#\s+(.*)");
Matcher nameMatcher = namePattern.matcher(content);
if (nameMatcher.find()) {
skillInfo.put("name", nameMatcher.group(1).trim());
}
// 解析技能描述
Pattern descPattern = Pattern.compile("##\s+Description\s+(.*?)(?=##|$)", Pattern.DOTALL);
Matcher descMatcher = descPattern.matcher(content);
if (descMatcher.find()) {
String description = descMatcher.group(1).trim();
skillInfo.put("description", description);
}
// 解析技能标签
Pattern tagPattern = Pattern.compile("##\s+Tags\s+(.*?)(?=##|$)", Pattern.DOTALL);
Matcher tagMatcher = tagPattern.matcher(content);
if (tagMatcher.find()) {
String tagsSection = tagMatcher.group(1);
Pattern tagItemPattern = Pattern.compile("-\s+(.*)");
Matcher tagItemMatcher = tagItemPattern.matcher(tagsSection);
List<String> tags = new ArrayList<>();
while (tagItemMatcher.find()) {
tags.add(tagItemMatcher.group(1).trim());
}
if (!tags.isEmpty()) {
skillInfo.put("tags", tags);
}
}
}
/**
* 从rar文件生成技能包结构
* @param rarFilePath rar文件路径
* @param skillName 技能包名称
* @param skillDescription 技能包描述
* @param skillTags 技能包标签
* @param author 作者
* @return 技能包结构
* @throws IOException 解析过程中的IO异常
* @throws Exception 解析过程中的异常
*/
private static Map<String, Object> generateSkillStructureFromRar(String rarFilePath, String skillName, String skillDescription, List<String> skillTags, String author) throws IOException, SevenZipException {
// 验证文件是否存在
File rarFile = new File(rarFilePath);
if (!rarFile.exists()) {
throw new FileNotFoundException("RAR file not found: " + rarFilePath);
}
if (!rarFile.canRead()) {
throw new IOException("Cannot read RAR file: " + rarFilePath);
}
Map<String, Object> skillStructure = new LinkedHashMap<>();
// 核心概要
skillStructure.put("name", skillName);
skillStructure.put("version", "1.0.0");
skillStructure.put("description", skillDescription);
skillStructure.put("author", author);
skillStructure.put("created", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
skillStructure.put("tags", skillTags);
// 核心节点 structure
Map<String, Object> structure = new LinkedHashMap<>();
structure.put("name", skillName);
structure.put("type", "directory");
structure.put("path", ".");
structure.put("format", "directory");
structure.put("description", skillDescription);
// 构建目录树结构
List<Map<String, Object>> children = new ArrayList<>();
Map<String, Map<String, Object>> directoryMap = new HashMap<>();
// 一次性读取所有条目并处理
try (RandomAccessFile randomAccessFile = new RandomAccessFile(rarFile, "r");
IInArchive archive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile))) {
// 使用简单接口
ISimpleInArchive simpleInArchive = archive.getSimpleInterface();
ISimpleInArchiveItem[] archiveItems = simpleInArchive.getArchiveItems();
// 首先处理所有目录
for (ISimpleInArchiveItem item : archiveItems) {
if (item.isFolder()) {
String path = item.getPath();
// 确保路径以/结尾,与 zip 处理一致
if (!path.endsWith("/")) {
path = path + "/";
}
String[] pathParts = path.split("/");
createDirectoryTree(children, directoryMap, pathParts, path);
}
}
// 然后处理所有文件
for (ISimpleInArchiveItem item : archiveItems) {
if (!item.isFolder()) {
String path = item.getPath();
String[] pathParts = path.split("/");
String fileName = pathParts[pathParts.length - 1];
// 读取文件内容
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
item.extractSlow(data -> {
try {
outputStream.write(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
return data.length;
});
String fileContent = outputStream.toString(StandardCharsets.UTF_8);
addFileToTreeFromRar(children, directoryMap, pathParts, fileName, fileContent, path);
}
}
}
// 确保包含skills.md文件
boolean hasSkillsMd = false;
for (Map<String, Object> child : children) {
if ("skills.md".equals(child.get("name")) && "file".equals(child.get("type"))) {
hasSkillsMd = true;
break;
}
}
if (!hasSkillsMd) {
Map<String, Object> skillsMdNode = createSkillsMdNode(skillName, skillDescription, skillTags);
children.add(skillsMdNode);
}
// 确保包含scripts目录
boolean hasScriptsDir = false;
for (Map<String, Object> child : children) {
if ("scripts".equals(child.get("name")) && "directory".equals(child.get("type"))) {
hasSkillsMd = true;
break;
}
}
if (!hasScriptsDir) {
Map<String, Object> scriptsDirNode = createScriptsDirNode();
children.add(scriptsDirNode);
}
structure.put("children", children);
skillStructure.put("structure", structure);
return skillStructure;
}
/**
* 将 rar 文件添加到目录树中
* @param children 子节点列表
* @param directoryMap 目录映射
* @param pathParts 路径部分
* @param item rar 文件项
* @param fileName 文件名
* @param fileContent 文件内容
* @param filePath 文件路径
* @throws IOException 读取文件内容时的 IO 异常
*/
private static void addFileToTreeFromRar(List<Map<String, Object>> children, Map<String, Map<String, Object>> directoryMap, String[] pathParts, String fileName, String fileContent, String filePath) throws IOException {
if (pathParts.length == 0) return;
// 构建目录路径
StringBuilder directoryPath = new StringBuilder();
for (int i = 0; i < pathParts.length - 1; i++) {
String part = pathParts[i];
if (!part.isEmpty()) {
directoryPath.append(part).append("/");
}
}
// 找到文件所在的目录节点
List<Map<String, Object>> targetChildren = children;
if (directoryPath.length() > 0) {
Map<String, Object> directoryNode = directoryMap.get(directoryPath.toString());
if (directoryNode != null) {
targetChildren = (List<Map<String, Object>>) directoryNode.get("children");
}
}
// 创建文件节点并添加到目标目录
Map<String, Object> fileNode = createFileNodeFromRar(fileName, fileContent, filePath);
targetChildren.add(fileNode);
}
/**
* 从文件信息创建文件节点
* @param fileName 文件名
* @param fileContent 文件内容
* @param filePath 文件路径
* @return 文件节点
* @throws IOException 读取文件内容时的 IO 异常
*/
private static Map<String, Object> createFileNodeFromRar(String fileName, String fileContent, String filePath) throws IOException {
Map<String, Object> fileNode = new LinkedHashMap<>();
fileNode.put("name", fileName);
fileNode.put("type", "file");
fileNode.put("path", filePath);
fileNode.put("format", getFileFormat(fileName));
fileNode.put("description", fileName + " file");
fileNode.put("content", fileContent);
return fileNode;
}
/**
* 生成技能包结构
* @param zipFile zip文件对象
* @param skillName 技能包名称
* @param skillDescription 技能包描述
* @param skillTags 技能包标签
* @param author 作者
* @return 技能包结构
* @throws IOException 解析过程中的IO异常
*/
private static Map<String, Object> generateSkillStructure(ZipFile zipFile, String skillName, String skillDescription, List<String> skillTags, String author) throws IOException {
Map<String, Object> skillStructure = new LinkedHashMap<>();
// 核心概要
skillStructure.put("name", skillName);
skillStructure.put("version", "1.0.0");
skillStructure.put("description", skillDescription);
skillStructure.put("author", author);
skillStructure.put("created", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
skillStructure.put("tags", skillTags);
// 核心节点structure
Map<String, Object> structure = new LinkedHashMap<>();
structure.put("name", skillName);
structure.put("type", "directory");
structure.put("path", ".");
structure.put("format", "directory");
structure.put("description", skillDescription);
// 构建目录树结构
List<Map<String, Object>> children = new ArrayList<>();
Map<String, Map<String, Object>> directoryMap = new HashMap<>();
// 首先处理所有目录
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.isDirectory()) {
String path = entry.getName();
String[] pathParts = path.split("/");
createDirectoryTree(children, directoryMap, pathParts, path);
}
}
// 然后处理所有文件
entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (!entry.isDirectory()) {
String path = entry.getName();
String[] pathParts = path.split("/");
addFileToTree(children, directoryMap, pathParts, entry, zipFile);
}
}
// 确保包含skills.md文件
boolean hasSkillsMd = false;
for (Map<String, Object> child : children) {
if ("skills.md".equals(child.get("name")) && "file".equals(child.get("type"))) {
hasSkillsMd = true;
break;
}
}
if (!hasSkillsMd) {
Map<String, Object> skillsMdNode = createSkillsMdNode(skillName, skillDescription, skillTags);
children.add(skillsMdNode);
}
// 确保包含scripts目录
boolean hasScriptsDir = false;
for (Map<String, Object> child : children) {
if ("scripts".equals(child.get("name")) && "directory".equals(child.get("type"))) {
hasScriptsDir = true;
break;
}
}
if (!hasScriptsDir) {
Map<String, Object> scriptsDirNode = createScriptsDirNode();
children.add(scriptsDirNode);
}
structure.put("children", children);
skillStructure.put("structure", structure);
return skillStructure;
}
/**
* 创建目录树结构
* @param children 子节点列表
* @param directoryMap 目录映射
* @param pathParts 路径部分
* @param fullPath 完整路径
*/
private static void createDirectoryTree(List<Map<String, Object>> children, Map<String, Map<String, Object>> directoryMap, String[] pathParts, String fullPath) {
List<Map<String, Object>> currentChildren = children;
StringBuilder currentPath = new StringBuilder();
for (int i = 0; i < pathParts.length; i++) {
String part = pathParts[i];
if (part.isEmpty()) continue;
currentPath.append(part).append("/");
String pathKey = currentPath.toString();
// 检查当前目录是否已存在
Map<String, Object> directoryNode = directoryMap.get(pathKey);
if (directoryNode == null) {
// 创建新目录节点
directoryNode = new LinkedHashMap<>();
directoryNode.put("name", part);
directoryNode.put("type", "directory");
directoryNode.put("path", pathKey);
directoryNode.put("format", "directory");
directoryNode.put("description", part + " directory");
directoryNode.put("children", new ArrayList<>());
// 添加到当前子节点列表
currentChildren.add(directoryNode);
directoryMap.put(pathKey, directoryNode);
}
// 进入下一级目录
currentChildren = (List<Map<String, Object>>) directoryNode.get("children");
}
}
/**
* 将文件添加到目录树中
* @param children 子节点列表
* @param directoryMap 目录映射
* @param pathParts 路径部分
* @param entry zip条目
* @param zipFile zip文件对象
* @throws IOException 读取文件内容时的IO异常
*/
private static void addFileToTree(List<Map<String, Object>> children, Map<String, Map<String, Object>> directoryMap, String[] pathParts, ZipEntry entry, ZipFile zipFile) throws IOException {
if (pathParts.length == 0) return;
// 获取文件名
String fileName = pathParts[pathParts.length - 1];
// 构建目录路径
StringBuilder directoryPath = new StringBuilder();
for (int i = 0; i < pathParts.length - 1; i++) {
String part = pathParts[i];
if (!part.isEmpty()) {
directoryPath.append(part).append("/");
}
}
// 找到文件所在的目录节点
List<Map<String, Object>> targetChildren = children;
if (directoryPath.length() > 0) {
Map<String, Object> directoryNode = directoryMap.get(directoryPath.toString());
if (directoryNode != null) {
targetChildren = (List<Map<String, Object>>) directoryNode.get("children");
}
}
// 创建文件节点并添加到目标目录
Map<String, Object> fileNode = createFileNode(entry, zipFile);
targetChildren.add(fileNode);
}
/**
* 创建目录节点
* @param entry zip条目
* @param zipFile zip文件对象
* @return 目录节点
*/
private static Map<String, Object> createDirectoryNode(ZipEntry entry, ZipFile zipFile) {
Map<String, Object> directoryNode = new LinkedHashMap<>();
String name = entry.getName().replaceAll("/$", "");
name = name.substring(name.lastIndexOf("/") + 1);
directoryNode.put("name", name);
directoryNode.put("type", "directory");
directoryNode.put("path", entry.getName());
directoryNode.put("format", "directory");
directoryNode.put("description", name + " directory");
directoryNode.put("children", new ArrayList<>());
return directoryNode;
}
/**
* 创建文件节点
* @param entry zip条目
* @param zipFile zip文件对象
* @return 文件节点
* @throws IOException 读取文件内容时的IO异常
*/
private static Map<String, Object> createFileNode(ZipEntry entry, ZipFile zipFile) throws IOException {
Map<String, Object> fileNode = new LinkedHashMap<>();
String name = entry.getName().substring(entry.getName().lastIndexOf("/") + 1);
fileNode.put("name", name);
fileNode.put("type", "file");
fileNode.put("path", entry.getName());
fileNode.put("format", getFileFormat(name));
fileNode.put("description", name + " file");
// 读取文件内容
try (InputStream inputStream = zipFile.getInputStream(entry);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
fileNode.put("content", content.toString());
}
return fileNode;
}
/**
* 创建skills.md文件节点
* @param skillName 技能包名称
* @param skillDescription 技能包描述
* @param skillTags 技能包标签
* @return skills.md文件节点
*/
private static Map<String, Object> createSkillsMdNode(String skillName, String skillDescription, List<String> skillTags) {
Map<String, Object> skillsMdNode = new LinkedHashMap<>();
skillsMdNode.put("name", "skills.md");
skillsMdNode.put("type", "file");
skillsMdNode.put("path", "skills.md");
skillsMdNode.put("format", "markdown");
skillsMdNode.put("description", "Skills metadata file");
// 生成skills.md内容
StringBuilder content = new StringBuilder();
content.append("# " + skillName).append("\n\n");
content.append("## Description").append("\n");
content.append(skillDescription).append("\n\n");
content.append("## Tags").append("\n");
if (skillTags != null && !skillTags.isEmpty()) {
for (String tag : skillTags) {
content.append("- " + tag).append("\n");
}
} else {
content.append("- general\n").append("- skill\n");
}
content.append("\n");
content.append("## Usage").append("\n");
content.append("This skill provides functionality for " + skillName).append("\n");
skillsMdNode.put("content", content.toString());
return skillsMdNode;
}
/**
* 创建scripts目录节点
* @return scripts目录节点
*/
private static Map<String, Object> createScriptsDirNode() {
Map<String, Object> scriptsDirNode = new LinkedHashMap<>();
scriptsDirNode.put("name", "scripts");
scriptsDirNode.put("type", "directory");
scriptsDirNode.put("path", "scripts/");
scriptsDirNode.put("format", "directory");
scriptsDirNode.put("description", "Scripts directory");
// 添加示例脚本
List<Map<String, Object>> scriptChildren = new ArrayList<>();
// 示例Python脚本
Map<String, Object> pythonScriptNode = new LinkedHashMap<>();
pythonScriptNode.put("name", "example.py");
pythonScriptNode.put("type", "file");
pythonScriptNode.put("path", "scripts/example.py");
pythonScriptNode.put("format", "python");
pythonScriptNode.put("description", "Example Python script");
pythonScriptNode.put("content", "#!/usr/bin/env python3\n" +
"\"\"\"\n" +
"Example script for " + "skill" + "\n" +
"\"\"\"\n\n" +
"def main():\n" +
" print('Hello from skill script!')\n\n" +
"if __name__ == '__main__':\n" +
" main()\n");
scriptChildren.add(pythonScriptNode);
scriptsDirNode.put("children", scriptChildren);
return scriptsDirNode;
}
/**
* 获取文件格式
* @param fileName 文件名
* @return 文件格式
*/
private static String getFileFormat(String fileName) {
if (fileName.endsWith(".md")) {
return "markdown";
} else if (fileName.endsWith(".py")) {
return "python";
} else if (fileName.endsWith(".js")) {
return "javascript";
} else if (fileName.endsWith(".json")) {
return "json";
} else if (fileName.endsWith(".yaml") || fileName.endsWith(".yml")) {
return "yaml";
} else if (fileName.endsWith(".txt")) {
return "text";
} else if (fileName.endsWith(".sh")) {
return "shell";
} else {
return "other";
}
}
/**
* 生成yaml
* @param skillStructure 技能包结构
* @return 生成的yaml描述
*/
private static String generateYaml(Map<String, Object> skillStructure) {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setIndent(2);
options.setPrettyFlow(true);
Yaml yaml = new Yaml(options);
return yaml.dump(skillStructure);
}
}