12/1 ?, 스웨거

AI·2025년 12월 1일
package com.mycom.myapp.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.mycom.myapp.dto.StudentDto;
import com.mycom.myapp.dto.StudentResultDto;
import com.mycom.myapp.service.StudentServiceCrud;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/re") // /api/v1  <= 버전을 추가하는 경우도 있다.
@RequiredArgsConstructor

// ResponseEntity를 이용하는 코드는 브라우저와 spring 간의 응답 처리에 대해 직접 관여 O
// - spring에게 의뢰 처리
// - 기존 응답 객체를 ResponseEntity로 감싼다(wrapper)
// => ResponseEntity의 설정 값을 확인하고 HttpResponse 코드 변경
// - ResponseEntity 객체에 응답 코드 설정
// ResponseEntity를 통해서 작업 결과 추가로 전달 <= ResultDto와 의미 중복이 되는 문제
// 선택 1: ResponseEntity만 사용
// 선택 2: ~ResultDto만 사용
// 선택 3: 함께 사용, - ex. 프론트와 협의 정상, 비정상 구분, 비정상 중에 어떤 원인.. 등 복잡한 응답구조가 필요할때
public class StudentControllerCrudResponseEntity {
	
	// StdentServiceCrud DI
	private final StudentServiceCrud studentServiceCrud;
	
	// 목록 get /students
	@GetMapping("/students")
	public ResponseEntity<StudentResultDto> listStudent(){
//		return studentServiceCrud.listStudent();
		StudentResultDto studentResultDto = studentServiceCrud.listStudent();
//		return new ResponseEntity<StudentResultDto>(studentResultDto, HttpStatus.OK);
//		return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
//		return new ResponseEntity<StudentResultDto>(studentResultDto, HttpStatus.NOT_FOUND); // 비추 = 응답과 불일치하기에
//		return new ResponseEntity<StudentResultDto>(studentResultDto, HttpStatus.INTERNAL_SERVER_ERROR);
		
		// 200
//		return ResponseEntity
//				.status(HttpStatus.OK)
//				.body(studentResultDto);
		
//		return ResponseEntity
//				.ok()
//				.body(studentResultDto);
		
		// 404
//		return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
//		return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); // Build the response entity with no body.
		
		// 500
//		return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
		return ResponseEntity.internalServerError().build();
	}
	// 상세 get /students/123
	@GetMapping("/students/{id}")
	public StudentResultDto detailStudent(@PathVariable("id") Integer id){
		return studentServiceCrud.detailStudent(id);
	}
	
	// 등록 post /students
	@PostMapping("/students")
	public StudentResultDto insertStudent(StudentDto studentDto) {
		return studentServiceCrud.insertStudent(studentDto);
	}
	
	// 수정
	@PutMapping("/students/{id}")
	public StudentResultDto updateStudent(@PathVariable("id") Integer id, StudentDto studentDto) {
		// Parameter StudentDto 객체의 id 필드를 명시적으로 처리
		// Parameter StudentDto 객체의 id 필드보다 PathVariable 로 넘어오는 id 를 우선시
		studentDto.setId(id);
		return studentServiceCrud.updateStudent(studentDto);
	}
	
	// 삭제
	@DeleteMapping("/students/{id}")
	public StudentResultDto deleteStudent(@PathVariable("id") Integer id) {
		return studentServiceCrud.deleteStudent(id);
	}
	
	// 전체 건수
	@GetMapping("/students/count")
	public StudentResultDto countStudent() {
		return studentServiceCrud.countStudent();
	}
	
	// 페이징
	// REST API 에서 반드시 PathVariable 또는 RequestParam 을 써야 한다는 내용 X
	// 우리는 PathVariable 사용.
	@GetMapping("/students/page/{pageNumber}/{pageSize}")
	public StudentResultDto listStudent(
			@PathVariable("pageNumber") Integer pageNumber, 
			@PathVariable("pageSize") Integer pageSize) {
		return studentServiceCrud.listStudent(pageNumber, pageSize);
	}
}

얼마가 충분한가의 판단이 중요
중요한 코드는 비관적으로 작성 - 성능 이슈, 비용 증가이기에 필수적인 곳에만 사용

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Student Test</title>
</head>
<body>
	<h1>Student.html</h1>
	<hr>
	<table>
		<thead>
			<tr><td>Id</td><td>Name</td><td>email</td><td>phone</td></tr>
		</thead>
		<tbody id="tbodyStudent"></tbody>
	</table>
	<hr>
	<form>
		<table>
			<tbody>
				<tr><td>Name</td><td><input type="text" name="name" id="inputName"></td></tr>
				<tr><td>Email</td><td><input type="text" name="email" id="inputEmail"></td></tr>
				<tr><td>Phone</td><td><input type="text" name="phone" id="inputPhone"></td></tr>
			</tbody>
		</table>
	</form>
	<div style="border: 1px; margin: auto; text-align: center">
		<button id="btnInsert">등록</button>
		<button id="btnUpdate">수정</button>
		<button id="btnDelete">삭제</button>
		<button id="btnClear">초기화</button>
	</div>
	
	<script>

		let CURRENT_ID;
		
		window.onload = function(){
			
			listStudent();
			
			// 이벤트 핸들러 등록
			document.querySelector("#btnInsert").onclick = insertStudent;
			document.querySelector("#btnUpdate").onclick = updateStudent;			
			document.querySelector("#btnDelete").onclick = deleteStudent;
			document.querySelector("#btnClear").onclick = clear;
		}
	
		// ResponseEntity에 대응하는 코드로 변경
		async function listStudent(){
			
			let url = '/api/re/students';
			
// 			// 낙관적 대응
// 			try{
// 				let response = await fetch(url);
// 				console.log(response)
// 				let data = await response.json(); // 백엔드에서 body가 null로 리턴
// 				makeListHtml( data.list );
// 				// 위 코드 대신data.list 대신 
// 				// let {list} = await response.json();
// 				// makeListHtml(list) 를 사용할 수도 있다.
// 			}catch(error){
// 				alert("학생 조회 과정 중 오류가 발생했습니다.")
// 				console.log(error)
// 			}

			//비낙관적 대응
			// respose의 응답 코드 확인
			// ok 또는 status 항목으로 판단
			try{
				let response = await fetch(url);
				console.log(response)
				
				if(response.ok){
					let data = await response.json(); // 백엔드에서 body가 null로 리턴 안 할수도 있다
					makeListHtml( data.list );
				} else{
					// #1 . 일반화된 오류 출력
// 					alert("학생 조회 과정 중 오류 발생");
					
					// #2. 세분화된 오류 출력
					switch(response.status){
						case 404: alert("학생 데이터를 찾을 수 없음"); break;
						case 500: alert("학생 조회 과정 중 서버 오류 발생"); break;
					}
				}
			}catch(error){
				alert("학생 조회 과정 중 오류가 발생했습니다.")
				console.log(error)
			}

		}
		
		function makeListHtml(list){
			let listHtml = ``;
			list.forEach( el => {
				let id = el.id;
				let name = el.name;
				let email = el.email;
				let phone = el.phone;
				
				listHtml += `<tr style="cursor: pointer;" data-id=${id}><td>${id}</td><td>${name}</td><td>${email}</td><td>${phone}</td></tr>`;
			});
			
			document.querySelector("#tbodyStudent").innerHTML = listHtml;
			
			document.querySelectorAll("#tbodyStudent tr").forEach( el => {
				el.onclick = function(){
					let id = this.getAttribute("data-id");
					detailStudent(id);
				}
			});
		}

		// studnetResultDto 대신 ResponseEntity의 status 코드와 studentDto 응답
		async function detailStudent(id){
		
			let url = '/api/re/students/' + id;

// 			try{
// 				let response = await fetch(url);
// 				console.log(response)
// 				let {dto} = await response.json(); // javascript es6 destruction 문법
// 				makeDetailHtml( dto );
// 			}catch(error){
// 				console.log("detail error")
// 				console.log(error)
// 			}
			
			try{
				let response = await fetch(url);
				console.log(response)
				
				if(response.ok){
					let data = await response.json(); // 백엔드에서 body가 null로 리턴 안 할수도 있다
					makeListHtml( data ); // studnetResultDto의 일부인 studnetDto가 바로 응답
				} else{
					// #1 . 일반화된 오류 출력
// 					alert("학생 조회 과정 중 오류 발생");
					
					// #2. 세분화된 오류 출력
					switch(response.status){
						case 404: alert("학생 데이터를 찾을 수 없음"); break;
						case 500: alert("학생 조회 과정 중 서버 오류 발생"); break;
					}
				}
			}catch(error){
				alert("학생 조회 과정 중 오류가 발생했습니다.")
				console.log(error)
			}
	
		}
		
		function makeDetailHtml(detail){
			console.log(detail);
			
			CURRENT_ID = detail.id;
			
			document.querySelector("#inputName").value = detail.name;
			document.querySelector("#inputEmail").value = detail.email;
			document.querySelector("#inputPhone").value = detail.phone;
			
		}
		
		async function insertStudent(){
			
			let url = '/api/students/';
			
			let name = document.querySelector("#inputName").value;
			let email = document.querySelector("#inputEmail").value;
			let phone = document.querySelector("#inputPhone").value;

			let urlParams = new URLSearchParams({
				name, email, phone   // javascript es6 shorthand property
		    })

			let fetchOptions = {
				method : "POST",
		        body: urlParams,
			}
			
			try{
				let response = await fetch(url, fetchOptions)
				let data = await response.json();
				console.log(data);
				listStudent();
				clear();
			}catch(error){ // BE 404 에러는 이곳에
				console.log(error)
			}
		}

		async function updateStudent(){

			let url = '/api/students/' + CURRENT_ID;

			let id = CURRENT_ID;
			let name = document.querySelector("#inputName").value;
			let email = document.querySelector("#inputEmail").value;
			let phone = document.querySelector("#inputPhone").value;
			
			let urlParams = new URLSearchParams({
				id, name, email, phone
		    })
			
			let fetchOptions = {
				method : "PUT",
		        body: urlParams,
			}
			
			try{
				let response = await fetch(url, fetchOptions)
				let data = await response.json();
				console.log(data);
				listStudent();
				clear();
			}catch(error){
				console.log(error)
			}
		}
		
		async function deleteStudent(){
			let url = '/api/students/' + CURRENT_ID;

			let fetchOptions = {
				method : "DELETE",
			}
			
			try{
				let response = await fetch(url, fetchOptions)
				let data = await response.json();
				console.log(data);
				listStudent();
				clear();
			}catch(error){
				console.log(error)
			}
		}
		
		function clear(){
			document.querySelector("#inputName").value = "";
			document.querySelector("#inputEmail").value = "";
			document.querySelector("#inputPhone").value = "";
		}
		
	</script>	
</body>
</html>
	// ResultDto와 ResponseEntity 함께 사용 예시
	// 작업 결과는 ResponseEntity의 status로, 데이터는 ResultDto => studentDto만, 결과는 제외
	// 상세 get /students/123
	@GetMapping("/students/{id}")
	public ResponseEntity<StudentDto> detailStudent(@PathVariable("id") Integer id){
		StudentResultDto studentResultDto = studentServiceCrud.deleteStudent(id);
		String result = studentResultDto.getResult();
		switch(result) {
			case "success" : return ResponseEntity.ok().body(studentResultDto.getDto());
			case "notfound" : return ResponseEntity.notFound().build();
			case "fail" : 
			default : return ResponseEntity.internalServerError().build();
		}
	}

DR = 장애 발생 복구

https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.html
https://docs.spring.io/spring-framework/reference/6.0/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.html
// @RequestBody 로 선언되지 않은 Dto 는 form data, query parameters, 그리고 URI path variables 로부터 일치하는 필드 자동 값 지정
// @RequestBody 로 선언된 Dto 는 제외

package com.mycom.myapp.controller;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.mycom.myapp.dto.StudentDto;
import com.mycom.myapp.dto.StudentResultDto;
import com.mycom.myapp.service.StudentServiceCrud;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/re") // /api/v1  <= 버전을 추가하는 경우도 있다.
@RequiredArgsConstructor

// ResponseEntity를 이용하는 코드는 브라우저와 spring 간의 응답 처리에 대해 직접 관여 O
// - spring에게 의뢰 처리
// - 기존 응답 객체를 ResponseEntity로 감싼다(wrapper)
// => ResponseEntity의 설정 값을 확인하고 HttpResponse 코드 변경
// - ResponseEntity 객체에 응답 코드 설정
// ResponseEntity를 통해서 작업 결과 추가로 전달 <= ResultDto와 의미 중복이 되는 문제
// 선택 1: ResponseEntity만 사용
// 선택 2: ~ResultDto만 사용
// 선택 3: 함께 사용, - ex. 프론트와 협의 정상, 비정상 구분, 비정상 중에 어떤 원인.. 등 복잡한 응답구조가 필요할때
public class StudentControllerCrudResponseEntity {
	
	// StdentServiceCrud DI
	private final StudentServiceCrud studentServiceCrud;
	
	// 목록 get /students
	@GetMapping("/students")
	public ResponseEntity<StudentResultDto> listStudent(){
//		return studentServiceCrud.listStudent();
		StudentResultDto studentResultDto = studentServiceCrud.listStudent();
//		return new ResponseEntity<StudentResultDto>(studentResultDto, HttpStatus.OK);
//		return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
//		return new ResponseEntity<StudentResultDto>(studentResultDto, HttpStatus.NOT_FOUND); // 비추 = 응답과 불일치하기에
//		return new ResponseEntity<StudentResultDto>(studentResultDto, HttpStatus.INTERNAL_SERVER_ERROR);
		
		// 200
//		return ResponseEntity
//				.status(HttpStatus.OK)
//				.body(studentResultDto);
		
		return ResponseEntity
				.ok()
				.body(studentResultDto);
		
		// 404
//		return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
//		return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); // Build the response entity with no body.
		
		// 500
//		return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
//		return ResponseEntity.internalServerError().build();
	}
	
	// ResultDto와 ResponseEntity 함께 사용 예시
	// 작업 결과는 ResponseEntity의 status로, 데이터는 ResultDto => studentDto만, 결과는 제외
	// 상세 get /students/123
	@GetMapping("/students/{id}")
	public ResponseEntity<StudentDto> detailStudent(@PathVariable("id") Integer id){
		StudentResultDto studentResultDto = studentServiceCrud.detailStudent(id);
		String result = studentResultDto.getResult();
		switch(result) {
			case "success" : return ResponseEntity.ok().body(studentResultDto.getDto());
			case "notfound" : return ResponseEntity.notFound().build();
			case "fail" : 
			default : return ResponseEntity.internalServerError().build();
		}
	}
	
	/////////////////////////////////////////////////////////////////
	// 등록 post /students
	@PostMapping("/students")
	public ResponseEntity<String> insertStudent(StudentDto studentDto) {
		StudentResultDto studentResultDto = studentServiceCrud.insertStudent(studentDto);
		System.out.println(studentResultDto);
		String result = studentResultDto.getResult();
		System.out.println("r "+result);
		switch(result) {
			case "success" : return ResponseEntity.ok().build();
			case "notfound" : return ResponseEntity.notFound().build();
			case "fail" : 
			default : return ResponseEntity.internalServerError().build();
		}
	}
	
	// 수정
	@PutMapping("/students/{id}")
	public ResponseEntity<StudentDto> updateStudent(@PathVariable("id") Integer id, @RequestBody StudentDto studentDto) {
		// Parameter StudentDto 객체의 id 필드를 명시적으로 처리
		// Parameter StudentDto 객체의 id 필드보다 PathVariable 로 넘어오는 id 를 우선시
		studentDto.setId(id);
		StudentResultDto studentResultDto = studentServiceCrud.updateStudent(studentDto);
		System.out.println("update "+studentResultDto);
		String result = studentResultDto.getResult();
		System.out.println("r "+result);
		switch(result) {
			case "success" : return ResponseEntity.ok().body(studentResultDto.getDto());
			case "notfound" : return ResponseEntity.notFound().build();
			case "fail" : 
			default : return ResponseEntity.internalServerError().build();
		}
	}
	
	// 삭제
	@DeleteMapping("/students/{id}")
	public ResponseEntity<StudentResultDto> deleteStudent(@PathVariable("id") Integer id) {
		StudentResultDto studentResultDto = studentServiceCrud.deleteStudent(id);
		return ResponseEntity
				.ok()
				.body(studentResultDto);
	}
	
	//////////////////////////////////////////////////////////////////////////
	// 전체 건수
	@GetMapping("/students/count")
	public ResponseEntity<Long> countStudent() {
		StudentResultDto studentResultDto = studentServiceCrud.countStudent();
		String result = studentResultDto.getResult();
		switch (result) {
			// "success" 또는 "" 또는 StudentDto (id 포함한) 이 경우 Service Layer 리턴을 변경
			case "success" : return ResponseEntity.ok().body(studentResultDto.getCount()); 
			case "fail" : 
			default : return ResponseEntity.internalServerError().build();
		}		
	}
	
	// 페이징
	// REST API 에서 반드시 PathVariable 또는 RequestParam 을 써야 한다는 내용 X
	// 우리는 PathVariable 사용.
	// 목록 전체를 가져오는 ResponseEntity<StudentResultDto> listStudent와 비교
	@GetMapping("/students/page/{pageNumber}/{pageSize}")
	public ResponseEntity<List<StudentDto>> listStudent(
			@PathVariable("pageNumber") Integer pageNumber, 
			@PathVariable("pageSize") Integer pageSize) {
		
		StudentResultDto studentResultDto = studentServiceCrud.listStudent(pageNumber, pageSize);
		String result = studentResultDto.getResult();
		switch (result) {
			// "success" 또는 "" 또는 StudentDto (id 포함한) 이 경우 Service Layer 리턴을 변경
			case "success" : return ResponseEntity.ok().body(studentResultDto.getList()); 
			case "fail" : 
			default : return ResponseEntity.internalServerError().build();
		}		
	}
}

==

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Student Test</title>
</head>
<body>
	<h1>Student.html</h1>
	<hr>
	<table>
		<thead>
			<tr><td>Id</td><td>Name</td><td>email</td><td>phone</td></tr>
		</thead>
		<tbody id="tbodyStudent"></tbody>
	</table>
	<hr>
	<form>
		<table>
			<tbody>
				<tr><td>Name</td><td><input type="text" name="name" id="inputName"></td></tr>
				<tr><td>Email</td><td><input type="text" name="email" id="inputEmail"></td></tr>
				<tr><td>Phone</td><td><input type="text" name="phone" id="inputPhone"></td></tr>
			</tbody>
		</table>
	</form>
	<div style="border: 1px; margin: auto; text-align: center">
		<button id="btnInsert">등록</button>
		<button id="btnUpdate">수정</button>
		<button id="btnDelete">삭제</button>
		<button id="btnClear">초기화</button>
	</div>
	
	<script>

		let CURRENT_ID;
		
		window.onload = function(){
			
			listStudent();
			
			// 이벤트 핸들러 등록
			document.querySelector("#btnInsert").onclick = insertStudent;
			document.querySelector("#btnUpdate").onclick = updateStudent;			
			document.querySelector("#btnDelete").onclick = deleteStudent;
			document.querySelector("#btnClear").onclick = clear;
		}
	
		// ResponseEntity에 대응하는 코드로 변경
		async function listStudent(){
			
			let url = '/api/re/students';
			
// 			// 낙관적 대응
// 			try{
// 				let response = await fetch(url);
// 				console.log(response)
// 				let data = await response.json(); // 백엔드에서 body가 null로 리턴
// 				makeListHtml( data.list );
// 				// 위 코드 대신data.list 대신 
// 				// let {list} = await response.json();
// 				// makeListHtml(list) 를 사용할 수도 있다.
// 			}catch(error){
// 				alert("학생 조회 과정 중 오류가 발생했습니다.")
// 				console.log(error)
// 			}

			//비낙관적 대응
			// respose의 응답 코드 확인
			// ok 또는 status 항목으로 판단
			try{
				let response = await fetch(url);
				console.log(response)
				
				if(response.ok){
					let data = await response.json(); // 백엔드에서 body가 null로 리턴 안 할수도 있다
					makeListHtml( data.list );
				} else{
					// #1 . 일반화된 오류 출력
// 					alert("학생 조회 과정 중 오류 발생");
					
					// #2. 세분화된 오류 출력
					switch(response.status){
						case 404: alert("학생 데이터를 찾을 수 없음"); break;
						case 500: alert("학생 조회 과정 중 서버 오류 발생"); break;
					}
				}
			}catch(error){
				alert("학생 조회 과정 중 오류가 발생했습니다.")
				console.log(error)
			}

		}
		
		function makeListHtml(list){
			let listHtml = ``;
			list.forEach( el => {
				let id = el.id;
				let name = el.name;
				let email = el.email;
				let phone = el.phone;
				
				listHtml += `<tr style="cursor: pointer;" data-id=${id}><td>${id}</td><td>${name}</td><td>${email}</td><td>${phone}</td></tr>`;
			});
			
			document.querySelector("#tbodyStudent").innerHTML = listHtml;
			
			document.querySelectorAll("#tbodyStudent tr").forEach( el => {
				el.onclick = function(){
					let id = this.getAttribute("data-id");
					detailStudent(id);
				}
			});
		}

		// studnetResultDto 대신 ResponseEntity의 status 코드와 studentDto 응답
		async function detailStudent(id){
		
			let url = '/api/re/students/' + id;

// 			try{
// 				let response = await fetch(url);
// 				console.log(response)
// 				let {dto} = await response.json(); // javascript es6 destruction 문법
// 				makeDetailHtml( dto );
// 			}catch(error){
// 				console.log("detail error")
// 				console.log(error)
// 			}
			
			try{
				let response = await fetch(url);
				console.log(response)
				
				if(response.ok){
					let data = await response.json(); // 백엔드에서 body가 null로 리턴 안 할수도 있다
					makeDetailHtml( data ); // studnetResultDto의 일부인 studnetDto가 바로 응답
				} else{
					// #1 . 일반화된 오류 출력
// 					alert("학생 조회 과정 중 오류 발생");
					
					// #2. 세분화된 오류 출력
					switch(response.status){
						case 404: alert("학생 데이터를 찾을 수 없음"); break;
						case 500: alert("학생 조회 과정 중 서버 오류 발생"); break;
					}
				}
			}catch(error){
				alert("학생 조회 과정 중 오류가 발생했습니다.")
				console.log(error)
			}
	
		}
		
		function makeDetailHtml(detail){
			console.log(detail);
			
			CURRENT_ID = detail.id;
			
			document.querySelector("#inputName").value = detail.name;
			document.querySelector("#inputEmail").value = detail.email;
			document.querySelector("#inputPhone").value = detail.phone;
			
		}
		
		async function insertStudent(){
			
			let url = '/api/re/students';
			
			let name = document.querySelector("#inputName").value;
			let email = document.querySelector("#inputEmail").value;
			let phone = document.querySelector("#inputPhone").value;

			let urlParams = new URLSearchParams({
				name, email, phone   // javascript es6 shorthand property
		    })

			let fetchOptions = {
				method : "POST",
		        body: urlParams,
			}
			
			try{
				let response = await fetch(url, fetchOptions);
				console.log(response)
				
				if(response.ok){
// 					let data = await response.json(); 
					listStudent();
					clear();
				} else{
					switch(response.status){
						case 404: alert("학생 데이터를 넣을 수 없음"); break;
						case 500: alert("학생 삽입 과정 중 서버 오류 발생"); break;
					}
				}
			}catch(error){
				alert("학생 삽입 과정 중 오류가 발생했습니다.")
				console.log(error)
			}
		}

		async function updateStudent(){

			let url = '/api/re/students/' + CURRENT_ID;

			let id = CURRENT_ID;
			let name = document.querySelector("#inputName").value;
			let email = document.querySelector("#inputEmail").value;
			let phone = document.querySelector("#inputPhone").value;
			
// 			let urlParams = new URLSearchParams({
// 				id, name, email, phone
// 		    })
			let jsonParams = JSON.stringify( {id, name, email, phone} );
			
			let fetchOptions = {
				method : "PUT",
				headers: {
					"Content-Type" : "application/json"
				},
		        body: jsonParams,
			}
			
			
			try{
				let response = await fetch(url, fetchOptions);
				console.log(response)
				
				if(response.ok){
// 					let data = await response.json(); 
					listStudent();
					clear();
				} else{
					switch(response.status){
						case 404: alert("학생 데이터를 수정할 수 없음"); break;
						case 500: alert("학생 수정 과정 중 서버 오류 발생"); break;
					}
				}
			}catch(error){
				alert("학생 수정 과정 중 오류가 발생했습니다.")
				console.log(error)
			}
		}
		
		async function deleteStudent(){
			let url = '/api/re/students/' + CURRENT_ID;

			let fetchOptions = {
				method : "DELETE",
			}
			
			
			try{
				let response = await fetch(url, fetchOptions);
				console.log(response)
				
				if(response.ok){
					let data = await response.json(); 
					listStudent();
					clear();
				} else{
					switch(response.status){
						case 404: alert("학생 데이터를 삭제할 수 없음"); break;
						case 500: alert("학생 삭제 과정 중 서버 오류 발생"); break;
					}
				}
			}catch(error){
				alert("학생 삭제 과정 중 오류가 발생했습니다.")
				console.log(error)
			}
		}
		
		function clear(){
			document.querySelector("#inputName").value = "";
			document.querySelector("#inputEmail").value = "";
			document.querySelector("#inputPhone").value = "";
		}
		
	</script>	
</body>
</html>

Swagger(스웨거)

비즈니스 로직이 아니라 사용자 매뉴얼+테스트
문서도 무시해서는 안됨. 설계 문서를 보고 개발을 해야하기 때문. 실제 고객과 할때는 서명이 들어가기 때문
https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui

http://localhost:8080/swagger-ui/index.html#

package com.mycom.myapp.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;

@Configuration
public class SwaggerConfig {
	@Bean
	OpenAPI openApi() {
		return new OpenAPI()
				.components(new Components())
				.info(apiInfo());
	}
	
	private Info apiInfo() {
		return new Info()
				.title("학생 관리 API")
				.description("REST API로 구현된 학생 관리 기능을 테스트합니다.")
				.version("v0.9");
	}
}

Controller에 @Tag(name="",description="") 추가
각 함수에는 @Operation(summary="", description="") 추가, deprecated, hidden도 존재

인터페이스로 처리도 가능

package com.mycom.myapp.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import com.mycom.myapp.dto.StudentDto;
import com.mycom.myapp.dto.StudentResultDto;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name="json student CRUD REST API",description="json요청을 통해 student의 CRUD기능을 REST API로 제공합니다")
public interface StudentControllerCrudJsonRequestSwagger {
	// 목록 get /students
	@Operation(summary="학생 목록", description="전체 학생 목록 조회")
	@GetMapping("/students")
	StudentResultDto listStudent();
	
	// 상세 get /students/123
	@Operation(summary="학생 상세", description="개별 학생 1명 조회")
	@GetMapping("/students/{id}")
	StudentResultDto detailStudent(@PathVariable("id") Integer id);
	
	// 등록 post /students
	@Operation(summary="학생 등록", description="json 요청을 통해 신규 학생 1명 등록")
	@PostMapping("/students")
	StudentResultDto insertStudent(StudentDto studentDto);
}

http://localhost:8080/v3/api-docs를 postman에 import를 해서 바로 실행해볼 수 있다.

path대신 tag로 수정하면 아래와 같이 좀 더 보기 편하게 된다

0개의 댓글