feat(content): 添加从目录导入Excel功能并优化内容管理
- 新增从指定目录批量导入Excel数据到CmsContent的功能 - 添加ImportPathDto请求参数实体类 - 实现importFromPath方法支持目录遍历和文件批量导入 - 添加truncateTable方法用于清空表数据 - 优化Excel导入逻辑增加异常处理和空值检查 - 调整批量处理大小从100改为10 - 更新审核状态和发布状态的描述文案 - 修复分享次数和官方标识字段的默认值设置 - 将Servlet API从javax迁移到jakarta - 更新README.md完善项目文档 - 优化技能解析逻辑支持多层级目录结构 - 修复AI模型生成中的标签选择和参数验证问题
This commit is contained in:
parent
59a44f9c53
commit
3df611f809
193
README.md
193
README.md
|
|
@ -1,3 +1,192 @@
|
|||
# agent-skill-backend
|
||||
# 可学AI-skills平台后端
|
||||
|
||||
agent-skill-backend
|
||||
## 项目简介
|
||||
|
||||
可学AI-skills平台是一个基于Spring Boot的智能技能管理系统,提供技能生成、内容管理、用户认证、支付等功能。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **基础框架**:Spring Boot 3.2.2
|
||||
- **持久层**:MyBatis 3.0.3
|
||||
- **数据库**:MySQL
|
||||
- **缓存**:Redis
|
||||
- **认证**:Sa-Token 1.38.0
|
||||
- **模板引擎**:Thymeleaf
|
||||
- **API文档**:Swagger 3.0.0
|
||||
- **文件处理**:sevenzipjbinding 16.02-2.01
|
||||
- **短信服务**:SMS4J 3.3.5
|
||||
- **分布式锁**:Redisson 3.23.5
|
||||
- **AI集成**:DeepSeek、GLM-4.6v
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
backend/
|
||||
├── .mvn/ # Maven包装器
|
||||
├── db/ # 数据库脚本
|
||||
├── src/
|
||||
│ ├── main/
|
||||
│ │ ├── java/com/kexue/skills/ # 主源码
|
||||
│ │ │ ├── annotation/ # 自定义注解
|
||||
│ │ │ ├── aspect/ # AOP切面
|
||||
│ │ │ ├── common/ # 通用工具和常量
|
||||
│ │ │ ├── config/ # 配置类
|
||||
│ │ │ ├── controller/ # 控制器
|
||||
│ │ │ ├── entity/ # 实体类
|
||||
│ │ │ ├── exception/ # 异常处理
|
||||
│ │ │ ├── interceptor/ # 拦截器
|
||||
│ │ │ ├── mapper/ # 数据访问层
|
||||
│ │ │ ├── service/ # 服务层
|
||||
│ │ │ ├── task/ # 定时任务
|
||||
│ │ │ ├── utils/ # 工具类
|
||||
│ │ │ └── SkillsApp.java # 应用入口
|
||||
│ │ └── resources/ # 资源文件
|
||||
│ │ ├── mapper/ # MyBatis映射文件
|
||||
│ │ ├── sql/ # SQL脚本
|
||||
│ │ ├── static/ # 静态资源
|
||||
│ │ ├── templates/ # Thymeleaf模板
|
||||
│ │ ├── application-*.yml # 配置文件
|
||||
│ │ └── logback-spring.xml # 日志配置
|
||||
│ └── test/ # 测试代码
|
||||
├── .gitignore # Git忽略文件
|
||||
├── Dockerfile # Docker构建文件
|
||||
├── README.md # 项目说明
|
||||
├── mvnw.cmd # Maven包装器脚本
|
||||
└── pom.xml # Maven依赖配置
|
||||
```
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 用户认证与授权
|
||||
- 基于Sa-Token的认证系统
|
||||
- 支持账号密码登录
|
||||
- 支持手机验证码登录
|
||||
- 角色权限管理
|
||||
- 防重复提交
|
||||
|
||||
### 2. 内容管理系统
|
||||
- 内容分类管理
|
||||
- 内容标签管理
|
||||
- 内容发布与管理
|
||||
- 内容点赞与浏览统计
|
||||
|
||||
### 3. 技能生成系统
|
||||
- 技能上传与解析(支持RAR等压缩格式)
|
||||
- 技能结构分析
|
||||
- 技能介绍生成
|
||||
|
||||
### 4. 支付系统
|
||||
- 微信支付集成
|
||||
- 支付宝集成
|
||||
- 支付订单管理
|
||||
|
||||
### 5. 账户管理
|
||||
- 账户余额管理
|
||||
- 积分管理
|
||||
- 交易记录
|
||||
|
||||
### 6. 系统管理
|
||||
- 菜单管理
|
||||
- 角色管理
|
||||
- 字典管理
|
||||
- 系统日志
|
||||
|
||||
### 7. AI集成
|
||||
- DeepSeek模型集成
|
||||
- GLM-4.6v模型集成
|
||||
- 智能内容生成
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 环境要求
|
||||
- JDK 17+
|
||||
- Maven 3.6+
|
||||
- MySQL 5.7+
|
||||
- Redis 5.0+
|
||||
|
||||
### 配置说明
|
||||
1. 修改 `application-dev.yml` 文件中的数据库连接信息
|
||||
2. 修改 `application.yml` 文件中的Redis连接信息
|
||||
3. 修改 `application.yml` 文件中的AI模型API密钥
|
||||
4. 修改 `application.yml` 文件中的短信服务配置
|
||||
|
||||
### 数据库初始化
|
||||
1. 执行 `db/create_tables.sql` 创建数据库表
|
||||
2. 执行 `db/init_data.sql` 初始化基础数据
|
||||
|
||||
### 启动项目
|
||||
```bash
|
||||
# 编译项目
|
||||
mvn clean compile
|
||||
|
||||
# 运行项目
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
### 访问地址
|
||||
- 项目首页:http://localhost:8080
|
||||
- Swagger文档:http://localhost:8080/doc.html
|
||||
|
||||
## 主要API
|
||||
|
||||
### 用户认证
|
||||
- `POST /api/login` - 用户登录
|
||||
- `POST /api/logout` - 用户登出
|
||||
- `GET /api/currentUser` - 获取当前用户信息
|
||||
|
||||
### 内容管理
|
||||
- `GET /api/cms/content/list` - 获取内容列表
|
||||
- `POST /api/cms/content/save` - 保存内容
|
||||
- `DELETE /api/cms/content/delete` - 删除内容
|
||||
|
||||
### 技能管理
|
||||
- `POST /api/skill/upload` - 上传技能
|
||||
- `POST /api/skill/analyze` - 分析技能结构
|
||||
- `POST /api/skill/genIntroduce` - 生成技能介绍
|
||||
|
||||
### 支付管理
|
||||
- `POST /api/pay/wx` - 微信支付
|
||||
- `POST /api/pay/alipay` - 支付宝支付
|
||||
- `GET /api/payment/order/list` - 获取支付订单列表
|
||||
|
||||
## 部署说明
|
||||
|
||||
### Docker部署
|
||||
1. 构建Docker镜像
|
||||
```bash
|
||||
docker build -t agent-skills .
|
||||
```
|
||||
|
||||
2. 运行Docker容器
|
||||
```bash
|
||||
docker run -p 8080:8080 --name agent-skills agent-skills
|
||||
```
|
||||
|
||||
### 生产环境部署
|
||||
1. 打包项目
|
||||
```bash
|
||||
mvn clean package -DskipTests
|
||||
|
||||
配置文件直接打在jar包内
|
||||
```
|
||||
|
||||
2. 部署jar包
|
||||
```bash
|
||||
java -jar agentSkills.jar --spring.profiles.active=prod
|
||||
或者执行脚本启动
|
||||
./start.sh
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 项目使用Redis作为缓存,需要确保Redis服务正常运行
|
||||
2. 项目使用阿里云短信服务,需要配置相关参数
|
||||
3. 项目使用AI模型API,需要配置相关API密钥
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目仅供内部使用,未经授权不得用于商业用途。
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题,请联系项目维护人员。
|
||||
|
|
|
|||
8
pom.xml
8
pom.xml
|
|
@ -146,11 +146,11 @@
|
|||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Servlet API for javax.servlet.http -->
|
||||
<!-- Servlet API for jakarta.servlet.http (Spring Boot 3.x uses Jakarta EE 9+) -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<version>6.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
|
|
|||
|
|
@ -84,92 +84,6 @@ public class SkillZipParser {
|
|||
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文件中提取技能信息
|
||||
|
|
@ -197,7 +111,7 @@ public class SkillZipParser {
|
|||
// 使用简单接口
|
||||
ISimpleInArchive simpleInArchive = archive.getSimpleInterface();
|
||||
|
||||
// 遍历所有文件条目
|
||||
// 首先尝试在根目录查找md文件
|
||||
for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
|
||||
// 检查是否是根目录下的md文件
|
||||
String path = item.getPath();
|
||||
|
|
@ -224,6 +138,55 @@ public class SkillZipParser {
|
|||
break; // 找到一个md文件后就停止
|
||||
}
|
||||
}
|
||||
|
||||
// 如果根目录没有找到md文件,检查根目录下的文件夹
|
||||
if (!foundMdFile) {
|
||||
// 收集根目录下的文件夹
|
||||
List<String> rootFolders = new ArrayList<>();
|
||||
for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
|
||||
if (item.isFolder()) {
|
||||
String path = item.getPath();
|
||||
// 确保是根目录下的文件夹(路径中不包含/)
|
||||
if (!path.contains("/") && !path.contains("\\")) {
|
||||
rootFolders.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查每个根目录文件夹下的md文件
|
||||
for (String folder : rootFolders) {
|
||||
for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
|
||||
if (!item.isFolder()) {
|
||||
String path = item.getPath();
|
||||
// 检查是否是该文件夹下的md文件
|
||||
if (path.endsWith(".md") && (path.equals(folder + "/skill.md") || path.equals(folder + "/SKILL.md") ||
|
||||
path.equals(folder + "/readme.md") || path.equals(folder + "/README.md"))) {
|
||||
// 读取文件内容
|
||||
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文件后就停止
|
||||
}
|
||||
}
|
||||
}
|
||||
if (foundMdFile) break; // 找到一个md文件后就停止
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到 md 文件,使用默认值
|
||||
|
|
@ -277,16 +240,58 @@ public class SkillZipParser {
|
|||
}
|
||||
}
|
||||
|
||||
// 如果根目录没有找到md文件,检查根目录下的文件夹
|
||||
if (!foundMdFile) {
|
||||
// 收集根目录下的文件夹
|
||||
List<String> rootFolders = new ArrayList<>();
|
||||
entries = zipFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
if (entry.isDirectory()) {
|
||||
String path = entry.getName();
|
||||
// 确保是根目录下的文件夹(路径中不包含/,或者以/结尾且前面没有/)
|
||||
if (path.endsWith("/") && !path.substring(0, path.length() - 1).contains("/")) {
|
||||
rootFolders.add(path.substring(0, path.length() - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查每个根目录文件夹下的md文件
|
||||
for (String folder : rootFolders) {
|
||||
entries = zipFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
if (!entry.isDirectory()) {
|
||||
String path = entry.getName();
|
||||
// 检查是否是该文件夹下的md文件
|
||||
if (path.endsWith(".md") && (path.equals(folder + "/skill.md") || path.equals(folder + "/SKILL.md") ||
|
||||
path.equals(folder + "/readme.md") || path.equals(folder + "/README.md"))) {
|
||||
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文件后就停止
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (foundMdFile) 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;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.kexue.skills.entity.CmsContent;
|
|||
import com.kexue.skills.entity.base.IdDto;
|
||||
import com.kexue.skills.entity.dto.CmsContentDto;
|
||||
import com.kexue.skills.entity.dto.QueryContentDto;
|
||||
import com.kexue.skills.entity.request.ImportPathDto;
|
||||
import com.kexue.skills.service.CmsContentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
|
@ -312,4 +313,24 @@ public class CmsContentController {
|
|||
String title = cmsContentService.getTitle(contentId);
|
||||
return CommonResult.success(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定目录导入Excel数据到CmsContent
|
||||
*
|
||||
* @param importPathDto 导入路径请求参数
|
||||
* @param createBy 创建人
|
||||
* @return 导入结果
|
||||
*/
|
||||
@PostMapping("/importFromPath")
|
||||
@Operation(summary = "从目录导入Excel数据", description = "从指定目录导入Excel数据到CmsContent")
|
||||
@RequireAuth
|
||||
public CommonResult<Integer> importFromPath(@RequestBody ImportPathDto importPathDto, @RequestParam("createBy") String createBy) {
|
||||
try {
|
||||
int successCount = cmsContentService.importFromPath(importPathDto, createBy);
|
||||
return CommonResult.success(successCount);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return CommonResult.failed("导入失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,8 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -42,18 +42,6 @@ public class SkillGenController {
|
|||
return CommonResult.success(skillGenService.preGenerateV2(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成技能V2
|
||||
*
|
||||
* @param request 生成请求
|
||||
* @return 生成结果
|
||||
*/
|
||||
@PostMapping("/preGenerateV2")
|
||||
@Operation(summary = "预生成技能V2", description = "使用新模型生成技能")
|
||||
public CommonResult<SkillResponse> preGenerateV2(@RequestBody SkillPreGenRequest request) {
|
||||
return CommonResult.success(skillGenService.preGenerateV2(request));
|
||||
}
|
||||
|
||||
@PostMapping("/generate")
|
||||
@Operation(summary = "生成技能", description = "生成技能")
|
||||
public CommonResult<CmsContent> generate(@RequestBody SkillGenRequest request) {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ package com.kexue.skills.controller;
|
|||
import com.kexue.skills.annotation.RequireAuth;
|
||||
import com.kexue.skills.entity.SysUser;
|
||||
import com.kexue.skills.entity.dto.SysUserDto;
|
||||
import com.kexue.skills.entity.request.ResetPasswordDto;
|
||||
import com.kexue.skills.entity.request.ResetPwdDto;
|
||||
import com.kexue.skills.entity.request.AdminResetPasswordDto;
|
||||
import com.kexue.skills.entity.request.*;
|
||||
import com.kexue.skills.exception.BizException;
|
||||
import com.kexue.skills.service.SysUserService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -17,7 +15,6 @@ import com.kexue.skills.common.CacheManager;
|
|||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.common.CommonResult;
|
||||
import com.kexue.skills.entity.base.IdDto;
|
||||
import com.kexue.skills.entity.request.LoginUserDto;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
/**
|
||||
|
|
@ -86,7 +83,7 @@ public class SysUserController {
|
|||
*/
|
||||
@PostMapping("/update")
|
||||
@Operation(summary = "更新用户", description = "更新用户")
|
||||
public CommonResult<SysUser> update(@RequestBody SysUser SysUser) {
|
||||
public CommonResult<SysUser> update(@RequestBody SysUserUpdateDto SysUser) {
|
||||
return CommonResult.success(sysUserService.update(SysUser));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,14 +97,14 @@ public class CmsContent extends BaseEntity implements Serializable {
|
|||
@Schema(description ="审核人名称")
|
||||
private String reviewerName;
|
||||
|
||||
@Schema(description ="审核状态(1草稿,2待审核,3审核通过,4审核拒绝)")
|
||||
@Schema(description ="审核状态(1未发布,2待审核,3审核通过,4审核未通过)")
|
||||
private Integer auditStatus;
|
||||
|
||||
@Schema(description ="审核意见")
|
||||
private String auditComment;
|
||||
|
||||
@Schema(description ="发布状态(1未发布,2已发布,3已下架)")
|
||||
private Integer publishStatus;
|
||||
@Schema(description ="发布状态(1未发布,2已发布,3已下架)--> 公有还是私有:1私有,2公有")
|
||||
private Integer publishStatus;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="发布时间")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.kexue.skills.entity.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 导入路径请求参数
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "ImportPathDto", description = "导入路径请求参数")
|
||||
public class ImportPathDto implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "是否追加,true表示追加,false表示清空表")
|
||||
private boolean append;
|
||||
|
||||
@Schema(description = "文件目录")
|
||||
private String filePath;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.kexue.skills.entity.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户更新请求参数
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "SysUserUpdateDto", description = "用户更新请求参数")
|
||||
public class SysUserUpdateDto implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号")
|
||||
private String tel;
|
||||
|
||||
@Schema(description = "状态(1-正常,0-禁用)")
|
||||
private Integer enable;
|
||||
|
||||
}
|
||||
|
|
@ -225,4 +225,11 @@ public interface CmsContentMapper {
|
|||
* @return 总记录数
|
||||
*/
|
||||
int getPageListByUserCreatedCount(@Param("userId") Long userId, @Param("publishStatus") Integer publishStatus);
|
||||
|
||||
/**
|
||||
* 清空表数据
|
||||
*
|
||||
* @return 影响行数
|
||||
*/
|
||||
int truncateTable();
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.kexue.skills.service;
|
|||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.entity.CmsContent;
|
||||
import com.kexue.skills.entity.dto.CmsContentDto;
|
||||
import com.kexue.skills.entity.request.ImportPathDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -191,4 +192,13 @@ public interface CmsContentService extends BaseService {
|
|||
* @return title字段的内容
|
||||
*/
|
||||
String getTitle(Long contentId);
|
||||
|
||||
/**
|
||||
* 从指定目录导入Excel数据到CmsContent
|
||||
*
|
||||
* @param importPathDto 导入路径请求参数
|
||||
* @param createBy 创建人
|
||||
* @return 导入结果
|
||||
*/
|
||||
int importFromPath(ImportPathDto importPathDto, String createBy);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.kexue.skills.service;
|
||||
|
||||
import com.kexue.skills.entity.PaymentOrder;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.kexue.skills.entity.request.LoginDto;
|
|||
import com.kexue.skills.entity.request.LoginUserDto;
|
||||
import com.kexue.skills.entity.request.PhoneLoginDto;
|
||||
import com.kexue.skills.entity.request.ResetPasswordDto;
|
||||
import com.kexue.skills.entity.request.SysUserUpdateDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -59,6 +60,14 @@ public interface SysUserService extends BaseService {
|
|||
*/
|
||||
SysUser update(SysUser sysUser);
|
||||
|
||||
/**
|
||||
* 修改用户数据
|
||||
*
|
||||
* @param sysUserUpdateDto 用户更新请求参数
|
||||
* @return 实例对象
|
||||
*/
|
||||
SysUser update(SysUserUpdateDto sysUserUpdateDto);
|
||||
|
||||
/**
|
||||
* 通过主键删除数据
|
||||
*
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.kexue.skills.entity.CmsContentView;
|
|||
import com.kexue.skills.entity.CmsContentLike;
|
||||
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||
import com.kexue.skills.entity.dto.CmsContentDto;
|
||||
import com.kexue.skills.entity.request.ImportPathDto;
|
||||
import com.kexue.skills.mapper.CmsContentMapper;
|
||||
import com.kexue.skills.mapper.CmsContentViewMapper;
|
||||
import com.kexue.skills.mapper.CmsContentLikeMapper;
|
||||
|
|
@ -21,6 +22,8 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
|
@ -311,9 +314,16 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
if (cmsContent.getCommentCount() == null) {
|
||||
cmsContent.setCommentCount(0);
|
||||
}
|
||||
if (cmsContent.getShareCount() == null) {
|
||||
cmsContent.setShareCount(0);
|
||||
}
|
||||
if (cmsContent.getSort() == null) {
|
||||
cmsContent.setSort(0);
|
||||
}
|
||||
if (cmsContent.getIsOfficial() == null) {
|
||||
cmsContent.setIsOfficial(false);
|
||||
}
|
||||
cmsContent.setAuthorId(StpUtil.getLoginIdAsLong());
|
||||
// 保存数据
|
||||
this.cmsContentMapper.insert(cmsContent);
|
||||
return cmsContent;
|
||||
|
|
@ -729,7 +739,7 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
@Override
|
||||
public int importFromExcel(byte[] fileBytes, String createBy) {
|
||||
int successCount = 0;
|
||||
final int BATCH_SIZE = 100; // 批量处理大小
|
||||
final int BATCH_SIZE = 10; // 批量处理大小
|
||||
|
||||
try (InputStream inputStream = new ByteArrayInputStream(fileBytes);
|
||||
ExcelReader reader = ExcelUtil.getReader(inputStream)) {
|
||||
|
|
@ -757,76 +767,91 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
|
||||
// 从第二行开始读取数据(第一行为标题行)
|
||||
for (int rowIndex = 1; rowIndex < totalRows; rowIndex++) {
|
||||
List<Object> rowList = reader.readRow(rowIndex);
|
||||
if (rowList == null || rowList.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 转换为Map<String, Object>
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
for (int i = 0; i < headerList.size() && i < rowList.size(); i++) {
|
||||
row.put(headerList.get(i), rowList.get(i));
|
||||
}
|
||||
if (row.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CmsContent cmsContent = new CmsContent();
|
||||
|
||||
// 设置创建时间和更新时间
|
||||
cmsContent.setCreateTime(now);
|
||||
cmsContent.setUpdateTime(now);
|
||||
cmsContent.setCreateBy(createBy);
|
||||
cmsContent.setUpdateBy(createBy);
|
||||
|
||||
// 设置默认值
|
||||
cmsContent.setDeleteFlag(0);
|
||||
cmsContent.setAuditStatus(1); // 默认草稿状态
|
||||
cmsContent.setPublishStatus(1); // 默认未发布状态
|
||||
cmsContent.setViewCount(0);
|
||||
cmsContent.setLikeCount(0);
|
||||
cmsContent.setCommentCount(0);
|
||||
cmsContent.setSort(0);
|
||||
|
||||
// 读取Excel中的字段
|
||||
// content_id - 数据库自动生成,不需要读取
|
||||
cmsContent.setTitle(getStringValue(row, "title"));
|
||||
cmsContent.setTitleEn(getStringValue(row, "title_en"));
|
||||
cmsContent.setOrigin(getStringValue(row, "origin"));
|
||||
cmsContent.setTags(Objects.requireNonNull(getStringValue(row, "tags")).replaceAll(" ",""));
|
||||
cmsContent.setIcon(getStringValue(row, "icon"));
|
||||
cmsContent.setIsOfficial(getBooleanValue(row, "is_official"));
|
||||
cmsContent.setPrice(getBigDecimalValue(row, "price"));
|
||||
cmsContent.setLikeCount(getIntegerValue(row, "like_count"));
|
||||
cmsContent.setShareCount(getIntegerValue(row, "share_count"));
|
||||
cmsContent.setContentType(getIntegerValue(row, "content_type"));
|
||||
cmsContent.setContent(getStringValue(row, "content"));
|
||||
cmsContent.setContentEn(getStringValue(row, "content_en"));
|
||||
cmsContent.setAuditStatus(getIntegerValue(row, "audit_status"));
|
||||
cmsContent.setPublishStatus(getIntegerValue(row, "publish_status"));
|
||||
cmsContent.setPublishTime(getDateValue(row, "publish_time"));
|
||||
cmsContent.setViewCount(getIntegerValue(row, "view_count"));
|
||||
cmsContent.setCommentCount(getIntegerValue(row, "comment_count"));
|
||||
cmsContent.setIsPaid(getIntegerValue(row, "is_paid"));
|
||||
cmsContent.setSupportPointsPay(getIntegerValue(row, "support_points_pay"));
|
||||
cmsContent.setDescription(getStringValue(row, "description"));
|
||||
cmsContent.setDescriptionEn(getStringValue(row, "description_en"));
|
||||
cmsContent.setIntroduce(getStringValue(row, "introduce"));
|
||||
cmsContent.setIntroduceEn(getStringValue(row, "introduce_en"));
|
||||
// create_time - 由系统生成,不需要读取
|
||||
// update_time - 由系统生成,不需要读取
|
||||
cmsContent.setDeleteFlag(getIntegerValue(row, "delete_flag"));
|
||||
|
||||
batchList.add(cmsContent);
|
||||
|
||||
// 达到批量大小或最后一行时,执行批量插入
|
||||
if (batchList.size() >= BATCH_SIZE || rowIndex == totalRows - 1) {
|
||||
if (!batchList.isEmpty()) {
|
||||
// 执行批量插入
|
||||
this.cmsContentMapper.batchInsert(batchList);
|
||||
successCount += batchList.size();
|
||||
batchList.clear(); // 清空批次
|
||||
try {
|
||||
List<Object> rowList = reader.readRow(rowIndex);
|
||||
if (rowList == null || rowList.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 转换为Map<String, Object>
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
for (int i = 0; i < headerList.size() && i < rowList.size(); i++) {
|
||||
row.put(headerList.get(i), rowList.get(i));
|
||||
}
|
||||
if (row.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CmsContent cmsContent = new CmsContent();
|
||||
|
||||
// 设置创建时间和更新时间
|
||||
cmsContent.setCreateTime(now);
|
||||
cmsContent.setUpdateTime(now);
|
||||
cmsContent.setCreateBy(createBy);
|
||||
cmsContent.setUpdateBy(createBy);
|
||||
|
||||
// 设置默认值
|
||||
cmsContent.setDeleteFlag(0);
|
||||
cmsContent.setAuditStatus(1); // 默认草稿状态
|
||||
cmsContent.setPublishStatus(1); // 默认未发布状态
|
||||
cmsContent.setViewCount(0);
|
||||
cmsContent.setLikeCount(0);
|
||||
cmsContent.setCommentCount(0);
|
||||
cmsContent.setSort(0);
|
||||
|
||||
// 读取Excel中的字段
|
||||
// content_id - 数据库自动生成,不需要读取
|
||||
cmsContent.setTitle(getStringValue(row, "title"));
|
||||
cmsContent.setTitleEn(getStringValue(row, "title_en"));
|
||||
cmsContent.setOrigin(getStringValue(row, "origin"));
|
||||
|
||||
// 处理tags字段,避免NullPointerException
|
||||
String tags = getStringValue(row, "tags");
|
||||
if (tags == null) {
|
||||
System.err.println("第 " + rowIndex + " 行数据的tags字段为null,跳过该条记录");
|
||||
continue;
|
||||
}
|
||||
cmsContent.setTags(tags.replaceAll(" ",""));
|
||||
|
||||
cmsContent.setIcon(getStringValue(row, "icon"));
|
||||
cmsContent.setIsOfficial(getBooleanValue(row, "is_official"));
|
||||
cmsContent.setPrice(getBigDecimalValue(row, "price"));
|
||||
cmsContent.setLikeCount(getIntegerValue(row, "like_count"));
|
||||
cmsContent.setShareCount(getIntegerValue(row, "share_count"));
|
||||
cmsContent.setContentType(getIntegerValue(row, "content_type"));
|
||||
cmsContent.setContent(getStringValue(row, "content"));
|
||||
cmsContent.setContentEn(getStringValue(row, "content_en"));
|
||||
cmsContent.setAuditStatus(getIntegerValue(row, "audit_status"));
|
||||
cmsContent.setPublishStatus(getIntegerValue(row, "publish_status"));
|
||||
cmsContent.setPublishTime(getDateValue(row, "publish_time"));
|
||||
cmsContent.setViewCount(getIntegerValue(row, "view_count"));
|
||||
cmsContent.setCommentCount(getIntegerValue(row, "comment_count"));
|
||||
cmsContent.setIsPaid(getIntegerValue(row, "is_paid"));
|
||||
cmsContent.setSupportPointsPay(getIntegerValue(row, "support_points_pay"));
|
||||
cmsContent.setDescription(getStringValue(row, "description"));
|
||||
cmsContent.setDescriptionEn(getStringValue(row, "description_en"));
|
||||
cmsContent.setIntroduce(getStringValue(row, "introduce"));
|
||||
cmsContent.setIntroduceEn(getStringValue(row, "introduce_en"));
|
||||
// create_time - 由系统生成,不需要读取
|
||||
// update_time - 由系统生成,不需要读取
|
||||
cmsContent.setDeleteFlag(getIntegerValue(row, "delete_flag"));
|
||||
|
||||
batchList.add(cmsContent);
|
||||
|
||||
// 达到批量大小或最后一行时,执行批量插入
|
||||
if (batchList.size() >= BATCH_SIZE || rowIndex == totalRows - 1) {
|
||||
if (!batchList.isEmpty()) {
|
||||
// 执行批量插入
|
||||
this.cmsContentMapper.batchInsert(batchList);
|
||||
successCount += batchList.size();
|
||||
batchList.clear(); // 清空批次
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("处理第 " + rowIndex + " 行数据时出错: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
// 跳过当前行,继续处理下一行
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
|
@ -942,4 +967,66 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
}
|
||||
return cmsContent.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int importFromPath(ImportPathDto importPathDto, String createBy) {
|
||||
int totalSuccessCount = 0;
|
||||
|
||||
try {
|
||||
// 检查目录是否存在
|
||||
File directory = new File(importPathDto.getFilePath());
|
||||
if (!directory.exists() || !directory.isDirectory()) {
|
||||
System.err.println("目录不存在或不是有效目录: " + importPathDto.getFilePath());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 如果不是追加模式,清空表
|
||||
if (!importPathDto.isAppend()) {
|
||||
cmsContentMapper.truncateTable();
|
||||
}
|
||||
|
||||
// 读取目录下所有 Excel 文件(排除 Office 临时锁定文件)
|
||||
File[] files = directory.listFiles((dir, name) -> {
|
||||
// 跳过以 ~$ 开头的临时锁定文件
|
||||
if (name.startsWith("~$")) {
|
||||
return false;
|
||||
}
|
||||
return name.endsWith(".xls") || name.endsWith(".xlsx");
|
||||
});
|
||||
if (files == null || files.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 记录文件总数
|
||||
int totalFiles = files.length;
|
||||
System.out.println("总共发现 " + totalFiles + " 个 Excel 文件需要导入");
|
||||
|
||||
// 遍历所有 Excel 文件并导入
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
File file = files[i];
|
||||
System.out.println("当前处理第 " + (i + 1) + " 个文件,文件名称是:" + file.getName());
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
// 读取文件内容到字节数组
|
||||
byte[] fileBytes = new byte[(int) file.length()];
|
||||
fis.read(fileBytes);
|
||||
|
||||
// 调用现有的 importFromExcel 方法进行导入
|
||||
int successCount = importFromExcel(fileBytes, createBy);
|
||||
totalSuccessCount += successCount;
|
||||
System.out.println("第 " + (i + 1) + " 个文件导入成功,导入了 " + successCount + " 条记录");
|
||||
} catch (Exception e) {
|
||||
System.err.println("导入文件失败: " + file.getAbsolutePath());
|
||||
e.printStackTrace();
|
||||
// 单个文件导入失败不影响其他文件
|
||||
continue;
|
||||
}
|
||||
}
|
||||
System.out.println("导入完成,共处理 " + totalFiles + " 个文件,成功导入 " + totalSuccessCount + " 条记录");
|
||||
} catch (Exception e) {
|
||||
System.err.println("导入操作失败: " + importPathDto.getFilePath());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return totalSuccessCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,8 +14,8 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import cn.dev33.satoken.stp.StpUtil;
|
|||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.kexue.skills.common.Assert;
|
||||
import com.kexue.skills.common.util.HttpUtil;
|
||||
import com.kexue.skills.config.DeepSeekConfig;
|
||||
import com.kexue.skills.config.GlmConfig;
|
||||
|
|
@ -140,14 +141,14 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
StringBuilder tagsList = new StringBuilder();
|
||||
for (int i = 0; i < tags.size(); i++) {
|
||||
CmsTag tag = tags.get(i);
|
||||
tagsList.append(tag.getTagName());
|
||||
tagsList.append(tag.getTagId()+"."+tag.getTagName());
|
||||
if (i < tags.size() - 1) {
|
||||
tagsList.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
// 构建系统消息内容
|
||||
String systemContent = "你是一个专业的AI技能设计助手。请根据agent skills撰写规范,按照用户提出的主题描述及参考文件,生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"" + tagsList.toString() + "\",并简述这个skill的具体价值点。输出json格式,仅输出以上所提到的名称、描述、标签、价值点,节点名称分别为name、description、tags、value_point,节点内容以中文形式返回。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。";
|
||||
String systemContent = "你是一个专业的AI技能设计助手。请根据agent skills撰写规范,按照用户提出的主题描述及参考文件,生成这个skill的名称、描述,并从以下标签列表中选择至少3个标签:\"" + tagsList.toString() + "\",tags只需要返回序号数组,并简述这个skill的具体价值点。输出json格式,仅输出以上所提到的名称、描述、标签、价值点,节点名称分别为name、description、tags、value_point,节点内容以中文形式返回。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。";
|
||||
|
||||
// 准备文件URL列表
|
||||
List<String> fileUrls = new ArrayList<>();
|
||||
|
|
@ -198,6 +199,11 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
@Override
|
||||
public CmsContent generate(SkillGenRequest request) {
|
||||
log.info("生成技能请求: {}", request);
|
||||
// 参数验证,确保每个参数都必须传递
|
||||
Assert.notEmpty(request.getName(), "技能名称不能为空");
|
||||
Assert.notEmpty(request.getDescription(), "技能描述不能为空");
|
||||
Assert.notEmpty(request.getTags(), "技能标签不能为空");
|
||||
Assert.notEmpty(request.getRequirement(), "技能摘要不能为空");
|
||||
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
|
||||
|
||||
// 从数据库中读取cms_tag表的标签信息
|
||||
|
|
@ -223,7 +229,7 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
}
|
||||
}
|
||||
}
|
||||
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 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 字段,必须保证content内容的缩进为2个字符;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");
|
||||
|
|
@ -467,7 +473,7 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
request.setName(defaultSkillName);
|
||||
request.setDescription("未知");
|
||||
request.setIntroduce("未知");
|
||||
request.setTags(Arrays.asList("10001", "10002"));
|
||||
request.setTags(Arrays.asList("1001", "1000"));
|
||||
}
|
||||
|
||||
// 3. 使用SkillGenRequest中的信息生成yaml
|
||||
|
|
@ -482,9 +488,19 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
|
||||
// 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());
|
||||
if (Objects.nonNull(cmsContent.getTags())) {
|
||||
String s = cmsContent.getTags().split(",")[0];
|
||||
long l = 1000;// 默认值
|
||||
try {
|
||||
l = Long.parseLong(s);
|
||||
} catch (NumberFormatException e) {
|
||||
// 异常是因为大模型返回的tag带中文了,忽略
|
||||
}
|
||||
long finalL = l;
|
||||
List<CmsTag> list = tags.stream().filter(tag -> tag.getTagId() == finalL).toList();
|
||||
if (CollectionUtil.isNotEmpty(list)){
|
||||
cmsContent.setIcon(list.get(0).getIcon());
|
||||
}
|
||||
}
|
||||
// 删除临时文件
|
||||
tempFile.delete();
|
||||
|
|
@ -518,7 +534,7 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
- skill的核心描述(description);
|
||||
- 详细功能介绍(introduce);
|
||||
- 从指定标签列表中选取至少3个适配标签(tagList),标签范围:TAG_LIST;
|
||||
- 选择标签的时候只需要输出对应的标签编号
|
||||
- 选择标签的时候只需要输出对应的标签编号,特别注意:只需要标签编号
|
||||
请将最终结果以JSON格式输出,JSON结构必须包含:name(技能名称),description(技能描述)、introduce(功能介绍)、tagList(标签列表),无需额外说明,仅输出符合要求的JSON内容。
|
||||
""";
|
||||
systemContent = systemContent.replace("TAG_LIST", tagsList.toString());
|
||||
|
|
@ -554,7 +570,7 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
if (skillJson.containsKey("tagList")) {
|
||||
request.setTags(skillJson.getJSONArray("tagList").toJavaList(String.class));
|
||||
} else {
|
||||
request.setTags(Arrays.asList("10001", "10002"));
|
||||
request.setTags(Arrays.asList("1001", "1002"));
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.kexue.skills.entity.dto.ContentPurchaseDto;
|
|||
import com.kexue.skills.entity.dto.SessionDto;
|
||||
import com.kexue.skills.entity.dto.SysUserDto;
|
||||
import com.kexue.skills.entity.request.*;
|
||||
import com.kexue.skills.entity.request.SysUserUpdateDto;
|
||||
import com.kexue.skills.mapper.*;
|
||||
import com.kexue.skills.service.SysUserService;
|
||||
import com.kexue.skills.utils.MD5Util;
|
||||
|
|
@ -194,6 +195,69 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
return queryById(sysUser.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户数据
|
||||
*
|
||||
* @param sysUserUpdateDto 用户更新请求参数
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
@CacheInvalidate(name = "sysUser:", key = "#sysUserUpdateDto.userId")
|
||||
@CacheInvalidate(name = "sysUser:username:", key = "#sysUserUpdateDto.userName")
|
||||
public SysUser update(SysUserUpdateDto sysUserUpdateDto) {
|
||||
if (Objects.nonNull(sysUserUpdateDto.getUserId())){
|
||||
// 查询用户信息
|
||||
SysUser sysUser = sysUserMapper.queryById(sysUserUpdateDto.getUserId());
|
||||
Assert.notNull(sysUser, "用户不存在");
|
||||
|
||||
// 校验用户名是否已经存在
|
||||
if (sysUserUpdateDto.getUserName() != null && !sysUserUpdateDto.getUserName().isEmpty()) {
|
||||
SysUser existingUser = sysUserMapper.getByUsername(sysUserUpdateDto.getUserName());
|
||||
Assert.isTrue(existingUser == null || existingUser.getUserId().equals(sysUserUpdateDto.getUserId()), "用户名已存在");
|
||||
sysUser.setUserName(sysUserUpdateDto.getUserName());
|
||||
}
|
||||
|
||||
// 更新邮箱
|
||||
if (sysUserUpdateDto.getEmail() != null) {
|
||||
sysUser.setEmail(sysUserUpdateDto.getEmail());
|
||||
}
|
||||
|
||||
// 更新手机号
|
||||
if (sysUserUpdateDto.getTel() != null) {
|
||||
sysUser.setTel(sysUserUpdateDto.getTel());
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
if (sysUserUpdateDto.getEnable() != null) {
|
||||
sysUser.setEnable(sysUserUpdateDto.getEnable());
|
||||
}
|
||||
|
||||
// 更新密码(如果有)
|
||||
if (sysUserUpdateDto.getPassword() != null && !sysUserUpdateDto.getPassword().isEmpty()) {
|
||||
try {
|
||||
// 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证
|
||||
String encryptedPwd = MD5Util.doubleEncrypt(sysUserUpdateDto.getPassword(), sysUser.getSalt());
|
||||
sysUser.setPwd(encryptedPwd);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
sysUserMapper.update(sysUser);
|
||||
}else {
|
||||
// 如果没有用户ID,创建新用户
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setUserName(sysUserUpdateDto.getUserName());
|
||||
sysUser.setEmail(sysUserUpdateDto.getEmail());
|
||||
sysUser.setTel(sysUserUpdateDto.getTel());
|
||||
sysUser.setEnable(sysUserUpdateDto.getEnable());
|
||||
sysUser.setPwd(sysUserUpdateDto.getPassword());
|
||||
insert(sysUser);
|
||||
}
|
||||
return queryById(sysUserUpdateDto.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键删除数据
|
||||
*
|
||||
|
|
@ -980,7 +1044,7 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
sessionDto.setNew(false);
|
||||
} else {
|
||||
// 没有会话,创建新的会话ID
|
||||
sessionId = java.util.UUID.randomUUID().toString().replaceAll("-","");
|
||||
sessionId = java.util.UUID.randomUUID().toString();
|
||||
sysUser.setSessionId(sessionId);
|
||||
sysUserMapper.update(sysUser);
|
||||
sessionDto.setSessionId(sessionId);
|
||||
|
|
|
|||
|
|
@ -593,4 +593,9 @@
|
|||
</if>
|
||||
</select>
|
||||
|
||||
<!--清空表数据-->
|
||||
<update id="truncateTable">
|
||||
truncate table cms_content
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
|
|
|
|||
Loading…
Reference in New Issue