diff --git a/src/main/java/com/kexue/skills/config/UploadConfig.java b/src/main/java/com/kexue/skills/config/UploadConfig.java new file mode 100644 index 0000000..8fe8d14 --- /dev/null +++ b/src/main/java/com/kexue/skills/config/UploadConfig.java @@ -0,0 +1,48 @@ +package com.kexue.skills.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 上传配置类 + * 用于读取web.upload配置 + */ +@Data +@Component +@ConfigurationProperties(prefix = "web.upload") +public class UploadConfig { + + /** + * 上传路径 + */ + private String path; + + /** + * 文件上传目录 + */ + public String getFileUploadDir() { + return path + "file/"; + } + + /** + * 图片上传目录 + */ + public String getImgUploadDir() { + return path + "images/"; + } + + /** + * 文件访问路径前缀 + */ + public String getFileUrlPrefix() { + return "/upload/file/"; + } + + /** + * 图片访问路径前缀 + */ + public String getImgUrlPrefix() { + return "/upload/images/"; + } +} diff --git a/src/main/java/com/kexue/skills/controller/CommonController.java b/src/main/java/com/kexue/skills/controller/CommonController.java index 3a62a89..0054bef 100644 --- a/src/main/java/com/kexue/skills/controller/CommonController.java +++ b/src/main/java/com/kexue/skills/controller/CommonController.java @@ -1,6 +1,7 @@ package com.kexue.skills.controller; import com.kexue.skills.common.CommonResult; +import com.kexue.skills.config.UploadConfig; import com.kexue.skills.entity.request.UploadResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -8,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.annotation.Resource; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -21,13 +23,13 @@ import java.nio.file.StandardCopyOption; */ @RestController @RequestMapping("api/common") -@Tag(name = "公共 Api") +@Tag(name = "Common Api") @CrossOrigin(origins = "*") @Slf4j public class CommonController { - private static final String FILE_UPLOAD_DIR = "/data/service/hyxp-portal/upload/file/"; - private static final String IMG_UPLOAD_DIR = "/data/service/hyxp-portal/upload/images/"; + @Resource + private UploadConfig uploadConfig; /** @@ -49,7 +51,7 @@ public class CommonController { try { // 创建上传目录(如果不存在) - Path uploadPath = Paths.get(FILE_UPLOAD_DIR); + Path uploadPath = Paths.get(uploadConfig.getFileUploadDir()); if (!Files.exists(uploadPath)) { Files.createDirectories(uploadPath); } @@ -62,7 +64,7 @@ public class CommonController { UploadResponse uploadResponse = new UploadResponse(); uploadResponse.setFileName(file.getOriginalFilename()); - uploadResponse.setFileUrl("/upload/file/" + file.getOriginalFilename()); + uploadResponse.setFileUrl(uploadConfig.getFileUrlPrefix() + file.getOriginalFilename()); return CommonResult.success(uploadResponse); } catch (IOException e) { e.printStackTrace(); @@ -94,7 +96,7 @@ public class CommonController { try { // 创建上传目录(如果不存在) - Path uploadPath = Paths.get(IMG_UPLOAD_DIR); + Path uploadPath = Paths.get(uploadConfig.getImgUploadDir()); if (!Files.exists(uploadPath)) { Files.createDirectories(uploadPath); } @@ -107,7 +109,7 @@ public class CommonController { UploadResponse uploadResponse = new UploadResponse(); uploadResponse.setFileName(image.getOriginalFilename()); - uploadResponse.setFileUrl("/upload/images/" + image.getOriginalFilename()); + uploadResponse.setFileUrl(uploadConfig.getImgUrlPrefix() + image.getOriginalFilename()); return CommonResult.success(uploadResponse); } catch (IOException e) { e.printStackTrace(); 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 1895e33..172e771 100644 --- a/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java @@ -17,6 +17,7 @@ import com.kexue.skills.entity.response.SkillResponse; 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 jodd.util.StringUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -215,7 +216,7 @@ public class SkillGenServiceImpl implements SkillGenService { } } } - String systemContent = "你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签,按照skills目录结构输出完整的skills内容,包括skills.md本体内容、scripts目录中的脚本等,并打包成一个YAML文件技能包。请严格遵循以下规范:1. 包含必需的文件和目录;2. 多行内容使用 | 字面块;3. 内容从行首开始;4. 空目录用 children: [] 表示;5. 文件内容要实际有用;6.输出一个完整的YAML文档,对于每个节点的内容要输出type,文件对应file,目录对应directory,每个节点都必须有name和content属性,无需其他额外说明。"; + String systemContent = "你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签,按照skills目录结构输出完整的skills内容,包括skills.md本体内容、scripts目录中的脚本等,并打包成一个YAML文件技能包。请严格遵循以下规范:1. 包含必需的文件和目录;2. 多行内容使用 | 字面块;3. 内容从行首开始;4. 空目录用 children: [] 表示;5. 文件内容要实际有用;6.输出一个完整的YAML文档,概要含核心属性:name、version、description、author、created、tags 等,其中 structure 为核心节点,用于描述 skill 包的文件目录结构;structure 下的每个节点均含基础属性:name、type、path、format、description,content 和 children 为互选属性,由 type 决定:type 为 file 时,展示文件具体内容在 content 字段;type 为 directory 时,展示子目录 / 文件节点在 children [] 数组中,无需其他额外说明。"; String userContent = "请根据以下Skill信息生成skills.md文档内容:Skill名称:SKILL_NAME,Skill描述:DESCRIPTION,Skill标签:TAGS ,摘要:SUMMARY。"; userContent = userContent.replace("SKILL_NAME", request.getName()).replace("DESCRIPTION", request.getDescription()).replace("TAGS", tagsList.toString()).replace("SUMMARY", request.getRequirement()); SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),systemContent,userContent,deepSeekConfig.getChat().getTemperature(), 8192,"text"); @@ -225,6 +226,7 @@ public class SkillGenServiceImpl implements SkillGenService { log.info("Deepseek API响应: {}", deepseekResponse); JSONObject responseJson = JSON.parseObject(deepseekResponse); String content = responseJson.getJSONArray("choices").toJavaList(JSONObject.class).get(0).getJSONObject("message").getString("content"); + content = EscapeCharacterUtils.removeEscapeCharacters( content);//去除转义字符 CmsContent cmsContent = getCmsContent(request, content, StpUtil.getLoginIdAsLong(),defaultIcon); // 保存到数据库 cmsContentMapper.insert(cmsContent); @@ -300,7 +302,7 @@ public class SkillGenServiceImpl implements SkillGenService { if (choices != null && !choices.isEmpty()) { JSONObject latestChoice = choices.get(0); JSONObject message = latestChoice.getJSONObject("message"); - return message.getString("content"); + return EscapeCharacterUtils.removeEscapeCharacters( message.getString("content"));//去除转义字符 } } catch (Exception e) { log.error("调用Deepseek API失败: {}", e.getMessage(), e); diff --git a/src/main/java/com/kexue/skills/utils/EscapeCharacterUtils.java b/src/main/java/com/kexue/skills/utils/EscapeCharacterUtils.java new file mode 100644 index 0000000..008a43b --- /dev/null +++ b/src/main/java/com/kexue/skills/utils/EscapeCharacterUtils.java @@ -0,0 +1,70 @@ +package com.kexue.skills.utils; + +import java.util.HashMap; +import java.util.Map; + +/** + * 去除/还原字符串中转义字符的工具类 + */ +public class EscapeCharacterUtils { + // 定义Java常见转义字符映射(键:转义符,值:原字符) + private static final Map ESCAPE_MAP = new HashMap<>(); + + static { + // 基础转义字符 + ESCAPE_MAP.put("\\n", "\n"); // 换行符 + ESCAPE_MAP.put("\\t", "\t"); // 制表符 + ESCAPE_MAP.put("\\r", "\r"); // 回车符 + ESCAPE_MAP.put("\\\\", "\\"); // 反斜杠本身 + ESCAPE_MAP.put("\\\"", "\""); // 双引号 + ESCAPE_MAP.put("\\'", "'"); // 单引号 + ESCAPE_MAP.put("\\b", "\b"); // 退格符 + ESCAPE_MAP.put("\\f", "\f"); // 换页符 + } + + /** + * 去除/还原字符串中的转义字符 + * @param input 包含转义字符的字符串 + * @param removeMode true=直接删除转义符(如"\n"→""),false=还原为原字符(如"\n"→换行符) + * @return 处理后的字符串 + */ + public static String removeEscapeCharacters(String input, boolean removeMode) { + // 空值校验 + if (input == null || input.isEmpty()) { + return input; + } + + String result = input; + // 遍历替换所有转义字符 + for (Map.Entry entry : ESCAPE_MAP.entrySet()) { + String escapeChar = entry.getKey(); + String targetChar = removeMode ? "" : entry.getValue(); + result = result.replace(escapeChar, targetChar); + } + + return result; + } + + // 重载方法:默认还原为原字符(非删除模式) + public static String removeEscapeCharacters(String input) { + return removeEscapeCharacters(input, false); + } + + // 测试示例 + public static void main(String[] args) { + // 测试字符串 + String testStr = "```yaml\\ntype: directory\\nname: skills\\ncontent:\\n children:\\n - type: file\\n name: skills.md\\n content: |\\n # 智能工作总结\\n\\n ## 技能描述\\n 该技能能够根据用户输入的工作内容、项目成果、个人成长等信息,自动生成结构清晰、内容完整的工作总结报告。\\n\\n ## 技能标签\\n 办公演示,文档标准\\n\\n ## 技能摘要\\n 帮助用户快速、高效地完成工作总结的撰写,提升报告的专业性和规范性,节省大量时间和精力。\\n\\n ## 使用说明\\n 1. 用户提供详细的工作内容、项目成果、个人成长等信息。\\n 2. 技能将自动分析信息,提取关键点。\\n 3. 生成结构化的总结报告草稿。\\n 4. 用户可对草稿进行审阅和微调。\\n\\n ## 输入参数\\n - `work_content` (字符串): 详细描述日常工作职责与完成的任务。\\n - `project_achievements` (字符串): 描述参与的项目及其取得的成果、个人贡献。\\n - `personal_growth` (字符串): 描述在技能、知识、协作等方面的进步与收获。\\n - `period` (字符串,可选): 总结覆盖的时间段,如“2024年第一季度”。默认为“近期”。\\n - `report_style` (字符串,可选): 报告风格,可选“正式”、“简洁”、“详细”。默认为“正式”。\\n\\n ## 输出结果\\n 一个结构清晰、语言通顺的工作总结报告文本。\\n\\n ## 示例\\n **输入:**\\n - work_content: “负责客户需求分析,撰写产品需求文档;参与每周迭代评审会。”\\n - project_achievements: “主导了‘智能报表模块’的开发,使数据生成效率提升30%。”\\n - personal_growth: “深入学习了Python数据分析库Pandas,提升了团队协作效率。”\\n - period: “2024年第二季度”\\n - report_style: “正式”\\n\\n **输出(摘要):**\\n “2024年第二季度工作总结... 本季度,我主要负责客户需求分析与产品需求文档撰写... 在项目方面,我主导了‘智能报表模块’的开发,成功将数据生成效率提升30%... 个人在专业技能上,深入学习了Python Pandas库,并在团队协作中发挥了更积极的作用... 展望下一季度,我将...”\\n - type: directory\\n name: scripts\\n content:\\n children:\\n - type: file\\n name: generate_summary.py\\n content: |\\n #!/usr/bin/env python3\\n # -*- coding: utf-8 -*-\\n \\\"\\\"\\\"\\n 智能工作总结生成脚本。\\n 根据输入的工作内容、项目成果、个人成长等信息,生成结构化的工作总结报告。\\n \\\"\\\"\\\"\\n\\n import sys\\n import json\\n from datetime import datetime\\n\\n def generate_work_summary(work_content, project_achievements, personal_growth, period=\\\"近期\\\", report_style=\\\"正式\\\"):\\n \\\"\\\"\\\"\\n 生成工作总结报告的核心函数。\\n\\n Args:\\n work_content (str): 工作内容描述。\\n project_achievements (str): 项目成果描述。\\n personal_growth (str): 个人成长描述。\\n period (str, optional): 时间段。默认为“近期”。\\n report_style (str, optional): 报告风格。默认为“正式”。\\n\\n Returns:\\n str: 生成的工作总结报告文本。\\n \\\"\\\"\\\"\\n # 根据风格调整模板和措辞\\n style_map = {\\n \\\"正式\\\": {\\n \\\"template\\\": \\\"\\\"\\\"{period}工作总结\\n\\n 一、 主要工作内容\\n {work_content}\\n\\n 二、 项目成果与贡献\\n {project_achievements}\\n\\n 三、 个人成长与收获\\n {personal_growth}\\n\\n 四、 不足与反思\\n (请根据实际情况补充)\\n\\n 五、 未来工作计划\\n (请根据实际情况补充)\\n\\n 总结人:[请填写]\\n {date}\\\"\\\"\\\",\\n \\\"opening\\\": \\\"现将{period}的工作情况总结如下:\\\",\\n \\\"closing\\\": \\\"以上是我对{period}工作的总结,请审阅。\\\"\\n },\\n \\\"简洁\\\": {\\n \\\"template\\\": \\\"\\\"\\\"{period}工作要点\\n\\n • 工作内容:{work_content}\\n • 项目成果:{project_achievements}\\n • 个人成长:{personal_growth}\\n • 后续计划:(待补充)\\\"\\\"\\\",\\n \\\"opening\\\": \\\"{period}主要工作:\\\",\\n \\\"closing\\\": \\\"报告完毕。\\\"\\n },\\n \\\"详细\\\": {\\n \\\"template\\\": \\\"\\\"\\\"{period}详细工作总结报告\\n\\n 1. 工作职责与日常任务完成情况\\n 1.1 概述\\n {work_content}\\n\\n 2. 重点项目参与情况及成果\\n 2.1 项目概述与个人角色\\n {project_achievements}\\n\\n 3. 能力提升与自我学习\\n 3.1 技能提升\\n {personal_growth}\\n\\n 4. 遇到的问题与解决方案\\n (请详细描述)\\n\\n 5. 经验教训与反思\\n (请详细描述)\\n\\n 6. 下一阶段目标与行动计划\\n (请详细规划)\\n\\n 报告人:[请填写]\\n 日期:{date}\\\"\\\"\\\",\\n \\\"opening\\\": \\\"本报告旨在详细梳理{period}的工作表现、成果与成长。\\\",\\n \\\"closing\\\": \\\"此报告为{period}工作的全面回顾与展望。\\\"\\n }\\n }\\n\\n if report_style not in style_map:\\n report_style = \\\"正式\\\"\\n print(f\\\"警告:未识别的报告风格 '{report_style}',已使用默认风格‘正式’。\\\", file=sys.stderr)\\n\\n style_config = style_map[report_style]\\n current_date = datetime.now().strftime(\\\"%Y年%m月%d日\\\")\\n\\n # 构建报告\\n report = style_config['template'].format(\\n period=period,\\n work_content=work_content,\\n project_achievements=project_achievements,\\n personal_growth=personal_growth,\\n date=current_date\\n )\\n\\n # 可选的增强:在报告开头或结尾插入风格化的开场/结束语\\n # 例如:report = style_config['opening'].format(period=period) + \\\"\\\\n\\\\n\\\" + report\\n # 例如:report = report + \\\"\\\\n\\\\n\\\" + style_config['closing'].format(period=period)\\n\\n return report\\n\\n if __name__ == \\\"__main__\\\":\\n # 示例:从命令行参数或标准输入读取JSON数据\\n # 这里使用简单的示例输入\\n if len(sys.argv) > 1:\\n try:\\n input_data = json.loads(sys.argv[1])\\n except json.JSONDecodeError:\\n print(\\\"错误:输入参数不是有效的JSON格式。\\\", file=sys.stderr)\\n sys.exit(1)\\n else:\\n # 默认示例数据\\n input_data = {\\n \\\"work_content\\\": \\\"负责日常客户需求沟通与文档维护,参与产品功能设计讨论。\\\",\\n \\\"project_achievements\\\": \\\"作为核心成员完成‘数据可视化看板’项目,用户满意度调查得分提升25%。\\\",\\n \\\"personal_growth\\\": \\\"系统学习了前端框架Vue.js的基础知识,并成功应用于一个小型内部工具的开发。\\\",\\n \\\"period\\\": \\\"本季度\\\",\\n \\\"report_style\\\": \\\"正式\\\"\\n }\\n print(\\\"信息:使用内置示例数据。\\\", file=sys.stderr)\\n\\n # 提取参数,提供默认值\\n work_content = input_data.get(\\\"work_content\\\", \\\"\\\")\\n project_achievements = input_data.get(\\\"project_achievements\\\", \\\"\\\")\\n personal_growth = input_data.get(\\\"personal_growth\\\", \\\"\\\")\\n period = input_data.get(\\\"period\\\", \\\"近期\\\")\\n report_style = input_data.get(\\\"report_style\\\", \\\"正式\\\")\\n\\n if not work_content and not project_achievements and not personal_growth:\\n print(\\\"错误:工作内容、项目成果、个人成长至少需要提供一项信息。\\\", file=sys.stderr)\\n sys.exit(1)\\n\\n # 生成报告\\n summary_report = generate_work_summary(work_content, project_achievements, personal_growth, period, report_style)\\n print(summary_report)\\n - type: file\\n name: README.md\\n content: |\\n # 脚本目录说明\\n\\n 本目录包含“智能工作总结”技能的核心处理脚本。\\n\\n ## 文件列表\\n 1. `generate_summary.py`: 主生成脚本。根据输入参数,调用模板生成工作总结报告。\\n\\n ## 使用方式\\n ### 直接运行(示例)\\n ```bash\\n python generate_summary.py\\n ```\\n 将使用内置的示例数据生成一份报告。\\n\\n ### 通过JSON输入运行\\n ```bash\\n python generate_summary.py '{\\\"work_content\\\":\\\"...\\\", \\\"project_achievements\\\":\\\"...\\\", \\\"personal_growth\\\":\\\"...\\\", \\\"period\\\":\\\"本月\\\", \\\"report_style\\\":\\\"简洁\\\"}'\\n ```\\n\\n ### 作为模块导入\\n ```python\\n from generate_summary import generate_work_summary\\n report = generate_work_summary(work_content=\\\"...\\\", ...)\\n ```\\n\\n ## 依赖\\n 本脚本仅依赖Python标准库,无需额外安装包。\\n```"; + + // 模式1:还原为原字符 + String result1 = removeEscapeCharacters(testStr); + System.out.println("还原模式结果:\n" + result1); + // 输出: + // Hello + // World "Java"\转义符(回车) + + // 模式2:直接删除转义符 + String result2 = removeEscapeCharacters(testStr, true); + System.out.println("\n删除模式结果:" + result2); + // 输出:HelloWorld"Java"转义符 + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 521ba92..b856945 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -103,7 +103,7 @@ jetcache: web: upload: - path: /kexue/agent-skills/upload/ + path: /kexue/agentSkills/upload/ # 支付配置 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index e9df013..251e350 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -100,7 +100,7 @@ jetcache: web: upload: - path: /kexue/agent-skills/upload/ + path: /kexue/agentSkills/upload/ payment: # 微信支付配置 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 63ab60f..399a085 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -89,3 +89,8 @@ sms: template-param-name: code template-cache: true +# 上传配置 +web: + upload: + path: /kexue/agentSkills/upload/ +