@@ -66,6 +66,7 @@ public Map<String, Object> executeMath(Long logSolveId, int grade) {
6666
6767 String gptContent = sendGptRequest (payload );
6868 Map <String , Object > result = parseGptJson (gptContent );
69+ validateMathJson (result );
6970 String resultJson = mapper .writerWithDefaultPrettyPrinter ().writeValueAsString (result );
7071
7172 logSolve .setResult (resultJson );
@@ -122,33 +123,70 @@ private String buildPromptByGrade(int grade) {
122123
123124
124125 public ResponseEntity <?> handleSolveImage (MultipartFile imageFile , User user , UserJr userJr , int grade ) {
126+ Image image = null ;
127+ LogSolve logSolve = null ;
128+
125129 try {
126130 if (grade < 1 || grade > 6 ) {
127131 return ResponseEntity .badRequest ().body (Map .of ("message" , "학년 정보가 올바르지 않습니다 (1~6학년만 허용)" ));
128132 }
129133
130- Long logSolveId = createLogAndReturnId (imageFile , user , userJr );
131- executeMath (logSolveId , grade );
134+ ImageResponseDto imageDto = imageService .uploadToS3AndSave (imageFile , ImageType .ETC , user );
135+ image = imageRepository .findById (imageDto .getImageId ())
136+ .orElseThrow (() -> new RuntimeException ("이미지 없음" ));
137+
138+ // 2. log_solve에 임시로 저장
139+ logSolve = logSolveRepository .save (
140+ LogSolve .builder ()
141+ .image (image )
142+ .user (user )
143+ .userJr (userJr )
144+ .build ()
145+ );
146+
147+ Map <String , Object > result = executeMath (logSolve .getLogSolveId (), grade );
148+
149+ String resultJson = new ObjectMapper ().writerWithDefaultPrettyPrinter ().writeValueAsString (result );
150+ logSolve .setResult (resultJson );
151+ logSolveRepository .save (logSolve );
152+
153+ return ResponseEntity .ok (Map .of ("message" , "AI 풀이 완료" , "logSolveId" , logSolve .getLogSolveId ()));
132154
133- return ResponseEntity .ok (Map .of ("message" , "AI 풀이 완료" , "logSolveId" , logSolveId ));
134155 } catch (Exception e ) {
135- return ResponseEntity .internalServerError ().body (Map .of ("message" , "AI 처리 실패" , "error" , e .getMessage ()));
156+ try {
157+ if (logSolve != null ) {
158+ logSolveRepository .delete (logSolve );
159+ }
160+ if (image != null && image .getUrl () != null ) {
161+ s3Uploader .delete (image .getUrl ());
162+ imageRepository .delete (image );
163+ }
164+ } catch (Exception cleanupEx ) {
165+ log .warn ("정리 중 오류 발생" , cleanupEx );
166+ }
167+
168+ return ResponseEntity .internalServerError ().body (Map .of (
169+ "message" , "AI 처리 실패" ,
170+ "error" , e .getMessage ()
171+ ));
136172 }
137173 }
138174
175+ private void validateMathJson (Map <String , Object > json ) {
176+ List <String > requiredKeys = List .of (
177+ "problem_title" , "problem_text" , "answer" ,
178+ "core_concept" , "parent_explanation" , "explanation_steps"
179+ );
139180
140- public Long createLogAndReturnId (MultipartFile imageFile , User user , UserJr userJr ) throws IOException {
141- ImageResponseDto imageDto = imageService .uploadToS3AndSave (imageFile , ImageType .ETC , user );
142- Image image = imageRepository .findById (imageDto .getImageId ())
143- .orElseThrow (() -> new RuntimeException ("이미지 없음" ));
181+ for (String key : requiredKeys ) {
182+ if (!json .containsKey (key ) || json .get (key ) == null || json .get (key ).toString ().isBlank ()) {
183+ throw new IllegalArgumentException ("수학 문제가 아닌 이미지입니다. 누락된 필드: " + key );
184+ }
185+ }
144186
145- return logSolveRepository .save (
146- LogSolve .builder ()
147- .image (image )
148- .user (user )
149- .userJr (userJr )
150- .build ()
151- ).getLogSolveId ();
187+ if (!(json .get ("explanation_steps" ) instanceof List <?> steps ) || steps .isEmpty ()) {
188+ throw new IllegalArgumentException ("수학 문제가 아닌 이미지입니다. explanation_steps가 비어 있음" );
189+ }
152190 }
153191
154192
0 commit comments