3 ๋ถ„ ์†Œ์š”

๐Ÿ“š SPRINGBOOT


๐Ÿ“š SWAGGER

SWAGGER ๋ž€

SWAGGER ์‚ฌ์šฉ ์ด์œ 

  • ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ ์‹œ ์ผ๋ฐ˜์ ์œผ๋กœ FrontEnd ๊ฐœ๋ฐœ์ž์™€ BackEnd ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ถ„๋ฆฌ๋œ๋‹ค.
  • FrontEnd ๊ฐœ๋ฐœ์ž์˜ ๊ฒฝ์šฐ ํ™”๋ฉด๊ณผ ๋กœ์ง์— ์ง‘์ค‘ํ•˜๊ณ  BackEnd ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งŒ๋“  ๋ฌธ์„œ API๋ฅผ ๋ณด๋ฉฐ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ฒŒ ๋œ๋‹ค.
  • ์ด๋•Œ ๊ฐœ๋ฐœ ์ƒํ™ฉ์˜ ๋ณ€ํ™”์— ๋”ฐ๋ฅธ API์˜ ์ถ”๊ฐ€ ๋˜๋Š” ๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค ๋ฌธ์„œ์— ์ ์šฉํ•˜๋Š” ๋ถˆํŽธํ•จ์ด ๋ฐœ์ƒํ•œ๋‹ค.
  • ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Swagger๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • ํ•œ ๋งˆ๋””๋กœ FrontEnd ๊ฐœ๋ฐœ์ž์™€ BackEnd ๊ฐœ๋ฐœ์ž์˜ ์˜์‚ฌ์†Œํ†ต์„ ๋•๋Š”๋‹ค.
  • Swagger๋ฅผ ๋ณด๋ฉฐ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์„ ๋ณด๊ณ  ๊ฑฐ๊ธฐ์— ๋”ฐ๋ผ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•œ๋‹ค.

SWAGGER ์ ์šฉ

pom.xml์— dependency ์ถ”๊ฐ€

        <dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>
        <dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>
  • Swagger3
    • Swagger3 ๋ถ€ํ„ฐ๋Š” 1๊ฐœ๋กœ ๋‹ค ์„ค์ •๋œ๋‹ค.
        <dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-boot-starter</artifactId>
			<version>3.0.0</version>
		</dependency>

application.properties

  • SpringBoot 2.6์ด์ƒ Swagger3.0 ์ ์šฉ์‹œ ์—๋Ÿฌ ํ•ด๊ฒฐ์„ ์œ„ํ•ด ์„ค์ •์„ ํ•ด์•ผํ•œ๋‹ค.
    • SpringBoot 2.6.0 ๋ถ€ํ„ฐ ์š”์ฒญ ๊ฒฝ๋กœ๋ฅผ ControllerHandler์— ๋งค์นญ์‹œํ‚ค๊ธฐ ์œ„ํ•œ ์ „๋žต์˜ ๊ธฐ๋ณธ ๊ฐ’์ด ant_path_matcher ์ „๋žต์—์„œ path_pattern_parser ์ „๋žต์œผ๋กœ ๋ณ€๊ฒฝ ๋˜์—ˆ๋‹ค.
    • ๊ทธ๋ž˜์„œ application.properties์—์„œ spring.mvc.pathmatch.matching-strategy=ant_path_matcher๋กœ default ๊ฐ’์„ ๋ณ€๊ฒฝํ•œ๋‹ค.
#Failed to start bean 'documentationPluginsBootstrapper'; error
spring.mvc.pathmatch.matching-strategy = ANT_PATH_MATCHER

SwaggerConfiguration

package com.ssafy.guestbook.config;

import static springfox.documentation.builders.PathSelectors.regex;

import java.util.HashSet;
import java.util.Set;

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

import springfox.documentation.builders.*;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
// swagger ์„ค์ •ํŒŒ์ผ
public class SwaggerConfiguration {

//	Swagger-UI 2.x ํ™•์ธ
//	http://localhost:8080/{your-app-root}/swagger-ui.html
//	Swagger-UI 3.x ํ™•์ธ
//	http://localhost:8080/{your-app-root}/swagger-ui/index.html

	private String version = "V1";
	private String title = "SSAFY GuestBook API " + version;
	
	@Bean
	public Docket api() {
		return new Docket(DocumentationType.SWAGGER_2).consumes(getConsumeContentTypes()).produces(getProduceContentTypes())
					.apiInfo(apiInfo()).groupName(version).select()
					// RestApi๊ฐ€ ์–ด๋””์— ์žˆ๋Š”์ง€ ์„ค์ •
					.apis(RequestHandlerSelectors.basePackage("com.ssafy.guestbook.controller"))
                    // ์œ„์น˜ ์„ค์ • ( Controller ์œ„์น˜ )
					.paths(regex("/admin/.*")).build()
					.useDefaultResponseMessages(false);
	}
	
	private Set<String> getConsumeContentTypes() {
        Set<String> consumes = new HashSet<>();
        consumes.add("application/json;charset=UTF-8");
//      consumes.add("application/xml;charset=UTF-8");
        consumes.add("application/x-www-form-urlencoded");
        return consumes;
    }

    private Set<String> getProduceContentTypes() {
        Set<String> produces = new HashSet<>();
        produces.add("application/json;charset=UTF-8");
        return produces;
    }

    // ์‹ค์งˆ์ ์œผ๋กœ swagger๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์—ญํ• 
	private ApiInfo apiInfo() {
		return new ApiInfoBuilder().title(title)
				.description("<h3>SSAFY API Reference for Developers</h3>Swagger๋ฅผ ์ด์šฉํ•œ GuestBook API<br><img src=\"/img/ssafy_logo.png\" width=\"150\">") 
				.contact(new Contact("SSAFY", "https://edu.ssafy.com", "ssafy@ssafy.com"))
				.license("SSAFY License")
				.licenseUrl("https://www.ssafy.com/ksp/jsp/swp/etc/swpPrivacy.jsp")
				.version("1.0").build();
	}

}

Controller, DTO

  • Annotation ์„ค๋ช…
    • @ApiOperation
      • ํ•ด๋‹น Controller ์•ˆ์˜ method์˜ ์„ค๋ช…์„ ์ถ”๊ฐ€
      • values : ๊ฐ’
      • notes : ๋‚ด์šฉ
    • @ApilmplicitParmam
      • ํ•ด๋‹น API Method ํ˜ธ์ถœ์— ํ•„์š”ํ•œ Parameter๋“ค์˜ ์„ค๋ช…์„ ์ถ”๊ฐ€
    • @ApiResponse
      • ํ•ด๋‹น method์˜ Response์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ž‘์„ฑ
      • ๋ณต์ˆ˜๋กœ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ์œผ๋ฉด @ApiResponses ์‚ฌ์šฉ, ๋ฐฐ์—ด
    • @Apiparam
      • DTO Field ์„ค๋ช…
    • @ApiModel
      • Model(DTO)์— ๋Œ€ํ•œ ์„ค๋ช…


package com.ssafy.word.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.ssafy.word.model.WordDto;
import com.ssafy.word.model.service.WordService;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;


@RestController
@CrossOrigin("*")
@Api("์–ด๋“œ๋ฏผ ์ปจํŠธ๋กค๋Ÿฌ V1")
public class WordCloudController {

	private static final Logger logger = LoggerFactory.getLogger(WordCloudController.class);
	
	@Autowired
	private WordService wordService;
	
	@ApiOperation(value = "๋ฆฌ์ŠคํŠธ๋ชฉ๋ก", notes = "ํšŒ์›์˜ <big>์ „์ฒด ๋ชฉ๋ก</big>์„ ๋ฆฌํ„ด.")
	@ApiResponses({
		@ApiResponse(code=404, message="์ฃผ์†Œ ์š”๋ฅ˜ !!"),
		@ApiResponse(code=500, message="์„œ๋ฒ„ ์—๋Ÿฌ!!"),
		@ApiResponse(code=200, message="ํšŒ์› ๋ชฉ๋ก ์ •์ƒ ์ฒ˜๋ฆฌ!!")
	})
	@GetMapping("/word")
	public ResponseEntity<List<WordDto>> listWord() {
		logger.debug("listWord - ํ˜ธ์ถœ");
		return new ResponseEntity<>(wordService.listWord(), HttpStatus.OK);
	}
	
	
	@ApiOperation(value = "๊ด€์‹ฌ์‚ฌ๋“ฑ๋ก", notes = "๊ด€์‹ฌ์‚ฌ ์ž…๋ ฅ")
	@PostMapping("/word")
	public ResponseEntity<List<WordDto>> registWord(@RequestParam(value = "concerns[]") List<String> concerns) {
		logger.debug("registWord - ํ˜ธ์ถœ");
		wordService.registWord(concerns);
		return new ResponseEntity<>(wordService.listWord(), HttpStatus.OK);
	}
	
	@ApiOperation(value = "๊ด€์‹ฌ์‚ฌ ์„ ํƒ", notes = "๊ด€์‹ฌ์‚ฌ์„ ํƒ ํ›„ ์ ์ˆ˜๋ฅผ ์˜ฌ๋ฆผ")
	@PostMapping("/word/{word}")
	public ResponseEntity<List<WordDto>> updateWordCount(@PathVariable("word") String word) {
		logger.debug("updateWordCount - ํ˜ธ์ถœ");
		wordService.updateCount(word);
		return new ResponseEntity<>(wordService.listWord(), HttpStatus.OK);
	}
}


package com.ssafy.word.model;

import io.swagger.annotations.ApiModel;

@ApiModel(value = "WordDto (๊ด€์‹ฌ์‚ฌ ์ •๋ณด)", description = "๊ด€์‹ฌ์‚ฌ ๋‹จ์–ด, ๋น„์ค‘ ์ •๋ณด๋ฅผ ๊ฐ€์ง„ ํด๋ž˜์Šค")
public class WordDto {

	private String text;
	private double weight;

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}
}

SWAGGER ์ถœ๋ ฅ

์ถœ๋ ฅ๋ฐฉ๋ฒ•




  • ์š”์ฒญ๋œ URL ๊ณผ ์‘๋‹ต๋œ body๊ฐ€ ๋‚˜์˜จ๋‹ค.
  • ์ด๊ฒƒ์„ ๋ณด๊ณ  ์ด ๋ฉ”์„œ๋“œ ์‹คํ–‰์‹œ ์–ด๋–ค ๊ฐ’์ด ์ถœ๋ ฅ๋˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋‹ค.



  • method ์‹คํ–‰ ( POST )
    • try it out -> ๊ฐ’ ์ž…๋ ฅ -> execute
    • required๋ผ๊ณ  ๋˜์–ด์žˆ๋Š” ๋ถ€๋ถ„์€ ๋ฐ˜๋“œ์‹œ ์ž…๋ ฅํ•ด์•ผ ํ•˜๋Š” ๊ฐ’์ด๋‹ค.



  • โ€˜JSโ€™ ๊ฐ€ ๋“ค์–ด๊ฐ„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
  • ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ–ˆ๋‹ค๋Š” ์˜๋ฏธ
  • ์ด๊ฒƒ์„ ๋ณด๊ณ  ์–ด๋–ค ๊ฐ’์„ ๋„ฃ์œผ๋ฉด ์–ด๋–ป๊ฒŒ ์ถœ๋ ฅ๋˜๊ณ  ์ €์žฅ๋˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋‹ค.



  • model
    • Dto์—์„œ @ApiModel์—์„œ ์„ค์ •ํ•ด์ค€ ๊ฒƒ๊ณผ ๊ฐ™์ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์ด๊ฒƒ์„ ํ†ตํ•ด Dto ๊ตฌ์กฐ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.