diff --git a/src/main/java/com/going/server/domain/graph/entity/Edge.java b/src/main/java/com/going/server/domain/graph/entity/Edge.java deleted file mode 100644 index f899657..0000000 --- a/src/main/java/com/going/server/domain/graph/entity/Edge.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.going.server.domain.graph.entity; - - -import lombok.*; -import org.springframework.data.neo4j.core.schema.GeneratedValue; -import org.springframework.data.neo4j.core.schema.Id; -import org.springframework.data.neo4j.core.schema.Node; - -@Node("Edge") -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Edge { - @Id @GeneratedValue - private Long id; - private String source; - private String target; - private String label; -} diff --git a/src/main/java/com/going/server/domain/graph/entity/Graph.java b/src/main/java/com/going/server/domain/graph/entity/Graph.java index 5f12560..6b8b41a 100644 --- a/src/main/java/com/going/server/domain/graph/entity/Graph.java +++ b/src/main/java/com/going/server/domain/graph/entity/Graph.java @@ -1,18 +1,17 @@ package com.going.server.domain.graph.entity; import com.going.server.global.common.BaseEntity; +import lombok.Builder; import lombok.Getter; import lombok.Setter; -import org.springframework.data.neo4j.core.schema.GeneratedValue; -import org.springframework.data.neo4j.core.schema.Id; -import org.springframework.data.neo4j.core.schema.Node; -import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.schema.*; import java.util.List; -@Node("KnowledgeGraph") +@Node("Graph") @Getter @Setter +@Builder public class Graph extends BaseEntity { @Id @GeneratedValue diff --git a/src/main/java/com/going/server/domain/graph/entity/GraphEdge.java b/src/main/java/com/going/server/domain/graph/entity/GraphEdge.java index c4b5511..22f01e9 100644 --- a/src/main/java/com/going/server/domain/graph/entity/GraphEdge.java +++ b/src/main/java/com/going/server/domain/graph/entity/GraphEdge.java @@ -1,5 +1,6 @@ package com.going.server.domain.graph.entity; +import lombok.Builder; import lombok.Getter; import lombok.Setter; import org.springframework.data.neo4j.core.schema.*; @@ -9,33 +10,33 @@ @RelationshipProperties @Getter @Setter +@Builder public class GraphEdge { @Id @GeneratedValue private Long id; // Neo4j 내부 ID + private String source; + private String label; // 관계 라벨 @TargetNode private GraphNode target; // 연결 대상 노드 - @Property - private Long edgeId; // JSON에서 받은 숫자형 edge ID (ex. 12) - - private GraphEdge createEdge(Long edgeId, String label, GraphNode source, GraphNode target) { - GraphEdge edge = new GraphEdge(); - edge.setEdgeId(edgeId); - edge.setLabel(label); - edge.setTarget(target); - - // outbound edge를 source에 연결 - if (source.getEdges() == null) { - source.setEdges(new ArrayList<>()); - } - source.getEdges().add(edge); - return edge; - } +// private GraphEdge createEdge(Long edgeId, String label, GraphNode source, GraphNode target) { +// GraphEdge edge = new GraphEdge(); +// edge.setEdgeId(edgeId); +// edge.setLabel(label); +// edge.setTarget(target); +// +// // outbound edge를 source에 연결 +// if (source.getEdges() == null) { +// source.setEdges(new ArrayList<>()); +// } +// source.getEdges().add(edge); +// return edge; +// } // Long → String 변환 (프론트 전송 시) public String getIdAsString() { diff --git a/src/main/java/com/going/server/domain/graph/entity/GraphNode.java b/src/main/java/com/going/server/domain/graph/entity/GraphNode.java index b0657ed..336723e 100644 --- a/src/main/java/com/going/server/domain/graph/entity/GraphNode.java +++ b/src/main/java/com/going/server/domain/graph/entity/GraphNode.java @@ -1,33 +1,37 @@ package com.going.server.domain.graph.entity; +import lombok.Builder; import lombok.Getter; import lombok.Setter; -import org.springframework.data.neo4j.core.schema.GeneratedValue; -import org.springframework.data.neo4j.core.schema.Id; -import org.springframework.data.neo4j.core.schema.Node; -import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.schema.*; import java.util.ArrayList; import java.util.List; +import java.util.Set; @Node("GraphNode") @Getter @Setter +@Builder public class GraphNode { @Id @GeneratedValue private Long id; + @Property("node_id") + private Long nodeId; + private String label; - private int level; + private String group; + private Long level; private String includeSentence; //해당 노드(단어)가 포함된 문장 @Relationship(type = "HAS_GRAPH", direction = Relationship.Direction.INCOMING) private Graph graph; @Relationship(type = "RELATED", direction = Relationship.Direction.OUTGOING) - private List edges = new ArrayList<>(); + private Set edges; public String getIdAsString() { return id != null ? String.valueOf(id) : null; diff --git a/src/main/java/com/going/server/domain/graph/repository/EdgeRepository.java b/src/main/java/com/going/server/domain/graph/repository/EdgeRepository.java deleted file mode 100644 index 7c5d606..0000000 --- a/src/main/java/com/going/server/domain/graph/repository/EdgeRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.going.server.domain.graph.repository; - -import com.going.server.domain.graph.entity.Edge; -import org.springframework.data.neo4j.repository.Neo4jRepository; - -public interface EdgeRepository extends Neo4jRepository { -} diff --git a/src/main/java/com/going/server/domain/graph/repository/GraphEdgeRepository.java b/src/main/java/com/going/server/domain/graph/repository/GraphEdgeRepository.java new file mode 100644 index 0000000..a3a00ca --- /dev/null +++ b/src/main/java/com/going/server/domain/graph/repository/GraphEdgeRepository.java @@ -0,0 +1,7 @@ +package com.going.server.domain.graph.repository; + +import com.going.server.domain.graph.entity.GraphEdge; +import org.springframework.data.neo4j.repository.Neo4jRepository; + +public interface GraphEdgeRepository extends Neo4jRepository { +} diff --git a/src/main/java/com/going/server/domain/graph/repository/GraphNodeRepository.java b/src/main/java/com/going/server/domain/graph/repository/GraphNodeRepository.java new file mode 100644 index 0000000..ccef5ca --- /dev/null +++ b/src/main/java/com/going/server/domain/graph/repository/GraphNodeRepository.java @@ -0,0 +1,7 @@ +package com.going.server.domain.graph.repository; + +import com.going.server.domain.graph.entity.GraphNode; +import org.springframework.data.neo4j.repository.Neo4jRepository; + +public interface GraphNodeRepository extends Neo4jRepository { +} diff --git a/src/main/java/com/going/server/domain/upload/dto/UploadResponseDto.java b/src/main/java/com/going/server/domain/upload/dto/UploadResponseDto.java index 43011f3..4eba053 100644 --- a/src/main/java/com/going/server/domain/upload/dto/UploadResponseDto.java +++ b/src/main/java/com/going/server/domain/upload/dto/UploadResponseDto.java @@ -9,13 +9,8 @@ @Builder public class UploadResponseDto { private Long graphId; - private String title; - private Boolean easy; //쉬움 모드 - private Boolean hard; //어려움 모드 - private Long nodes; //그래프 노드 개수 - private Long edges; //그래프 관계 개수 - public static UploadResponseDto from(Long graphId, String title, Boolean easy, Boolean haed, Long nodes, Long edges) { - return UploadResponseDto.builder().graphId(graphId).title(title).easy(easy).hard(haed).nodes(nodes).edges(edges).build(); + public static UploadResponseDto from(Long graphId) { + return UploadResponseDto.builder().graphId(graphId).build(); } } diff --git a/src/main/java/com/going/server/domain/upload/service/UploadServiceImpl.java b/src/main/java/com/going/server/domain/upload/service/UploadServiceImpl.java index d8bbe3b..5918d9c 100644 --- a/src/main/java/com/going/server/domain/upload/service/UploadServiceImpl.java +++ b/src/main/java/com/going/server/domain/upload/service/UploadServiceImpl.java @@ -1,12 +1,13 @@ package com.going.server.domain.upload.service; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.going.server.domain.graph.dto.edgeDto; -import com.going.server.domain.graph.dto.nodeDto; -import com.going.server.domain.graph.entity.Edge; -import com.going.server.domain.graph.repository.EdgeRepository; +import com.going.server.domain.graph.entity.Graph; +import com.going.server.domain.graph.entity.GraphEdge; +import com.going.server.domain.graph.entity.GraphNode; +import com.going.server.domain.graph.repository.GraphEdgeRepository; +import com.going.server.domain.graph.repository.GraphNodeRepository; +import com.going.server.domain.graph.repository.GraphRepository; import com.going.server.domain.ocr.OcrService; import com.going.server.domain.ocr.PdfOcrService; import com.going.server.domain.upload.dto.UploadRequestDto; @@ -19,16 +20,16 @@ import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; @Service @RequiredArgsConstructor public class UploadServiceImpl implements UploadService { private final OcrService ocrService; private final PdfOcrService pdfOcrService; - private final EdgeRepository edgeRepository; + private final GraphNodeRepository graphNodeRepository; + private final GraphEdgeRepository graphEdgeRepository; + private final GraphRepository graphRepository; @Value("${ocr.api.url}") private String apiUrl; @Value("${ocr.api.secret-key}") @@ -45,40 +46,90 @@ public UploadResponseDto uploadFile(UploadRequestDto dto) { ObjectMapper mapper = new ObjectMapper(); InputStream is = getClass().getClassLoader().getResourceAsStream("data.json"); JsonNode root = mapper.readTree(is); - JsonNode dataNode = root.get("data"); - JsonNode edgesNode = dataNode.get("edges"); + JsonNode graph = root.get("data"); + JsonNode nodesNode = graph.get("nodes"); + JsonNode edgesNode = graph.get("edges"); - List edgeList = new ArrayList<>(); + //node 데이터 파싱코드 + List nodeList = new ArrayList<>(); + for (JsonNode node : nodesNode) { + GraphNode graphNode = GraphNode.builder() + .nodeId(Long.parseLong(node.get("id").asText())) + .label(node.get("label").asText()) + .group(node.get("group").asText()) + .level(node.get("level").asLong()) + .includeSentence(node.get("includeSentence").asText()) + .build(); + nodeList.add(graphNode); + } + + //DB에 저장 + graphNodeRepository.saveAll(nodeList); + + //노드 ID로 빠르게 찾을 수 있게 Map 생성 + Map nodeIdToNode = new HashMap<>(); + for (GraphNode graphNode : nodeList) { + nodeIdToNode.put(String.valueOf(graphNode.getNodeId()), graphNode); + } + //edge 데이터 파싱 코드 + List edgeList = new ArrayList<>(); for (JsonNode edgeNode : edgesNode) { if (!edgeNode.has("source") || !edgeNode.has("target") || !edgeNode.has("label")) { System.out.println("필드 누락: " + edgeNode.toPrettyString()); continue; } - - String source = edgeNode.get("source").asText(); - String target = edgeNode.get("target").asText(); + String sourceId = edgeNode.get("source").asText(); + String targetId = edgeNode.get("target").asText(); String label = edgeNode.get("label").asText(); - Edge edge = Edge.builder() - .source(source) - .target(target) + GraphNode sourceNode = nodeIdToNode.get(sourceId); + GraphNode targetNode = nodeIdToNode.get(targetId); + + if (sourceNode == null || targetNode == null) { + System.out.println("노드 매칭 실패: source=" + sourceId + ", target=" + targetId); + continue; + } + + //edge 엔티티 생성 + GraphEdge edge = GraphEdge.builder() + .source(sourceId) .label(label) + .target(targetNode) .build(); - edgeList.add(edge); + // sourceNode에 edge 연결 + if (sourceNode.getEdges() == null) { + sourceNode.setEdges(new HashSet<>()); + } + sourceNode.getEdges().add(edge); + } + + //edge db에 저장 + graphNodeRepository.saveAll(nodeList); + + //그래프 생성 + String title = dto.getTitle(); + Graph graphEntity = Graph.builder() + .title(title) + .listenUpPerfect(false) + .connectPerfect(false) + .picturePerfect(false) + .nodes(nodeList) + .build(); + graphRepository.save(graphEntity); + + // 양방향 연결 설정 (GraphNode → Graph) + for (GraphNode graphNode : nodeList) { + graphNode.setGraph(graphEntity); } - edgeRepository.saveAll(edgeList); - System.out.println("총 " + edgeList.size() + "개의 edge가 저장되었습니다."); + // 저장 (이제 연결된 상태로 저장됨) + graphRepository.save(graphEntity); + graphNodeRepository.saveAll(nodeList); return UploadResponseDto.from( - null, - null, - null, - null, - null, - null + graphEntity.getId() ); } catch (IllegalArgumentException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); diff --git a/src/main/resources/data.json b/src/main/resources/data.json index 477c59f..f547def 100644 --- a/src/main/resources/data.json +++ b/src/main/resources/data.json @@ -9,21 +9,21 @@ "label": "동물", "group": 1, "level": 0, - "description": "기린은 세계에서 가장 키가 큰 동물로, 길게 뻗은 목을 이용해 높은 나무의 잎을 먹습니다. 초식동물이며 아프리카 사바나에서 주로 서식하고, 길고 튼튼한 다리로 천천히 이동하며, 천적을 피할 때는 매우 빠른 속도로 달릴 수 있습니다." + "includeSentence": "기린은 세계에서 가장 키가 큰 동물로, 길게 뻗은 목을 이용해 높은 나무의 잎을 먹습니다. 초식동물이며 아프리카 사바나에서 주로 서식하고, 길고 튼튼한 다리로 천천히 이동하며, 천적을 피할 때는 매우 빠른 속도로 달릴 수 있습니다." }, { "id": "2", "label": "고양이", "group": 2, "level": 1, - "description": "기린은 세계에서 가장 키가 큰 동물로, 길게 뻗은 목을 이용해 높은 나무의 잎을 먹습니다. 초식동물이며 아프리카 사바나에서 주로 서식하고, 길고 튼튼한 다리로 천천히 이동하며, 천적을 피할 때는 매우 빠른 속도로 달릴 수 있습니다." + "includeSentence": "기린은 세계에서 가장 키가 큰 동물로, 길게 뻗은 목을 이용해 높은 나무의 잎을 먹습니다. 초식동물이며 아프리카 사바나에서 주로 서식하고, 길고 튼튼한 다리로 천천히 이동하며, 천적을 피할 때는 매우 빠른 속도로 달릴 수 있습니다." }, { "id": "3", "label": "토끼", "group": 2, "level": 1, - "description": "기린은 세계에서 가장 키가 큰 동물로, 길게 뻗은 목을 이용해 높은 나무의 잎을 먹습니다. 초식동물이며 아프리카 사바나에서 주로 서식하고, 길고 튼튼한 다리로 천천히 이동하며, 천적을 피할 때는 매우 빠른 속도로 달릴 수 있습니다." + "includeSentence": "기린은 세계에서 가장 키가 큰 동물로, 길게 뻗은 목을 이용해 높은 나무의 잎을 먹습니다. 초식동물이며 아프리카 사바나에서 주로 서식하고, 길고 튼튼한 다리로 천천히 이동하며, 천적을 피할 때는 매우 빠른 속도로 달릴 수 있습니다." }, { @@ -31,28 +31,28 @@ "label": "기린", "group": 2, "level": 1, - "description": "기린은 세계에서 가장 키가 큰 동물로, 길게 뻗은 목을 이용해 높은 나무의 잎을 먹습니다. 초식동물이며 아프리카 사바나에서 주로 서식하고, 길고 튼튼한 다리로 천천히 이동하며, 천적을 피할 때는 매우 빠른 속도로 달릴 수 있습니다." + "includeSentence": "기린은 세계에서 가장 키가 큰 동물로, 길게 뻗은 목을 이용해 높은 나무의 잎을 먹습니다. 초식동물이며 아프리카 사바나에서 주로 서식하고, 길고 튼튼한 다리로 천천히 이동하며, 천적을 피할 때는 매우 빠른 속도로 달릴 수 있습니다." }, { "id": "5", "label": "원숭이", "group": 2, "level": 1, - "description": "원숭이는 사람과 유전적으로 가까운 영장류에 속하며, 높은 지능과 손을 이용한 다양한 도구 사용 능력을 갖추고 있습니다. 나무를 잘 타며 무리를 지어 생활하고, 감정 표현이 풍부하며 사회적 행동이 발달해 있습니다." + "includeSentence": "원숭이는 사람과 유전적으로 가까운 영장류에 속하며, 높은 지능과 손을 이용한 다양한 도구 사용 능력을 갖추고 있습니다. 나무를 잘 타며 무리를 지어 생활하고, 감정 표현이 풍부하며 사회적 행동이 발달해 있습니다." }, { "id": "6", "label": "추론", "group": 2, "level": 2, - "description": "추론은 관찰하거나 알고 있는 사실을 바탕으로 새로운 사실이나 결론을 이끌어내는 사고 과정입니다. 예를 들어, 고양이가 생선을 먹는다는 사실을 안다면, 생선을 보고 고양이가 있을 가능성을 추론할 수 있습니다. 과학적 탐구나 문제 해결에서 매우 중요한 능력입니다." + "includeSentence": "추론은 관찰하거나 알고 있는 사실을 바탕으로 새로운 사실이나 결론을 이끌어내는 사고 과정입니다. 예를 들어, 고양이가 생선을 먹는다는 사실을 안다면, 생선을 보고 고양이가 있을 가능성을 추론할 수 있습니다. 과학적 탐구나 문제 해결에서 매우 중요한 능력입니다." }, { "id": "7", "label": "생선", "group": 2, "level": 2, - "description": "생선은 물속에 사는 척추동물로, 지느러미와 비늘을 가지고 있으며 아가미로 숨을 쉽니다. 다양한 종류가 있으며, 많은 동물과 사람에게 중요한 단백질 공급원입니다. 고양이는 생선을 매우 좋아하는 동물로 자주 묘사됩니다." + "includeSentence": "생선은 물속에 사는 척추동물로, 지느러미와 비늘을 가지고 있으며 아가미로 숨을 쉽니다. 다양한 종류가 있으며, 많은 동물과 사람에게 중요한 단백질 공급원입니다. 고양이는 생선을 매우 좋아하는 동물로 자주 묘사됩니다." } ], "edges": [