diff --git a/src/main/java/com/kexue/skills/controller/SkillGenController.java b/src/main/java/com/kexue/skills/controller/SkillGenController.java index c6fdb12..67d3d14 100644 --- a/src/main/java/com/kexue/skills/controller/SkillGenController.java +++ b/src/main/java/com/kexue/skills/controller/SkillGenController.java @@ -1,8 +1,8 @@ package com.kexue.skills.controller; -import com.alibaba.fastjson.JSONObject; import com.kexue.skills.common.CommonResult; import com.kexue.skills.entity.CmsContent; +import com.kexue.skills.entity.dto.YamlContentDto; import com.kexue.skills.entity.request.GenIntroduceRequest; import com.kexue.skills.entity.request.SkillGenRequest; import com.kexue.skills.entity.request.SkillPreGenRequest; @@ -72,6 +72,18 @@ public class SkillGenController { return CommonResult.success(skillGenService.genIntroduce(request.getContent())); } + /** + * 根据技能描述生成技能介绍 + * + * @param request 技能描述 + * @return 技能介绍 + */ + @PostMapping("/genIntroduceByDescription") + @Operation(summary = "根据技能描述生成技能介绍", description = "根据技能描述生成技能介绍") + public CommonResult genIntroduceByDescription(@RequestBody GenIntroduceRequest request) { + return CommonResult.success(skillGenService.genIntroduceByDescription(request.getContent())); + } + /** * 上传技能压缩包 * @@ -103,5 +115,17 @@ public class SkillGenController { return CommonResult.failed("上传失败:" + e.getMessage()); } } - + /** + * 上传本地技能压缩包V3 直接传入yamlContent + * + * @param yamlContentDto 技能压缩包文件 + * @return 生成的技能内容 + */ + @PostMapping("/uploadSkillV3") + @Operation(summary = "上传本地技能压缩包V3,直接传入yamlContent", description = "直接传入yamlContent") + public CommonResult uploadSkillV3(@RequestBody YamlContentDto yamlContentDto) { + CmsContent cmsContent = skillGenService.uploadSkillV3(yamlContentDto.getYamlContent()); + return CommonResult.success(cmsContent); + } + } diff --git a/src/main/java/com/kexue/skills/entity/dto/YamlContentDto.java b/src/main/java/com/kexue/skills/entity/dto/YamlContentDto.java new file mode 100644 index 0000000..3abf397 --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/dto/YamlContentDto.java @@ -0,0 +1,14 @@ +package com.kexue.skills.entity.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +@Data +@Schema(description = "yaml内容") +public class YamlContentDto implements Serializable { + + @Schema(description = "yaml内容") + private String yamlContent; +} diff --git a/src/main/java/com/kexue/skills/service/SkillGenService.java b/src/main/java/com/kexue/skills/service/SkillGenService.java index 020d03a..ef16dd3 100644 --- a/src/main/java/com/kexue/skills/service/SkillGenService.java +++ b/src/main/java/com/kexue/skills/service/SkillGenService.java @@ -44,6 +44,14 @@ public interface SkillGenService { */ String genIntroduce(String content); + /** + * 根据技能描述生成技能介绍 + * + * @param description 技能描述 + * @return 技能介绍 + */ + String genIntroduceByDescription(String description); + /** * 上传技能压缩包并生成技能 * @@ -60,5 +68,6 @@ public interface SkillGenService { * @return 生成的技能内容 */ CmsContent uploadSkillV2(byte[] fileBytes, String fileName); - + + CmsContent uploadSkillV3(String yamlContent); } diff --git a/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java index 9812689..40d0fca 100644 --- a/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java @@ -21,10 +21,12 @@ import com.kexue.skills.mapper.CmsContentMapper; import com.kexue.skills.service.CmsTagService; import com.kexue.skills.service.SkillGenService; import com.kexue.skills.utils.EscapeCharacterUtils; +import com.kexue.skills.utils.YamlToMapUtil; import jodd.util.StringUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.yaml.snakeyaml.error.YAMLException; import javax.annotation.Resource; import java.io.File; @@ -420,6 +422,36 @@ public class SkillGenServiceImpl implements SkillGenService { return null; } + + @Override + public String genIntroduceByDescription(String description) { + log.info("根据技能描述生成技能介绍请求: {}", description); + String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; + + String systemContent = "你是一个专业的AI技能设计助手。我会给你提供一个skill的描述,请你基于这个描述,生成一段详细的技能介绍,包括技能的作用、能够解决的问题、使用场景等,输出一段完整的描述文本"; + SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, description, 0.3, 500, "text"); + 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 choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class); + + if (choices != null && !choices.isEmpty()) { + JSONObject latestChoice = choices.get(0); + JSONObject message = latestChoice.getJSONObject("message"); + return EscapeCharacterUtils.removeEscapeCharacters( message.getString("content"));//去除转义字符 + } + } catch (Exception e) { + log.error("调用Deepseek API失败: {}", e.getMessage(), e); + throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse); + } + + return null; + } @Override public CmsContent uploadSkill(String skillUrl) { @@ -593,7 +625,7 @@ public class SkillGenServiceImpl implements SkillGenService { } } // 保存到数据库 - cmsContentMapper.insert(cmsContent); + // cmsContentMapper.insert(cmsContent); // 删除临时文件 tempFile.delete(); @@ -604,6 +636,48 @@ public class SkillGenServiceImpl implements SkillGenService { } } + @Override + public CmsContent uploadSkillV3(String yamlContent) { + log.info("uploadSkillV3 上传技能压缩包请求: {}", yamlContent); + try { + Map yamlMap = YamlToMapUtil.yamlTextToMap(yamlContent); + + Map packageMap = (Map)yamlMap.get("package"); + String skillName = (String) packageMap.get("name"); + String skillDescription = (String) packageMap.get("description"); + List tagList = (List) packageMap.get("tags"); + List tagsStrList = tagList.stream().map(String::valueOf).toList(); + String skillIcon = getDefaultIcon(tagsStrList.get(0)); + SkillGenRequest request = new SkillGenRequest(); + request.setName(skillName); + request.setDescription(skillDescription); + request.setTags(tagsStrList); + request.setIntroduce(genIntroduceByDescription(skillDescription)); + return getCmsContent(request, yamlContent, StpUtil.getLoginIdAsLong(), skillIcon); + } catch (YAMLException e) { + throw new BizException("yaml解析失败:"+ e.getMessage()); + } + } + + private String getDefaultIcon(String tagId){ + CmsTagDto tagDto = new CmsTagDto(); + tagDto.setDeleteFlag(0); + tagDto.setStatus(1); + List tagList = cmsTagService.getList(tagDto); + + String defaultIcon = ""; + for (int i = 0; i < tagList.size(); i++) { + CmsTag tag = tagList.get(i); + if (tagId.contains(tag.getTagId()+"")) { + if (StringUtil.isEmpty(defaultIcon)){ + defaultIcon = tag.getIcon(); + break; + } + } + } + return defaultIcon; + } + public SkillGenRequest parseSkillMdText(String skillMdText,List tags) { String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; diff --git a/src/main/java/com/kexue/skills/utils/YamlToMapUtil.java b/src/main/java/com/kexue/skills/utils/YamlToMapUtil.java new file mode 100644 index 0000000..23498cb --- /dev/null +++ b/src/main/java/com/kexue/skills/utils/YamlToMapUtil.java @@ -0,0 +1,158 @@ +package com.kexue.skills.utils; + +import org.apache.commons.io.FileUtils; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.error.YAMLException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 通用 YAML 解析工具:YAML 文件/文本 → Map(支持嵌套结构) + */ +public class YamlToMapUtil { + + // 初始化 YAML 解析器(线程安全,可全局复用) + private static final Yaml YAML_PARSER = new Yaml(); + + /** + * 1. 读取 YAML 文件,解析成 Map(支持嵌套结构) + * @param yamlFilePath YAML 文件路径(如 "D:/config.yaml" 或 "classpath:config.yaml") + * @return 嵌套 Map(key: 字段名,value: 字段值,嵌套结构保留) + * @throws IOException 文件不存在/读取失败 + * @throws YAMLException YAML 格式错误 + */ + @SuppressWarnings("unchecked") + public static Map yamlFileToMap(String yamlFilePath) throws IOException, YAMLException { + // 处理 classpath 下的文件(如 resources/config.yaml) + if (yamlFilePath.startsWith("classpath:")) { + String path = yamlFilePath.replace("classpath:", ""); + // 通过类加载器获取输入流(避免路径问题) + try (InputStream inputStream = YamlToMapUtil.class.getClassLoader().getResourceAsStream(path)) { + if (inputStream == null) { + throw new IOException("Classpath 下未找到 YAML 文件:" + path); + } + // 解析 YAML 为 Map(SnakeYAML 自动识别结构) + return YAML_PARSER.loadAs(inputStream, Map.class); + } + } + + // 处理本地绝对路径文件(如 D:/config.yaml) + File yamlFile = new File(yamlFilePath); + if (!yamlFile.exists()) { + throw new IOException("YAML 文件不存在:" + yamlFilePath); + } + // 读取文件内容并解析 + String yamlContent = FileUtils.readFileToString(yamlFile, StandardCharsets.UTF_8); + return yamlTextToMap(yamlContent); + } + + /** + * 2. 解析 YAML 字符串,生成 Map(支持嵌套结构) + * @param yamlText YAML 字符串(如 "name: test\nage: 18") + * @return 嵌套 Map + * @throws YAMLException YAML 格式错误 + */ + @SuppressWarnings("unchecked") + public static Map yamlTextToMap(String yamlText) throws YAMLException { + if (yamlText == null || yamlText.trim().isEmpty()) { + return new LinkedHashMap<>(); // 返回空 Map,避免空指针 + } + // 解析 YAML 文本,SnakeYAML 会自动转换类型(数字→Integer/Long,布尔→Boolean) + Object result = YAML_PARSER.load(yamlText); + // 若 YAML 是纯数组(如 "- a\n- b"),返回 key 为 "list" 的 Map;否则直接返回 Map + if (result instanceof Map) { + return (Map) result; + } else if (result instanceof Iterable) { + Map map = new LinkedHashMap<>(); + map.put("list", result); + return map; + } else { + // 纯单个值(如 "test"),返回 key 为 "value" 的 Map + Map map = new LinkedHashMap<>(); + map.put("value", result); + return map; + } + } + + /** + * 3. 可选工具:将嵌套 Map 扁平化(如 a.b.c: 123),方便直接取值 + * @param nestedMap 嵌套 Map(来自上述两个方法的返回值) + * @return 扁平化 Map(key: 拼接路径,value: 原始值) + */ + public static Map flattenMap(Map nestedMap) { + Map flatMap = new LinkedHashMap<>(); + flattenMapRecursive(nestedMap, "", flatMap); + return flatMap; + } + + /** + * 递归处理嵌套 Map,生成扁平化 key(私有辅助方法) + */ + @SuppressWarnings("unchecked") + private static void flattenMapRecursive(Map map, String parentKey, Map flatMap) { + for (Map.Entry entry : map.entrySet()) { + String currentKey = parentKey.isEmpty() ? entry.getKey() : parentKey + "." + entry.getKey(); + Object value = entry.getValue(); + + if (value instanceof Map) { + // 嵌套 Map,递归处理 + flattenMapRecursive((Map) value, currentKey, flatMap); + } else if (value instanceof Iterable && !(value instanceof String)) { + // 数组/列表(如 List),key 加索引(如 list.0, list.1) + int index = 0; + for (Object item : (Iterable) value) { + String listKey = currentKey + "." + index; + if (item instanceof Map) { + flattenMapRecursive((Map) item, listKey, flatMap); + } else { + flatMap.put(listKey, item); + } + index++; + } + } else { + // 基本类型(字符串、数字、布尔),直接放入扁平化 Map + flatMap.put(currentKey, value); + } + } + } + + // ==================== 测试示例 ==================== + public static void main(String[] args) { + try { + // 示例1:解析 YAML 文件(classpath 下的 config.yaml) + Map fileMap = yamlFileToMap("classpath:config.yaml"); + System.out.println("【嵌套 Map(文件解析)】" + fileMap); + + // 示例2:解析 YAML 字符串(含嵌套结构) + String yamlText = """ + name: "ppt_gen_direct" + description: "Create PPT" + settings: + timeout: 300 + enable: true + colors: + - red + - blue + """; + Map textMap = yamlTextToMap(yamlText); + System.out.println("【嵌套 Map(文本解析)】" + textMap); + + // 示例3:扁平化 Map,方便取值 + Map flatMap = flattenMap(textMap); + System.out.println("【扁平化 Map】" + flatMap); + // 直接通过拼接 key 取值 + System.out.println("settings.timeout = " + flatMap.get("settings.timeout")); + System.out.println("settings.colors.0 = " + flatMap.get("settings.colors.0")); + + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file