feat(复习):标准思维导图生成与回忆对比
- 新增 review_standard_mind_maps / review_recall_records 数据表 - BuiltinMindMapGenerator:从报告/残片/应用场景规则生成标准导图 - MindMapAiClient 接口 + RemoteAiMindMapClient 预留 - StandardMindMapService:生成/编辑/重新生成/回忆对比 - 对比算法:标题归一化 + Bigram Jaccard 模糊匹配 - ReviewController 新增 6 个端点 - MindMapTreeTool / MindMapNode / CompareResult 工具类 - 完整单元测试(10 个,全部通过) - 更新 docs/review-module-design.md
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package com.guo.learningprogresstracker.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 思维导图树节点 DTO,与 MindMapFileParser 的输出结构兼容。
|
||||
* 序列化后可在「nodes」和「children」两个 key 下放置子节点列表。
|
||||
*
|
||||
* <pre>{@code
|
||||
* {
|
||||
* "title": "根标题",
|
||||
* "notes": "备注",
|
||||
* "sourceType": "REPORT", // 可选:节点来源类型
|
||||
* "sourceId": 1, // 可选:来源主键
|
||||
* "children": [ ... ]
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class MindMapNode {
|
||||
|
||||
private String title;
|
||||
private String notes;
|
||||
private String sourceType;
|
||||
private Integer sourceId;
|
||||
private List<MindMapNode> children;
|
||||
|
||||
public MindMapNode(String title) {
|
||||
this.title = title;
|
||||
this.notes = "";
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
/** 构建 JSON 友好的 Map 结构(兼容 MindMapFileParser 输出风格) */
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("title", title != null ? title : "");
|
||||
map.put("notes", notes != null ? notes : "");
|
||||
if (sourceType != null) map.put("sourceType", sourceType);
|
||||
if (sourceId != null) map.put("sourceId", sourceId);
|
||||
List<Object> childMaps = new ArrayList<>();
|
||||
if (children != null) {
|
||||
for (MindMapNode c : children) {
|
||||
childMaps.add(c.toMap());
|
||||
}
|
||||
}
|
||||
map.put("children", childMaps);
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 从 Map 重建节点 */
|
||||
public static MindMapNode fromMap(Map<String, Object> map) {
|
||||
MindMapNode node = new MindMapNode();
|
||||
node.setTitle((String) map.getOrDefault("title", ""));
|
||||
node.setNotes((String) map.getOrDefault("notes", ""));
|
||||
node.setSourceType((String) map.get("sourceType"));
|
||||
if (map.containsKey("sourceId") && map.get("sourceId") != null) {
|
||||
node.setSourceId(((Number) map.get("sourceId")).intValue());
|
||||
}
|
||||
Object raw = map.getOrDefault("children", map.get("nodes"));
|
||||
List<MindMapNode> children = new ArrayList<>();
|
||||
if (raw instanceof List<?> list) {
|
||||
for (Object item : list) {
|
||||
if (item instanceof Map<?, ?> m) {
|
||||
children.add(fromMap((Map<String, Object>) m));
|
||||
}
|
||||
}
|
||||
}
|
||||
node.setChildren(children);
|
||||
return node;
|
||||
}
|
||||
|
||||
/** 反序列化 JSON 字符串为 MindMapNode */
|
||||
public static MindMapNode fromJson(String json, ObjectMapper mapper) {
|
||||
try {
|
||||
Map<String, Object> map = mapper.readValue(json, LinkedHashMap.class);
|
||||
return fromMap(map);
|
||||
} catch (Exception e) {
|
||||
return new MindMapNode("解析失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user