feat(skills): 新增技能包解析和标题获取功能
- 添加了CmsContentController的getTitle接口用于获取内容标题 - 实现了CmsContentService的getTitle方法支持内容标题查询 - 新增SkillZipParser工具类支持ZIP和RAR格式技能包解析 - 集成snakeyaml和sevenzipjbinding依赖处理YAML配置和压缩文件 - 实现SkillGenService的uploadSkillV2方法支持本地技能包上传 - 在SysUserController中增强token验证逻辑确保登录状态检查 - 支持从技能包中提取MD文件内容并自动生成YAML描述结构
This commit is contained in:
parent
a92b668ac3
commit
59a44f9c53
18
pom.xml
18
pom.xml
|
|
@ -282,6 +282,24 @@
|
|||
<version>4.38.0.ALL</version>
|
||||
</dependency>
|
||||
|
||||
<!-- YAML 配置文件解析器 -->
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>2.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.sf.sevenzipjbinding</groupId>
|
||||
<artifactId>sevenzipjbinding</artifactId>
|
||||
<version>16.02-2.01</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.sevenzipjbinding</groupId>
|
||||
<artifactId>sevenzipjbinding-all-platforms</artifactId>
|
||||
<version>16.02-2.01</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,843 @@
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -299,4 +299,17 @@ public class CmsContentController {
|
|||
String content = cmsContentService.getContent(queryContentDto.getContentId(), queryContentDto.getLanguageType());
|
||||
return CommonResult.success(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取CmsContent的title字段内容
|
||||
*
|
||||
* @param contentId 内容ID
|
||||
* @return title字段的内容
|
||||
*/
|
||||
@PostMapping("/getTitle/{contentId}")
|
||||
@Operation(summary = "获取标题", description = "根据contentId获取title字段的内容")
|
||||
public CommonResult<String> getTitle(@PathVariable("contentId") Long contentId) {
|
||||
String title = cmsContentService.getTitle(contentId);
|
||||
return CommonResult.success(title);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ import com.kexue.skills.entity.response.SkillResponse;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
|
|
@ -93,4 +95,24 @@ public class SkillGenController {
|
|||
public CommonResult<CmsContent> uploadSkill(@RequestBody SkillUploadDto skillUploadDto) {
|
||||
return CommonResult.success(skillGenService.uploadSkill(skillUploadDto.getUrl()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传本地技能压缩包V2
|
||||
*
|
||||
* @param file 技能压缩包文件
|
||||
* @return 生成的技能内容
|
||||
*/
|
||||
@PostMapping("/uploadSkillV2")
|
||||
@Operation(summary = "上传本地技能压缩包V2", description = "上传本地zip或rar文件并生成技能")
|
||||
public CommonResult<CmsContent> uploadSkillV2(
|
||||
@RequestParam("file") MultipartFile file) {
|
||||
try {
|
||||
byte[] fileBytes = file.getBytes();
|
||||
String fileName = file.getOriginalFilename();
|
||||
CmsContent cmsContent = skillGenService.uploadSkillV2(fileBytes, fileName);
|
||||
return CommonResult.success(cmsContent);
|
||||
} catch (Exception e) {
|
||||
return CommonResult.failed("上传失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,6 +215,13 @@ public class SysUserController {
|
|||
throw new BizException("请先登录认证后操作");
|
||||
}
|
||||
|
||||
// 使用Sa-Token检查token是否有效
|
||||
try {
|
||||
cn.dev33.satoken.stp.StpUtil.checkLogin();
|
||||
} catch (Exception e) {
|
||||
throw new BizException("无效的token,请重新登录");
|
||||
}
|
||||
|
||||
// 从Redis缓存中获取LoginUser对象
|
||||
String loginUserJson = (String)redissonClient.getBucket("loginUser:" + token).get();
|
||||
if (loginUserJson == null || loginUserJson.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -183,4 +183,12 @@ public interface CmsContentService extends BaseService {
|
|||
* @return content或contentEn字段的内容
|
||||
*/
|
||||
String getContent(Long contentId, Integer languageType);
|
||||
|
||||
/**
|
||||
* 获取CmsContent的title字段内容
|
||||
*
|
||||
* @param contentId 内容ID
|
||||
* @return title字段的内容
|
||||
*/
|
||||
String getTitle(Long contentId);
|
||||
}
|
||||
|
|
@ -51,4 +51,13 @@ public interface SkillGenService {
|
|||
* @return 生成的技能内容
|
||||
*/
|
||||
CmsContent uploadSkill(String skillUrl);
|
||||
|
||||
/**
|
||||
* 上传本地技能压缩包并生成技能V2
|
||||
*
|
||||
* @param fileBytes 技能压缩包字节数组
|
||||
* @param fileName 文件名
|
||||
* @return 生成的技能内容
|
||||
*/
|
||||
CmsContent uploadSkillV2(byte[] fileBytes, String fileName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -932,4 +932,14 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
return cmsContent.getContent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle(Long contentId) {
|
||||
Assert.notNull(contentId, "内容ID不能为空");
|
||||
CmsContent cmsContent = this.cmsContentMapper.queryById(contentId);
|
||||
if (cmsContent == null) {
|
||||
return null;
|
||||
}
|
||||
return cmsContent.getTitle();
|
||||
}
|
||||
}
|
||||
|
|
@ -26,10 +26,12 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
|
@ -415,4 +417,152 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CmsContent uploadSkillV2(byte[] fileBytes, String fileName) {
|
||||
log.info("上传本地技能压缩包请求: {}", fileName);
|
||||
|
||||
try {
|
||||
// 创建临时文件
|
||||
String fileExtension = "";
|
||||
if (fileName.contains(".")) {
|
||||
int dotIndex = fileName.lastIndexOf(".");
|
||||
if (dotIndex != -1) {
|
||||
fileExtension = fileName.substring(dotIndex);
|
||||
}
|
||||
}
|
||||
File tempFile = File.createTempFile("skill", fileExtension);
|
||||
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
|
||||
fos.write(fileBytes);
|
||||
}
|
||||
|
||||
// 生成默认参数
|
||||
String author = StpUtil.getLoginIdAsString();
|
||||
// 从文件名中提取技能名称
|
||||
String defaultSkillName = fileName;
|
||||
if (fileName.contains(".")) {
|
||||
int dotIndex = fileName.lastIndexOf(".");
|
||||
if (dotIndex != -1) {
|
||||
defaultSkillName = fileName.substring(0, dotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 提取压缩包中的skillMdText
|
||||
Map<String, Object> skillInfo = com.kexue.skills.common.util.SkillZipParser.extractSkillMdText(tempFile.getAbsolutePath(), defaultSkillName);
|
||||
String skillMdText = (String) skillInfo.get("skillMdText");
|
||||
|
||||
// 从数据库中读取cms_tag表的标签信息
|
||||
CmsTagDto tagDto = new CmsTagDto();
|
||||
tagDto.setDeleteFlag(0);
|
||||
tagDto.setStatus(1);
|
||||
List<CmsTag> tags = cmsTagService.getList(tagDto);
|
||||
|
||||
// 2. 解析skillMdText获取SkillGenRequest
|
||||
SkillGenRequest request = null;
|
||||
if (skillMdText != null && !skillMdText.isEmpty()) {
|
||||
request = parseSkillMdText(skillMdText, tags);
|
||||
} else {
|
||||
// 如果没有找到md文件,使用默认值
|
||||
request = new SkillGenRequest();
|
||||
request.setName(defaultSkillName);
|
||||
request.setDescription("未知");
|
||||
request.setIntroduce("未知");
|
||||
request.setTags(Arrays.asList("10001", "10002"));
|
||||
}
|
||||
|
||||
// 3. 使用SkillGenRequest中的信息生成yaml
|
||||
String yamlContent = com.kexue.skills.common.util.SkillZipParser.generateYamlFromSkillInfo(
|
||||
tempFile.getAbsolutePath(),
|
||||
author,
|
||||
request.getName(),
|
||||
request.getDescription(),
|
||||
request.getTags()
|
||||
);
|
||||
log.info("解析出skill 压缩包内容:{}", yamlContent);
|
||||
|
||||
// 4. 生成CmsContent对象
|
||||
CmsContent cmsContent = getCmsContent(request, yamlContent, StpUtil.getLoginIdAsLong(), "");
|
||||
List<CmsTag> list = tags.stream().filter(tag -> tag.getTagId() == Long.parseLong(cmsContent.getTags().split(",")[0])).toList();
|
||||
if (CollectionUtil.isNotEmpty(list)){
|
||||
cmsContent.setIcon(list.get(0).getIcon());
|
||||
}
|
||||
// 删除临时文件
|
||||
tempFile.delete();
|
||||
|
||||
return cmsContent;
|
||||
} catch (Exception e) {
|
||||
log.error("上传本地技能压缩包失败: {}", e.getMessage(), e);
|
||||
throw new BizException("上传本地技能压缩包失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public SkillGenRequest parseSkillMdText(String skillMdText,List<CmsTag> tags) {
|
||||
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
|
||||
|
||||
// 将标签名称拼接成逗号分隔的字符串
|
||||
StringBuilder tagsList = new StringBuilder();
|
||||
for (int i = 0; i < tags.size(); i++) {
|
||||
CmsTag tag = tags.get(i);
|
||||
tagsList.append(tag.getTagId()+"."+tag.getTagName());
|
||||
if (i < tags.size() - 1) {
|
||||
tagsList.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
// 构建系统消息内容
|
||||
String systemContent = """
|
||||
你是一位专业的AI技能包解析助手,需完成以下任务:
|
||||
1. 我会提供一个技能包压缩包SKILL.md文件的具体内容;
|
||||
2. 基于SKILL.md内容,提炼出:
|
||||
- skill的核心标题(name);
|
||||
- skill的核心描述(description);
|
||||
- 详细功能介绍(introduce);
|
||||
- 从指定标签列表中选取至少3个适配标签(tagList),标签范围:TAG_LIST;
|
||||
- 选择标签的时候只需要输出对应的标签编号
|
||||
请将最终结果以JSON格式输出,JSON结构必须包含:name(技能名称),description(技能描述)、introduce(功能介绍)、tagList(标签列表),无需额外说明,仅输出符合要求的JSON内容。
|
||||
""";
|
||||
systemContent = systemContent.replace("TAG_LIST", tagsList.toString());
|
||||
|
||||
// 创建技能请求
|
||||
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, skillMdText, 0.5, 8192, "json_object");
|
||||
|
||||
String deepseekResponse = "";
|
||||
try {
|
||||
// 发送HTTP请求到DeepSeek API
|
||||
deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
|
||||
log.info("Deepseek API响应: {}", deepseekResponse);
|
||||
|
||||
// 解析返回结果
|
||||
JSONObject responseJson = JSON.parseObject(deepseekResponse);
|
||||
List<JSONObject> choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class);
|
||||
|
||||
if (choices != null && !choices.isEmpty()) {
|
||||
// 获取最新的choice
|
||||
JSONObject latestChoice = choices.get(0);
|
||||
JSONObject message = latestChoice.getJSONObject("message");
|
||||
String content = message.getString("content");
|
||||
|
||||
// 解析content中的JSON
|
||||
JSONObject skillJson = JSON.parseObject(content);
|
||||
|
||||
// 构建技能生成请求
|
||||
SkillGenRequest request = new SkillGenRequest();
|
||||
request.setName(skillJson.getString("name"));
|
||||
request.setDescription(skillJson.getString("description"));
|
||||
request.setIntroduce(skillJson.getString("introduce"));
|
||||
// 处理tagList可能不存在的情况
|
||||
if (skillJson.containsKey("tagList")) {
|
||||
request.setTags(skillJson.getJSONArray("tagList").toJavaList(String.class));
|
||||
} else {
|
||||
request.setTags(Arrays.asList("10001", "10002"));
|
||||
}
|
||||
return request;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
|
||||
throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue