文件本地上传+知识图谱功能

This commit is contained in:
朱春云 2025-11-25 18:04:27 +08:00
parent 5ce697523a
commit c66439995f
30 changed files with 974 additions and 302 deletions

View File

@ -12,4 +12,5 @@ import org.springframework.stereotype.Component;
public class AIPlatformConfig {
private String host;
private String api_key;
private String profile;
}

View File

@ -1,5 +1,6 @@
package xyz.playedu.api.controller.backend.jc;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.common.annotation.Log;
@ -7,6 +8,7 @@ import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.jc.domain.Knowledge;
import xyz.playedu.jc.param.KnowledgeParam;
import xyz.playedu.jc.service.IKnowledgeService;
import java.util.HashMap;
@ -22,48 +24,52 @@ public class KnowledgeController {
@Autowired
private IKnowledgeService knowledgeService;
/** 分页列表 */
@GetMapping("/index")
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
PaginationResult<Knowledge> result = knowledgeService.paginate(params);
return JsonResponse.data(result);
/**
* 获取知识点卡片列表
*/
@GetMapping("/list")
public JsonResponse list(KnowledgeParam param) {
List<Knowledge> list = knowledgeService.listVo(param);
return JsonResponse.data(list);
}
/** 全量列表 */
@GetMapping("/list")
public JsonResponse list() {
List<Knowledge> list = knowledgeService.list();
/**
* 获取知识点树结构
*/
@GetMapping("/treeList")
public JsonResponse treeList(KnowledgeParam param) {
List<Knowledge> list = knowledgeService.treeList(param);
return JsonResponse.data(list);
}
/** 详情 */
@GetMapping("/{id}")
public JsonResponse detail(@PathVariable("id") Integer id) {
Knowledge one = knowledgeService.getById(id);
return JsonResponse.data(one);
return JsonResponse.data( knowledgeService.getByIdVo(id));
}
/** 新增 */
@Log(title = "新增知识点", businessType = BusinessTypeConstant.INSERT)
@PostMapping
public JsonResponse store(@RequestBody Knowledge knowledge) {
knowledgeService.save(knowledge);
@PostMapping("/saveOrUpdateVo")
public JsonResponse saveOrUpdateVo(@RequestBody Knowledge knowledge) {
knowledgeService.saveOrUpdateVo(knowledge);
return JsonResponse.success();
}
/** 修改 */
@Log(title = "修改知识点", businessType = BusinessTypeConstant.UPDATE)
@PutMapping
public JsonResponse update(@RequestBody Knowledge knowledge) {
knowledgeService.updateById(knowledge);
return JsonResponse.success();
}
/** 删除 */
@Log(title = "删除知识点", businessType = BusinessTypeConstant.DELETE)
@DeleteMapping("/{id}")
public JsonResponse destroy(@PathVariable("id") Integer id) {
knowledgeService.removeById(id);
Knowledge byId = knowledgeService.getById(id);
LambdaQueryWrapper<Knowledge> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.likeRight(Knowledge::getKnowledgeCode, byId.getKnowledgeCode());
knowledgeService.remove(queryWrapper);
return JsonResponse.success();
}
}

View File

@ -0,0 +1,60 @@
package xyz.playedu.api.controller.backend.system;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import xyz.playedu.common.config.PlatformConfig;
import xyz.playedu.common.config.ServerConfig;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.FileUploadUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 通用请求处理
*
* @author ruoyi
*/
@RestController
@RequestMapping("/common")
public class LocalFileController {
private static final Logger log = LoggerFactory.getLogger(LocalFileController.class);
private static final String FILE_DELIMETER = ",";
@Autowired
private ServerConfig serverConfig;
/**
* 通用上传请求单个
*/
@PostMapping("/upload")
public JsonResponse uploadFile(MultipartFile file) throws Exception {
// 上传文件路径
String filePath = PlatformConfig.getUploadPath();
// 上传并返回新文件名称
String fileName = FileUploadUtils.upload(filePath, file);
try {
String url = serverConfig.getUrl() + fileName;
JSONObject ajax = new JSONObject();
ajax.put("url", url);
ajax.put("path", fileName);
ajax.put("newFileName", FileUploadUtils.getName(fileName));
ajax.put("originalFilename", file.getOriginalFilename());
return JsonResponse.data(ajax);
} catch (Exception e) {
return JsonResponse.error(e.getMessage());
}
}
}

View File

@ -54,6 +54,8 @@ public class UploadController {
@Autowired private AppConfigService appConfigService;
@PostMapping("/minio")
@Log(title = "上传-MinIO", businessType = BusinessTypeConstant.UPLOAD)
public JsonResponse uploadMinio(

View File

@ -0,0 +1,49 @@
package xyz.playedu.api.controller.frontend;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.jc.domain.Knowledge;
import xyz.playedu.jc.param.KnowledgeParam;
import xyz.playedu.jc.service.IKnowledgeService;
import java.util.List;
/**
* 知识点管理
*/
@RestController
@RequestMapping("/api/v1/jc/knowledge")
public class KnowledgeController {
@Autowired
private IKnowledgeService knowledgeService;
/**
* 获取知识点卡片列表
*/
@GetMapping("/list")
public JsonResponse list(KnowledgeParam param) {
List<Knowledge> list = knowledgeService.listVo(param);
return JsonResponse.data(list);
}
/**
* 获取知识点树结构
*/
@GetMapping("/treeList")
public JsonResponse treeList(KnowledgeParam param) {
List<Knowledge> list = knowledgeService.treeList(param);
return JsonResponse.data(list);
}
/** 详情 */
@GetMapping("/getDetail")
public JsonResponse detail(@RequestParam("id") Integer id) {
return JsonResponse.data( knowledgeService.stuGetByIdVo(id));
}
}

View File

@ -3,10 +3,14 @@ server:
tomcat:
max-swallow-size: 10000MB
connection-timeout: 600000
ai:
platform:
host: "http://localhost:9380/"
api-key: "ragflow-VlZWVjMDg0ZjAzMTExZWZhZDhkZTU5ZD"
platform:
config:
profile: D:/ruoyi/uploadPath
spring:
profiles:
active: kafka,quartz,dev

View File

@ -0,0 +1,50 @@
package xyz.playedu.common.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "platform.config")
public class PlatformConfig {
private static String profile;
public static String getProfile() {
return profile;
}
public void setProfile(String profile) {
PlatformConfig.profile = profile;
}
/**
* 获取导入上传路径
*/
public static String getImportPath() {
return getProfile() + "/import";
}
/**
* 获取头像上传路径
*/
public static String getAvatarPath() {
return getProfile() + "/avatar";
}
/**
* 获取下载路径
*/
public static String getDownloadPath() {
return getProfile() + "/download/";
}
/**
* 获取上传路径
*/
public static String getUploadPath() {
return getProfile() + "/upload";
}
}

View File

@ -0,0 +1,31 @@
package xyz.playedu.common.config;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import xyz.playedu.common.util.ServletUtils;
/**
* 服务相关配置
*
* @author ruoyi
*/
@Component
public class ServerConfig {
public static String getDomain(HttpServletRequest request) {
StringBuffer url = request.getRequestURL();
String contextPath = request.getServletContext().getContextPath();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
}
/**
* 获取完整的请求路径包括域名端口上下文访问路径
*
* @return 服务地址
*/
public String getUrl() {
HttpServletRequest request = ServletUtils.getRequest();
return getDomain(request);
}
}

View File

@ -0,0 +1,171 @@
package xyz.playedu.common.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Objects;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;
import xyz.playedu.common.config.PlatformConfig;
/**
* 文件上传工具类
*
* @author ruoyi
*/
public class FileUploadUtils {
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
/**
* 默认的文件名最大长度 100
*/
public static final int DEFAULT_FILE_NAME_LENGTH = 100;
/**
* 默认上传的地址
*/
private static String defaultBaseDir = PlatformConfig.getProfile();
public static final String RESOURCE_PREFIX = "/profile";
public static String getDefaultBaseDir() {
return defaultBaseDir;
}
public static void setDefaultBaseDir(String defaultBaseDir) {
FileUploadUtils.defaultBaseDir = defaultBaseDir;
}
/**
* 以默认配置进行文件上传
*
* @param file 上传的文件
* @return 文件名称
* @throws Exception
*/
public static final String upload(MultipartFile file) throws IOException {
try {
return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
}
/**
* 根据文件路径上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String upload(String baseDir, MultipartFile file) throws IOException {
try {
return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
}
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws IOException 比如读写文件出错时
*/
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
throws Exception {
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
throw new Exception("文件名过长"+FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
String fileName = extractFilename(file);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
return getPathFileName(baseDir, fileName);
}
/**
* 编码文件名
*/
public static final String extractFilename(MultipartFile file) {
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
}
public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException {
File desc = new File(uploadDir + File.separator + fileName);
if (!desc.exists()) {
if (!desc.getParentFile().exists()) {
desc.getParentFile().mkdirs();
}
}
return desc;
}
public static final String getPathFileName(String uploadDir, String fileName) throws IOException {
int dirLastIndex = PlatformConfig.getProfile().length() + 1;
String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
return RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
}
/**
* 判断MIME类型是否是允许的MIME类型
*
* @param extension
* @param allowedExtension
* @return
*/
public static final boolean isAllowedExtension(String extension, String[] allowedExtension) {
for (String str : allowedExtension) {
if (str.equalsIgnoreCase(extension)) {
return true;
}
}
return false;
}
/**
* 获取文件名的后缀
*
* @param file 表单文件
* @return 后缀名
*/
public static final String getExtension(MultipartFile file) {
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StringUtils.isEmpty(extension)) {
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
return extension;
}
/**
* 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png
*
* @param fileName 路径名称
* @return 没有文件路径的名称
*/
public static String getName(String fileName) {
if (fileName == null) {
return null;
}
int lastUnixPos = fileName.lastIndexOf('/');
int lastWindowsPos = fileName.lastIndexOf('\\');
int index = Math.max(lastUnixPos, lastWindowsPos);
return fileName.substring(index + 1);
}
}

View File

@ -0,0 +1,56 @@
package xyz.playedu.common.util;
/**
* 媒体类型工具类
*
* @author ruoyi
*/
public class MimeTypeUtils {
public static final String IMAGE_PNG = "image/png";
public static final String IMAGE_JPG = "image/jpg";
public static final String IMAGE_JPEG = "image/jpeg";
public static final String IMAGE_BMP = "image/bmp";
public static final String IMAGE_GIF = "image/gif";
public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
public static final String[] FLASH_EXTENSION = {"swf", "flv"};
public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
"asf", "rm", "rmvb"};
public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"};
public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
"mp4", "avi", "rmvb",
// pdf
"pdf"};
public static String getExtension(String prefix) {
switch (prefix) {
case IMAGE_PNG:
return "png";
case IMAGE_JPG:
return "jpg";
case IMAGE_JPEG:
return "jpeg";
case IMAGE_BMP:
return "bmp";
case IMAGE_GIF:
return "gif";
default:
return "";
}
}
}

View File

@ -0,0 +1,76 @@
package xyz.playedu.common.util;
import cn.hutool.core.date.DateUtil;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author ruoyi 序列生成类
*/
public class Seq {
// 通用序列类型
public static final String commSeqType = "COMMON";
// 上传序列类型
public static final String uploadSeqType = "UPLOAD";
// 机器标识
private static final String machineCode = "A";
// 通用接口序列数
private static AtomicInteger commSeq = new AtomicInteger(1);
// 上传接口序列数
private static AtomicInteger uploadSeq = new AtomicInteger(1);
/**
* 获取通用序列号
*
* @return 序列值
*/
public static String getId() {
return getId(commSeqType);
}
/**
* 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串
*
* @return 序列值
*/
public static String getId(String type) {
AtomicInteger atomicInt = commSeq;
if (uploadSeqType.equals(type)) {
atomicInt = uploadSeq;
}
return getId(atomicInt, 3);
}
/**
* 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串
*
* @param atomicInt 序列数
* @param length 数值长度
* @return 序列值
*/
public static String getId(AtomicInteger atomicInt, int length) {
String result = DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss");
result += machineCode;
result += getSeq(atomicInt, length);
return result;
}
/**
* 序列循环递增字符串[1, 10 (length)幂次方), 用0左补齐length位数
*
* @return 序列值
*/
private synchronized static String getSeq(AtomicInteger atomicInt, int length) {
// 先取值再+1
int value = atomicInt.getAndIncrement();
// 如果更新后值>=10 (length)幂次方则重置为1
int maxSeq = (int) Math.pow(10, length);
if (atomicInt.get() >= maxSeq) {
atomicInt.set(1);
}
// 转字符串用0左补齐
return StringUtils.padl(value, length);
}
}

View File

@ -0,0 +1,72 @@
package xyz.playedu.common.util;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* 客户端工具类
*
* @author ruoyi
*/
public class ServletUtils {
/**
* 获取request
*/
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
/**
* 获取response
*/
public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse();
}
/**
* 获取session
*/
public static HttpSession getSession() {
return getRequest().getSession();
}
public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
*/
public static void renderString(HttpServletResponse response, String string) {
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
import java.util.Date;
@ -14,7 +15,7 @@ import java.util.Date;
*/
@Data
@TableName("jc_book_chapter")
public class BookChapter {
public class BookChapter extends TenantBaseDO {
@TableId(type = IdType.AUTO)
private Integer id;
@ -34,23 +35,11 @@ public class BookChapter {
/** 章节名 */
@TableField("name")
private String name;
/** 层级 */
private Integer level;
/** 排序 */
@TableField("sort")
private Integer sort;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("creator")
private String creator;
@TableField("updater")
private String updater;
@TableField("tenant_id")
private String tenantId;
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
import java.util.Date;
@ -14,7 +15,7 @@ import java.util.Date;
*/
@Data
@TableName("jc_book_department_user")
public class BookDepartmentUser {
public class BookDepartmentUser extends TenantBaseDO {
/** 教材ID */
@TableField("book_id")
@ -28,18 +29,4 @@ public class BookDepartmentUser {
@TableField("type")
private Integer type;
@TableField("creator")
private String creator;
@TableField("updater")
private String updater;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("tenant_id")
private String tenantId;
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
import java.util.Date;
@ -14,7 +15,7 @@ import java.util.Date;
*/
@Data
@TableName("jc_book_paper")
public class BookPaper {
public class BookPaper extends TenantBaseDO {
@TableId(type = IdType.AUTO)
private Integer id;
@ -32,18 +33,4 @@ public class BookPaper {
@TableField("extra")
private String extra;
@TableField("creator")
private String creator;
@TableField("updater")
private String updater;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("tenant_id")
private String tenantId;
}

View File

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
import java.util.Date;
@ -15,7 +16,7 @@ import java.util.Date;
*/
@Data
@TableName("jc_chapter_content")
public class ChapterContent {
public class ChapterContent extends TenantBaseDO {
@TableId(type = IdType.AUTO)
private Integer id;
@ -34,28 +35,9 @@ public class ChapterContent {
@TableField("type")
private String type;
// /** 关联资源ID逗号分隔 */
// @TableField("resource_ids")
// private String resourceIds;
//
// /** 关联知识点ID逗号分隔 */
// @TableField("knowledge_ids")
// private String knowledgeIds;
private String knowledgeCode;
/** 创建人 */
@TableField("creator")
private String creator;
/** 更新人 */
@TableField("updater")
private String updater;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("tenant_id")
private String tenantId;
/** 章节名称 */
@TableField(exist = false)
private String chapterName;
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
import java.util.Date;
@ -14,7 +15,7 @@ import java.util.Date;
*/
@Data
@TableName("jc_discussion")
public class Discussion {
public class Discussion extends TenantBaseDO {
@TableId(type = IdType.AUTO)
private Integer id;
@ -51,20 +52,12 @@ public class Discussion {
@TableField("status")
private Integer status;
/** 创建人(可以是用户名) */
@TableField("creator")
private String creator;
/** 知识点编码 */
@TableField(exist = false)
private String knowledgeCode;
@TableField(exist = false)
private String chapterName;
@TableField(exist = false)
private String orderType;
/** 更新人 */
@TableField("updater")
private String updater;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("tenant_id")
private String tenantId;
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
import java.util.Date;
@ -14,7 +15,7 @@ import java.util.Date;
*/
@Data
@TableName("jc_discussion_detail")
public class DiscussionDetail {
public class DiscussionDetail extends TenantBaseDO {
@TableId(type = IdType.AUTO)
private Integer id;
@ -46,19 +47,4 @@ public class DiscussionDetail {
/** 讨论内容 */
@TableField("content")
private String content;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("creator")
private String creator;
@TableField("updater")
private String updater;
@TableField("tenant_id")
private String tenantId;
}

View File

@ -31,8 +31,8 @@ public class JCResource {
@TableField("name")
private String name;
@TableField("konwledge_code")
private String konwledgeCode;
@TableField("knowledge_code")
private String knowledgeCode;
/** 文本描述 AI分析或自动填入 */

View File

@ -5,8 +5,10 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
import java.util.Date;
import java.util.List;
/**
* 知识点表
@ -14,7 +16,7 @@ import java.util.Date;
*/
@Data
@TableName("jc_knowledge")
public class Knowledge {
public class Knowledge extends TenantBaseDO {
@TableId(type = IdType.AUTO)
private Integer id;
@ -31,9 +33,9 @@ public class Knowledge {
@TableField("name")
private String name;
/** 知识点code字段名拼写为 konwledge_code */
@TableField("konwledge_code")
private String konwledgeCode;
/** 知识点code字段名拼写为 knowledge_code */
@TableField("knowledge_code")
private String knowledgeCode;
/** 知识点介绍 */
@TableField("desc")
@ -41,11 +43,14 @@ public class Knowledge {
/** 层级 */
@TableField("level")
private String level;
private Integer level;
/** 知识点类型 */
@TableField("type")
private String type;
/** 是否是真实知识点 */
@TableField("is_real")
private String isReal;
/** 当前层级排序 */
@TableField("order_num")
@ -54,19 +59,7 @@ public class Knowledge {
/** 预留JSON */
@TableField("extra_json")
private String extraJson;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("creator")
private String creator;
@TableField("updater")
private String updater;
@TableField("tenant_id")
private String tenantId;
//子节点
@TableField(exist = false)
private List<Knowledge> children;
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
import java.util.Date;
@ -14,7 +15,7 @@ import java.util.Date;
*/
@Data
@TableName("jc_note")
public class Note {
public class Note extends TenantBaseDO {
@TableId(type = IdType.AUTO)
private Integer id;
@ -50,19 +51,11 @@ public class Note {
/** 存储笔记信息的JSON位置信息等 */
@TableField("extra_json")
private String extraJson;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("creator")
private String creator;
@TableField("updater")
private String updater;
@TableField("tenant_id")
private String tenantId;
/** 知识点编码 */
@TableField(exist = false)
private String knowledgeCode;
@TableField(exist = false)
private String chapterName;
@TableField(exist = false)
private String orderType;
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
import java.util.Date;
@ -14,7 +15,7 @@ import java.util.Date;
*/
@Data
@TableName("jc_textbook")
public class Textbook {
public class Textbook extends TenantBaseDO {
@TableId(type = IdType.AUTO)
private Integer id;
@ -54,20 +55,4 @@ public class Textbook {
@TableField("publish_time")
private Date publishTime;
/** 创建人 */
@TableField("creator")
private String creator;
/** 更新人 */
@TableField("updater")
private String updater;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("tenant_id")
private String tenantId;
}

View File

@ -1,10 +1,15 @@
package xyz.playedu.jc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import xyz.playedu.jc.domain.Discussion;
import xyz.playedu.jc.domain.Note;
import java.util.List;
/**
* 讨论 Mapper
*/
public interface DiscussionMapper extends BaseMapper<Discussion> {
List<Discussion> listVo(@Param("param") Discussion note);
}

View File

@ -1,10 +1,14 @@
package xyz.playedu.jc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import xyz.playedu.jc.domain.Note;
import java.util.List;
/**
* 笔记 Mapper
*/
public interface NoteMapper extends BaseMapper<Note> {
List<Note> listVo(@Param("param") Note note);
}

View File

@ -0,0 +1,13 @@
package xyz.playedu.jc.param;
import lombok.Data;
@Data
public class KnowledgeParam {
//书本id
private Integer bookId;
//知识点名称
private String name;
//知识点code
private String knowledgeCode;
}

View File

@ -1,15 +1,24 @@
package xyz.playedu.jc.service;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.jc.domain.Knowledge;
import xyz.playedu.jc.param.KnowledgeParam;
import java.util.HashMap;
import java.util.List;
/**
* 知识点 Service
*/
public interface IKnowledgeService extends IService<Knowledge> {
List<Knowledge> listVo(KnowledgeParam param);
List<Knowledge> treeList(KnowledgeParam param);
void saveOrUpdateVo(Knowledge data);
JSONObject getByIdVo(Integer id);
JSONObject stuGetByIdVo(Integer id);
PaginationResult<Knowledge> paginate(HashMap<String, Object> params);
}

View File

@ -1,75 +1,255 @@
package xyz.playedu.jc.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import xyz.playedu.common.context.FCtx;
import xyz.playedu.common.domain.User;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.jc.domain.Knowledge;
import xyz.playedu.jc.mapper.KnowledgeMapper;
import xyz.playedu.jc.domain.*;
import xyz.playedu.jc.mapper.*;
import xyz.playedu.jc.param.KnowledgeParam;
import xyz.playedu.jc.service.IKnowledgeService;
import xyz.playedu.jc.service.ITextbookService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* 知识点 Service 实现
*/
@Slf4j
@Service
public class KnowledgeServiceImpl
extends ServiceImpl<KnowledgeMapper, Knowledge>
implements IKnowledgeService {
public class KnowledgeServiceImpl extends ServiceImpl<KnowledgeMapper, Knowledge> implements IKnowledgeService {
@Autowired
private ChapterContentMapper contentMapper;
@Autowired
private JCResourceMapper resourceMapper;
@Autowired
private BookChapterMapper chapterMapper;
@Autowired
private NoteMapper noteMapper;
@Autowired
private DiscussionMapper discussionMapper;
@Override
public PaginationResult<Knowledge> paginate(HashMap<String, Object> params) {
try {
Integer page = MapUtils.getInteger(params, "page", 1);
Integer size = MapUtils.getInteger(params, "size", 10);
Page<Knowledge> pageParam = new Page<>(page, size);
public List<Knowledge> listVo(KnowledgeParam param) {
//获取知识点卡片
LambdaQueryWrapper<Knowledge> queryWrapper = new LambdaQueryWrapper<>();
Integer bookId = MapUtils.getInteger(params, "bookId");
if (bookId != null) {
queryWrapper.eq(Knowledge::getBookId, bookId);
queryWrapper.eq(Knowledge::getBookId, param.getBookId())
.eq(Knowledge::getIsReal,"1").orderByAsc(Knowledge::getOrderNum);
return list(queryWrapper);
}
@Override
public JSONObject getByIdVo(Integer id) {
JSONObject res = new JSONObject();
Knowledge knowledge = this.getById(id);
res.put("knowledge", knowledge);
//获取各个板块该知识点下的资源
LambdaQueryWrapper<ChapterContent> contentWrapper = new LambdaQueryWrapper<>();
contentWrapper.select(ChapterContent::getId,ChapterContent::getChapterId).like(ChapterContent::getKnowledgeCode, knowledge.getKnowledgeCode()).eq(ChapterContent::getBookId, knowledge.getBookId());
List<ChapterContent> contents = contentMapper.selectList(contentWrapper);
for (ChapterContent content : contents) {
BookChapter bookChapter = chapterMapper.selectById(content.getChapterId());
content.setChapterName(bookChapter.getName());
}
res.put("contents", contents);
LambdaQueryWrapper<JCResource> resourceWrapper = new LambdaQueryWrapper<>();
resourceWrapper.like(JCResource::getKnowledgeCode, knowledge.getKnowledgeCode()).eq(JCResource::getBookId, knowledge.getBookId());
res.put("resources", resourceMapper.selectList(resourceWrapper));
return res;
}
Integer parentId = MapUtils.getInteger(params, "parentId");
if (parentId != null) {
@Override
public JSONObject stuGetByIdVo(Integer id) {
Knowledge knowledge = this.getById(id);
//获取当前学生信息
User user = FCtx.getUser();
JSONObject res = this.getByIdVo(id);
//获取笔记信息
Note param = new Note();
param.setBookId(knowledge.getBookId());
param.setKnowledgeCode(knowledge.getKnowledgeCode());
param.setUserId(user.getId());
List<Note> notes = noteMapper.listVo(param);
res.put("notes", notes);
//获取讨论信息
Discussion discussion = new Discussion();
discussion.setBookId(knowledge.getBookId());
discussion.setKnowledgeCode(knowledge.getKnowledgeCode());
discussion.setUserId(user.getId());
List<Discussion> discussions = discussionMapper.listVo(discussion);
res.put("discussions", discussions);
return res;
}
@Override
public List<Knowledge> treeList(KnowledgeParam param) {
//获取全部知识点
LambdaQueryWrapper<Knowledge> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Knowledge::getBookId, param.getBookId()).orderByAsc(Knowledge::getOrderNum);
List<Knowledge> allKnowledges = this.list(queryWrapper);
// 处理为树结构
return buildTree(allKnowledges);
}
@Override
public void saveOrUpdateVo(Knowledge data) {
if (ObjectUtil.isEmpty(data.getId())){
Knowledge knowledge = this.getById(data.getId());
knowledge.setDesc(data.getDesc());
knowledge.setName(data.getName());
knowledge.setType(data.getType());
this.updateById(knowledge);
return;
}
if (ObjectUtil.isNotEmpty(data.getParentId())||data.getParentId()==0){
data.setLevel(1);
data.setKnowledgeCode(generateKnowledgeCode(data.getParentId()));
this.save(data);
}else {
Knowledge byId = this.getById(data.getParentId());
data.setLevel(byId.getLevel()+1);
data.setKnowledgeCode(generateKnowledgeCode(data.getParentId()));
this.save(data);
}
}
private List<Knowledge> buildTree(List<Knowledge> knowledges) {
// 创建一个map来存储id到对象的映射方便快速查找
HashMap<Integer, Knowledge> knowledgeMap = new HashMap<>();
for (Knowledge knowledge : knowledges) {
knowledgeMap.put(knowledge.getId(), knowledge);
}
// 存储最终的根节点
List<Knowledge> rootNodes = new ArrayList<>();
for (Knowledge knowledge : knowledges) {
Integer parentId = knowledge.getParentId();
if (parentId == null || parentId == 0) { // 假设没有父节点的是根节点
rootNodes.add(knowledge);
} else {
Knowledge parent = knowledgeMap.get(parentId);
if (parent != null) {
if (parent.getChildren() == null) {
parent.setChildren(new ArrayList<>());
}
parent.getChildren().add(knowledge);
}
}
}
return rootNodes;
}
/**
* 生成知识点编码
* @param parentId 父节点ID0或null代表顶级节点
* @return 知识点编码如A01A01A01等
*/
public String generateKnowledgeCode(Integer parentId) {
if (parentId == null || parentId == 0) {
// 顶级节点编码生成逻辑
// 查询当前最大的顶级节点编码
LambdaQueryWrapper<Knowledge> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.isNull(Knowledge::getParentId).or().eq(Knowledge::getParentId, 0);
queryWrapper.orderByDesc(Knowledge::getId);
List<Knowledge> topKnowledges = this.list(queryWrapper);
if (topKnowledges.isEmpty()) {
return "A01";
}
// 获取最后一个顶级节点的编码
String lastCode = topKnowledges.get(0).getKnowledgeCode();
if (lastCode == null || lastCode.isEmpty()) {
return "A01";
}
// 解析并递增编码
return incrementCode(lastCode, 3); // 顶级节点固定3位编码
} else {
// 子节点编码生成逻辑
Knowledge parent = this.getById(parentId);
if (parent == null || parent.getKnowledgeCode() == null) {
throw new RuntimeException("父节点不存在或无编码");
}
String parentCode = parent.getKnowledgeCode();
// 查询该父节点下的所有子节点
LambdaQueryWrapper<Knowledge> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Knowledge::getParentId, parentId);
queryWrapper.orderByDesc(Knowledge::getId);
List<Knowledge> childKnowledges = this.list(queryWrapper);
if (childKnowledges.isEmpty()) {
// 第一个子节点
return parentCode + "A01";
}
String name = MapUtils.getString(params, "name");
if (name != null && !name.isEmpty()) {
queryWrapper.like(Knowledge::getName, name);
// 获取最后一个子节点的编码
String lastChildCode = childKnowledges.get(0).getKnowledgeCode();
if (lastChildCode == null || lastChildCode.length() <= parentCode.length()) {
return parentCode + "A01";
}
queryWrapper.orderByAsc(Knowledge::getOrderNum);
// 提取子节点部分并递增
String childPart = lastChildCode.substring(parentCode.length());
String newChildPart = incrementCode(childPart, 3); // 子节点部分固定3位编码
IPage<Knowledge> pageResult = this.page(pageParam, queryWrapper);
Long total = pageResult.getTotal();
PaginationResult<Knowledge> result = new PaginationResult<>();
result.setData(pageResult.getRecords());
result.setTotal(total);
result.setCurrent(page);
result.setSize(size);
result.setPages((total + size - 1) / size);
return result;
} catch (Exception e) {
log.error("分页查询知识点失败,参数:{}", params, e);
PaginationResult<Knowledge> emptyResult = new PaginationResult<>();
emptyResult.setData(new ArrayList<>());
emptyResult.setTotal(0L);
emptyResult.setCurrent(MapUtils.getInteger(params, "page", 1));
emptyResult.setSize(MapUtils.getInteger(params, "size", 10));
emptyResult.setPages(0L);
return emptyResult;
return parentCode + newChildPart;
}
}
/**
* 递增编码
* @param code 当前编码
* @param length 编码长度
* @return 递增后的编码
*/
private String incrementCode(String code, int length) {
if (code == null || code.isEmpty()) {
return "A01";
}
// 分离字母和数字部分
StringBuilder letters = new StringBuilder();
StringBuilder digits = new StringBuilder();
for (char c : code.toCharArray()) {
if (Character.isLetter(c)) {
letters.append(c);
} else if (Character.isDigit(c)) {
digits.append(c);
}
}
if (digits.isEmpty()) {
return "A01";
}
// 递增数字部分
int num = Integer.parseInt(digits.toString());
num++;
// 格式化回固定长度
String format = "%0" + length + "d";
return letters.toString() + String.format(format, num);
}
}

View File

@ -5,9 +5,34 @@
<mapper namespace="xyz.playedu.jc.mapper.DiscussionMapper">
<!--
目前使用 MyBatis-Plus 的通用 CRUD这里可以先留空。
如果后续有复杂查询,可以在这里新增 <select> / <update> 等。
-->
<select id="listVo" resultType="xyz.playedu.jc.domain.Discussion">
select discussion.*, chapter.name as chapterName
from jc_discussion discussion
inner join jc_chapter_content content on discussion.chapter_id = content.chapter_id
inner join jc_book_chapter chapter on content.chapter_id = chapter.id
<where>
<if test="param.bookId != null">
and discussion.book_id = #{param.bookId}
</if>
<if test="param.userId != null">
and discussion.user_id = #{param.userId}
</if>
<if test="param.chapterId != null">
and discussion.chapter_id = #{param.chapterId}
</if>
<if test="param.knowledgeCode != null and knowledgeCode!=''">
and content.knowledge_code like concat('%',#{param.knowledgeCode},'%')
</if>
</where>
<choose>
<when test="orderType == 'time'">
order by discussion.create_time desc
</when>
<otherwise>
order by chapter.level asc, chapter.sort asc
</otherwise>
</choose>
</select>
</mapper>

View File

@ -4,40 +4,6 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.playedu.jc.mapper.KnowledgeMapper">
<resultMap id="KnowledgeResultMap" type="xyz.playedu.jc.domain.Knowledge">
<id column="id" property="id"/>
<result column="book_id" property="bookId"/>
<result column="parent_id" property="parentId"/>
<result column="name" property="name"/>
<result column="konwledge_code" property="konwledgeCode"/>
<result column="desc" property="desc"/>
<result column="level" property="level"/>
<result column="type" property="type"/>
<result column="order_num" property="orderNum"/>
<result column="extra_json" property="extraJson"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="creator" property="creator"/>
<result column="updater" property="updater"/>
<result column="tenant_id" property="tenantId"/>
</resultMap>
<sql id="Base_Column_List">
id,
book_id,
parent_id,
name,
konwledge_code,
`desc`,
`level`,
`type`,
order_num,
extra_json,
create_time,
update_time,
creator,
updater,
tenant_id
</sql>
</mapper>

View File

@ -4,38 +4,35 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.playedu.jc.mapper.NoteMapper">
<resultMap id="NoteResultMap" type="xyz.playedu.jc.domain.Note">
<id column="id" property="id"/>
<result column="book_id" property="bookId"/>
<result column="user_id" property="userId"/>
<result column="chapter_id" property="chapterId"/>
<result column="content" property="content"/>
<result column="txt" property="txt"/>
<result column="section_id" property="sectionId"/>
<result column="section_origin_id" property="sectionOriginId"/>
<result column="extra_json" property="extraJson"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="creator" property="creator"/>
<result column="updater" property="updater"/>
<result column="tenant_id" property="tenantId"/>
</resultMap>
<select id="listVo" resultType="xyz.playedu.jc.domain.Note">
select note.*, chapter.name as chapterName
from jc_note note
inner join jc_chapter_content content on note.chapter_id = content.chapter_id
inner join jc_book_chapter chapter on content.chapter_id = chapter.id
<where>
<if test="param.bookId != null">
and note.book_id = #{param.bookId}
</if>
<if test="param.userId != null">
and note.user_id = #{param.userId}
</if>
<if test="param.chapterId != null">
and note.chapter_id = #{param.chapterId}
</if>
<if test="param.knowledgeCode != null and knowledgeCode!=''">
and content.knowledge_code like concat('%',#{param.knowledgeCode},'%')
</if>
<sql id="Base_Column_List">
id,
book_id,
user_id,
chapter_id,
content,
txt,
section_id,
section_origin_id,
extra_json,
create_time,
update_time,
creator,
updater,
tenant_id
</sql>
</where>
<choose>
<when test="orderType == 'time'">
order by note.create_time desc
</when>
<otherwise>
order by chapter.level asc, chapter.sort asc
</otherwise>
</choose>
</select>
</mapper>