skill 模块开发

This commit is contained in:
cyc 2026-03-31 16:51:34 +08:00
parent bc432f1ccd
commit 681e3de3bb
6 changed files with 230 additions and 14 deletions

View File

@ -14,13 +14,14 @@ import io.swagger.annotations.ApiOperation;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;
import org.springframework.data.domain.Page;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("skills")
@Api(value = "新模型管理")
@Api(value = "技能管理")
public class SkillsController extends BaseController {
@Resource
@ -31,10 +32,12 @@ public class SkillsController extends BaseController {
public GenericsAjaxResult<Page<Skill>> queryByPage(@RequestParam("page") int page,
@RequestParam("size") int size,
@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "type", required = false) String type,
@RequestParam("is_public") boolean isPublic) {
PageRequest pageRequest = PageRequest.of(page, size);
Skill skill = new Skill();
skill.setName(name);
skill.setType(type);
if (!isPublic) {
skill.setCreateBy(SecurityUtils.getUsername());
} else {
@ -43,7 +46,7 @@ public class SkillsController extends BaseController {
return genericsSuccess(this.skillService.queryByPage(skill, pageRequest));
}
@PostMapping("/addSkill")
@PostMapping
@ApiOperation("添加Skill")
@OperationNotification("添加Skill,Skill名称#{#skillVo.name}")
public GenericsAjaxResult<Skill> addSkill(@RequestBody SkillVo skillVo) throws Exception {
@ -77,4 +80,29 @@ public class SkillsController extends BaseController {
LoginUser loginUser = SecurityUtils.getLoginUser();
return AjaxResult.success(skillService.publish(skill, loginUser, request));
}
@PutMapping("/praise/{id}")
@ApiOperation(value = "点赞一个项目")
public AjaxResult praise(@PathVariable("id") Long id){
return AjaxResult.success(skillService.praise(id));
}
@PutMapping("/unpraise/{id}")
@ApiOperation(value = "取消点赞一个项目")
public AjaxResult unpraise(@PathVariable("id") Long id){
return AjaxResult.success(skillService.unpraise(id));
}
/**
* 上传
*
* @param files 上传文件
* @return 上传结果
*/
@CrossOrigin(origins = "*", allowedHeaders = "*")
@PostMapping("/upload")
@ApiOperation(value = "上传")
public AjaxResult uploadDataset(@RequestParam("file") MultipartFile files,Long id) throws Exception {
return AjaxResult.success(skillService.upload(files, id));
}
}

View File

@ -37,4 +37,10 @@ public class Skill {
private Integer state;
private String filePath;
private String fileTreeJson;
private String mdContent;
private Integer praisesCount;
}

View File

@ -5,8 +5,11 @@ import com.ruoyi.platform.vo.SkillVo;
import com.ruoyi.system.api.model.LoginUser;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
public interface SkillService {
Page<Skill> queryByPage(Skill skill, PageRequest pageRequest);
@ -20,4 +23,10 @@ public interface SkillService {
String delete(Long id) throws Exception;
String publish(Skill skill, LoginUser loginUser, HttpServletRequest request);
String praise(Long id);
String unpraise(Long id);
Map<String, Object> upload(MultipartFile files, Long id) throws IOException;
}

View File

@ -173,12 +173,15 @@ public class CSServiceVersionServiceImpl implements ServiceVersionService {
public TrainingTask insertTask(TrainingTaskVo trainingTaskVo) throws Exception {
TrainingTask trainingTask = new TrainingTask();
BeanUtils.copyProperties(trainingTaskVo, trainingTask);
trainingTask.setTaskInfo(JacksonUtil.toJSONString(trainingTaskVo.getTaskInfo()));
trainingTask.setTaskParam(JacksonUtil.toJSONString(trainingTaskVo.getTaskParam()));
trainingTask.setStatus("Pending");
trainingTask.setState(1);
trainingTask.setStatus("Pending");
trainingTask.setCreateTime(new Date());
trainingTask.setUpdateTime(new Date());
trainingTask.setTaskName((String) trainingTaskVo.getTaskInfo().get("name"));
trainingTask.setTaskInfo(JacksonUtil.toJSONString(trainingTaskVo.getTaskInfo()));
trainingTask.setTaskParam(JacksonUtil.toJSONString(trainingTaskVo.getTaskParam()));
String username = SecurityUtils.getLoginUser().getUsername();
trainingTask.setCreateBy(username);
trainingTask.setUpdateBy(username);

View File

@ -1,37 +1,55 @@
package com.ruoyi.platform.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.platform.domain.ApprovalRequest;
import com.ruoyi.platform.domain.ApprovalStatus;
import com.ruoyi.platform.domain.ApprovalType;
import com.ruoyi.platform.domain.Skill;
import com.ruoyi.platform.domain.*;
import com.ruoyi.platform.mapper.ApprovalInfoMapper;
import com.ruoyi.platform.mapper.SkillDao;
import com.ruoyi.platform.service.MinioService;
import com.ruoyi.platform.service.SkillService;
import com.ruoyi.platform.vo.SkillVo;
import com.ruoyi.system.api.constant.Constant;
import com.ruoyi.system.api.model.LoginUser;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@Service("skillService")
@Transactional
@Log4j2
public class SkillServiceImpl implements SkillService {
@Value("${minio.dataReleaseBucketName}")
private String bucketName;
@Resource
private SkillDao skillDao;
@Resource
private ApprovalInfoMapper approvalInfoMapper;
@Resource
private MinioService minioService;
@Override
public Page<Skill> queryByPage(Skill skill, PageRequest pageRequest) {
@ -76,8 +94,7 @@ public class SkillServiceImpl implements SkillService {
@Override
public Skill queryById(Long id) {
Skill skill = skillDao.getSkillById(id);
return skill;
return skillDao.getSkillById(id);
}
@Override
@ -128,4 +145,142 @@ public class SkillServiceImpl implements SkillService {
return "发布成功,待管理员审核";
}
@Override
public String praise(Long id) {
Skill skill = skillDao.getSkillById(id);
skill.setPraisesCount(skill.getPraisesCount() + 1);
skillDao.edit(skill);
return "点赞成功";
}
@Override
public String unpraise(Long id) {
Skill skill = skillDao.getSkillById(id);
skill.setPraisesCount(skill.getPraisesCount() - 1);
skillDao.edit(skill);
return "取消点赞成功";
}
@Override
public Map<String, Object> upload(MultipartFile file, Long id) throws IOException {
String originalFilename = file.getOriginalFilename();
String fileType = getFileType(originalFilename);
if (fileType == null) {
throw new IllegalArgumentException("仅支持 .md 或 .zip 文件");
}
Skill skill = new Skill();
skill.setId(id);
if ("md".equals(fileType)) {
skill.setMdContent(IoUtil.read(file.getInputStream(), StandardCharsets.UTF_8));
} else if ("zip".equals(fileType)) {
// 解析 ZIP直接填充 metadata directoryTree contentText
parseZipAndFillMetadata(file.getInputStream(), skill);
}
String savePath = "/mini-model-platform-data/skill_file/upload/" + id + "/" + cn.hutool.core.lang.UUID.fastUUID() + "/" + file.getOriginalFilename();
HashMap<String, Object> result = MapUtil.newHashMap();
result.put("filename", file.getOriginalFilename());
result.put("direct_url", savePath);
// 异步上传原始文件到 MinIO
CompletableFuture.supplyAsync(() -> {
try {
minioService.uploadFile(bucketName, savePath, file);
return "上传成功";
} catch (Exception e) {
throw new RuntimeException("异步提交任务失败", e);
}
});
skill.setFilePath(savePath);
skillDao.edit(skill);
return result;
}
/**
* 解析 ZIP 直接填充 metadata directoryTree contentText
*/
private void parseZipAndFillMetadata(InputStream inputStream, Skill skill) throws IOException {
List<Map<String, Object>> rootChildren = new ArrayList<>();
Map<String, Map<String, Object>> pathToNode = new HashMap<>();
Map<String, Object> rootNode = new HashMap<>();
rootNode.put("children", rootChildren);
pathToNode.put("", rootNode);
StringBuilder skillMdContent = new StringBuilder();
try (ZipInputStream zis = new ZipInputStream(inputStream)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String entryName = entry.getName();
// 去除目录结尾的斜杠
if (entryName.endsWith("/")) {
entryName = entryName.substring(0, entryName.length() - 1);
}
// 使用 Hutool 分割路径
String[] parts = StrUtil.splitToArray(entryName, '/');
StringBuilder currentPath = new StringBuilder();
Map<String, Object> parentNode = pathToNode.get(""); // 根节点
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
if (i > 0) currentPath.append('/');
currentPath.append(part);
String fullPath = currentPath.toString();
Map<String, Object> node = pathToNode.get(fullPath);
if (node == null) {
boolean isDir = (i < parts.length - 1) || entry.isDirectory();
Map<String, Object> newNode = new HashMap<>();
newNode.put("name", part);
if (isDir) {
List<Map<String, Object>> children = new ArrayList<>();
newNode.put("children", children);
pathToNode.put(fullPath, newNode);
@SuppressWarnings("unchecked")
List<Map<String, Object>> parentChildren = (List<Map<String, Object>>) parentNode.get("children");
parentChildren.add(newNode);
} else {
newNode.put("children", null);
long sizeBytes = entry.getSize();
if (sizeBytes < 0) sizeBytes = 0;
newNode.put("fileSize", FileUtil.readableFileSize(sizeBytes));
pathToNode.put(fullPath, newNode);
@SuppressWarnings("unchecked")
List<Map<String, Object>> parentChildren = (List<Map<String, Object>>) parentNode.get("children");
parentChildren.add(newNode);
}
}
parentNode = node != null ? node : pathToNode.get(fullPath);
}
// 读取最外层 Skill.md 内容
if (isOuterSkillMd(entryName) && !entry.isDirectory()) {
// 使用 Hutool 读取当前条目的内容
String content = IoUtil.read(zis, StandardCharsets.UTF_8);
skillMdContent.append(content);
}
zis.closeEntry();
}
}
// 使用 Hutool 序列化为 JSON
skill.setFileTreeJson(JSONUtil.toJsonStr(rootChildren));
skill.setMdContent(skillMdContent.toString());
}
private boolean isOuterSkillMd(String entryName) {
return "Skill.md".equals(entryName);
}
private String getFileType(String fileName) {
if (fileName == null) return null;
String ext = StrUtil.subAfter(fileName, ".", true);
if ("md".equalsIgnoreCase(ext)) return "md";
if ("zip".equalsIgnoreCase(ext)) return "zip";
return null;
}
}

View File

@ -22,6 +22,18 @@
<if test="skill.previewPic != null and skill.previewPic !=''">
preview_pic = #{skill.previewPic},
</if>
<if test="skill.praisesCount != null and skill.praisesCount !=''">
praises_count = #{skill.praisesCount},
</if>
<if test="skill.filePath != null and skill.filePath !=''">
file_path = #{skill.filePath},
</if>
<if test="skill.fileTreeJson != null and skill.fileTreeJson !=''">
file_tree_json = #{skill.fileTreeJson},
</if>
<if test="skill.mdContent != null and skill.mdContent !=''">
md_content = #{skill.mdContent},
</if>
<if test="skill.isPublic != null">
is_public = #{skill.isPublic},
</if>
@ -40,7 +52,7 @@
<select id="queryByPage" resultType="com.ruoyi.platform.domain.Skill">
select * from skill
<include refid="common_condition"></include>
order by update_time desc limit #{pageable.offset}, #{pageable.pageSize}
order by praises_count desc limit #{pageable.offset}, #{pageable.pageSize}
</select>
<select id="getSkillByName" resultType="com.ruoyi.platform.domain.Skill">
@ -66,6 +78,9 @@
<if test="skill.isPublic != null">
and is_public = #{skill.isPublic}
</if>
<if test="skill.type != null">
and type = #{skill.type}
</if>
<if test="skill.createBy != null and skill.createBy != ''">
and create_by = #{skill.createBy}
</if>