๐Ÿ”ฅํ”„๋กœ์ ํŠธ๐Ÿ”ฅ

ํ•œ์ด์Œ ICT ๊ณต๋ชจ์ „ ๊ฐœ๋ฐœ ์ผ์ง€ ( 5/30 )

jmboy 2024. 5. 30. 17:56

๋ฐฐ์šด๊ฒƒ

Git

  • Cherry-Pick

๐Ÿ’ก cherry-pick์„ ์•Œ๊ฒŒ๋œ ๋ฐฐ๊ฒฝ

  • ์˜ค๋Š˜ ์ž‘์—…์„ ํ•˜๋‹ค๊ฐ€ jira์˜ ํ‹ฐ์ผ“ ๋‹จ์œ„๋กœ ๋ธŒ๋žœ์น˜๋ฅผ ์ƒ์„ฑํ•ด ๊ฐœ๋ฐœ์„ ํ•˜๋ ค๋‹ค ๋ณด๋‹ˆ feat-99 ์—์„œ ๊ฐœ๋ฐœํ•œ ์ฝ”๋“œ๊ฐ€ feat-103์— ํ•„์š”ํ•˜๊ฒŒ ๋œ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ feat-99๋ฅผ Pull Request๋ฅผ ํ•˜๊ณ  ๋‚˜๋‹ˆ ํŒ€์›๋“ค์ด pr์„ ๋ฐ›์•„์ฃผ๊ณ  ๋‚ด๊ฐ€ upstream ์—์„œ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๊นŒ์ง€ ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๊ณ  ์žˆ์—ˆ๋‹ค! ํŒ€์›์ด ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ๋”ฐ๋กœ ์ž‘์„ฑํ•˜๋ผ ํ•˜์˜€์œผ๋‚˜ cherry-pick ์— ๋Œ€ํ•ด์„œ ์•Œ๊ฒŒ ๋˜์–ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
  • feat-99 ์—์„œ ๊ฐœ๋ฐœํ•œ ์ปค๋ฐ‹ ๊ธฐ๋ก์„ feat-103 ๋ธŒ๋žœ์น˜๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์—ˆ๋‹ค.
  • ์ด๋Ÿฌํ•œ ๊ณผ์ • ๋•์— ๊ฐœ๋ฐœ์„ ์ˆ˜์›”ํžˆ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.
    • ๋ฌผ๋ก  ๋ฆฌ๋ทฐ ๊ณผ์ •์—์„œ ํ•ด๋‹น ๋กœ์ง์ด ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค..!
  • ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์ปค๋ฐ‹๋“ค๋งŒ ๊ฐ€์ ธ์™€์„œ ์“ธ ์ˆ˜ ์žˆ๋Š”๊ฒƒ์œผ๋กœ๋„ ์•Œ๊ณ  ์žˆ๋‹ค.
  • ๋‚ด๊ฐ€ pr ์˜ฌ๋ฆฐ ์ฝ”๋“œ๊ฐ€ ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค๋ฉด cherry-pick๊ณผ rebase๋ฅผ ํ†ตํ•ด ๊น”๋”ํ•˜๊ฒŒ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Docker

  • SpringBoot ์ด๋ฏธ์ง€ํ™” ํ•˜๊ธฐ
  • SpringBoot๋ฅผ dockerfile ์„ ํ†ตํ•ด ์ด๋ฏธ์ง€ํ™” ํ•˜์˜€๋‹ค.
  1. springBoot ํ”„๋กœ์ ํŠธ๋ฅผ ./gradlew clean build ๋ฅผ ํ†ตํ•ด jar ํŒŒ์ผ๋กœ ๋งŒ๋“ค์–ด ์ค€๋‹ค.
  2. ์ดํ›„ dockerfile์„ ์ž‘์„ฑํ•œ๋‹ค.
FROM amazoncorretto:17
CMD ["./mvnw", "clean", "package"]
ARG JAR_FILE=target/*.jar
COPY ./.env ./.env
COPY ./build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
  1. ์ด๋•Œ .env ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ณต์‚ฌํ•ด ์ตœ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ์— ๋†“์„ ์ˆ˜ ์žˆ๊ฒŒ ์„ค์ •ํ•œ๋‹ค.
  2. ์ดํ›„ docker build -f Dockerfile -t diarist:0.0.1 . ๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  3. docker run -p 8080:8080 diarist:0.0.1 ๋กœ ์ •์ƒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋‹ค.

Swagger

  • SpringDocs๋ฅผ ํ†ตํ•ด API๋ฌธ์„œํ™”๋ฅผ ๋”ฐ๋กœ ํ•˜์ง€ ์•Š์•„๋„ ํ•  ์ˆ˜ ์žˆ๋‹ค!

  • Swagger์˜ ๋ฒ„์ „์€ 3.x.x๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค. 3.0.0 ๋ฒ„์ „๋ถ€ํ„ฐ API Test๋ฅผ ์œ„ํ•ด ์ ‘์†ํ•ด์•ผ ํ•˜๋Š” url ์ด ๋ฐ”๋€Œ์—ˆ๋‹ค๊ณ  ํ•œ๋‹ค.

    Swagger

  • SpringBoot ์—์„œ swagger ์“ฐ๊ธฐ

  1. ์˜์กด์„ฑ ์ถ”๊ฐ€
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
  1. SwaggerConfig ํด๋ž˜์Šค ์ƒ์„ฑ

     @Configuration
     public class SwaggerConfig {
         @Bean
         public OpenAPI openAPI() {
             String jwt = "JWT";
             SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwt);
             Components components = new Components().addSecuritySchemes(jwt, new SecurityScheme()
                     .name(jwt)
                     .type(SecurityScheme.Type.HTTP)
                     .scheme("bearer")
                     .bearerFormat("JWT")
             );
             return new OpenAPI()
                     .components(new Components())
                     .info(apiInfo())
                     .addSecurityItem(securityRequirement)
                     .components(components);
         }
         private Info apiInfo() {
             return new Info()
                     .title("API Test") // API์˜ ์ œ๋ชฉ
                     .description("Let's practice Swagger UI") // API์— ๋Œ€ํ•œ ์„ค๋ช…
                     .version("1.0.0"); // API์˜ ๋ฒ„์ „
         }
     }
     @Configuration
     public class SwaggerConfig {
         @Bean
         public OpenAPI openAPI() {
             return new OpenAPI()
                     .components(new Components())
                     .info(apiInfo());
         }
    
         private Info apiInfo() {
             return new Info()
                     .title("API Test") // API์˜ ์ œ๋ชฉ
                     .description("Let's practice Swagger UI") // API์— ๋Œ€ํ•œ ์„ค๋ช…
                     .version("1.0.0"); // API์˜ ๋ฒ„์ „
         }
     }
  2. application.yml ํŒŒ์ผ ์„ค์ •

springdoc:
  swagger-ui:
    path: /api-test  # swagger-ui ์ ‘๊ทผ ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ๋ณ„์นญ, ํ•ด๋‹น ์ฃผ์†Œ๋กœ ์ ‘์†ํ•ด๋„ http://localhost:8080/swagger-ui/index.html๋กœ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ๋จ.

    groups-order: DESC # path, query, body, response ์ˆœ์œผ๋กœ ์ถœ๋ ฅ

    tags-sorter: alpha # ํƒœ๊ทธ๋ฅผ ์•ŒํŒŒ๋ฒณ ์ˆœ์œผ๋กœ ์ •๋ ฌ

    operations-sorter: method  # delete - get - patch - post - put ์ˆœ์œผ๋กœ ์ •๋ ฌ, alpha๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ŒํŒŒ๋ฒณ ์ˆœ์œผ๋กœ ์ •๋ ฌ ๊ฐ€๋Šฅ

  paths-to-match:
    - /api/** # swagger-ui์— ํ‘œ์‹œํ•  api์˜ ์—”๋“œํฌ์ธํŠธ ํŒจํ„ด
  1. @anotation ์„ค์ •ํ•˜๊ธฐ
@Operation(summary = "์—…์ฒด ํšŒ์›๊ฐ€์ž…", description = "์—…์ฒด ์ธก์—์„œ ํšŒ์›๊ฐ€์ž… ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” API")
public BaseResponse<CompanySignupRes> signUp(@RequestBody CompanySignupReq request) {

// @Operation์œผ๋กœ api์— ๋Œ€ํ•œ ์„ค๋ช…๊ณผ ์š”์•ฝ์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. 

@ApiResponses(value = {
        @ApiResponse(responseCode = "1000", description = "์š”์ฒญ์— ์„ฑ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค.", content = @Content(mediaType = "application/json")),
        @ApiResponse(responseCode = "2002", description = "์ด๋ฏธ ๊ฐ€์ž…๋œ ๊ณ„์ •์ž…๋‹ˆ๋‹ค.", content = @Content(mediaType = "application/json")),
        @ApiResponse(responseCode = "4000", description = "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.", content = @Content(mediaType = "application/json")),
        @ApiResponse(responseCode = "4011", description = "๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.", content = @Content(mediaType = "application/json"))
})

//@ApiResponses ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ, ์‘๋‹ต ์ฝ”๋“œ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜๋„ ์žˆ๋‹ค.

@Parameters({
        @Parameter(name = "email", description = "์ด๋ฉ”์ผ", example = "chrome123@naver.com"),
        @Parameter(name = "password", description = "6์ž~12์ž ์ด๋‚ด", example = "abcd1234"),
        @Parameter(name = "companyName", description = "์—…์ฒด๋ช…", example = "์ฝ”๋ฆฌ์•„ ์‹œ์Šคํ…œ"),
        @Parameter(name = "companyNumber", description = "์—…์ฒด ๋ฒˆํ˜ธ", example = "112233"),
        @Parameter(name = "companyAddress", description = "์—…์ฒด ์ฃผ์†Œ", example = "์ธ์ฒœ์‹œ ๋ฏธ์ถ”ํ™€๊ตฌ ์šฉํ˜„๋™")
})

//@Parameters ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ, ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

CORS ์„ค์ •ํ•˜๊ธฐ

  • corsConfig ๋ฅผ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.
  • Spring์˜ webMvcConfigurer ๋ฅผ ํ†ตํ•ด cors ์„ค์ •์„ ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
package com.hanium.diarist.common.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig  implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:5173", // ๋ฆฌ์•กํŠธ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
                        "http://10.0.2.2:8081", // ๋ฆฌ์•กํŠธ ๋„ค์ดํ‹ฐ๋ธŒ ์—๋ฎฌ๋ ˆ์ดํ„ฐ (์•ˆ๋“œ๋กœ์ด๋“œ)
                        "http://localhost:8081", // ๋ฆฌ์•กํŠธ ๋„ค์ดํ‹ฐ๋ธŒ ์—๋ฎฌ๋ ˆ์ดํ„ฐ (iOS)
//                        "http://<YOUR_DEVICE_IP>:8081", // ์‹ค์ œ ๋””๋ฐ”์ด์Šค์˜ IP ์ฃผ์†Œ์™€ ํฌํŠธ
                        "http://localhost:8000", // ์žฅ๊ณ  ์„œ๋ฒ„
                        "http://localhost:8080/swagger-ui/index.html"
//                        "http://<YOUR_DJANGO_SERVER_IP>:8000" // ์žฅ๊ณ  ์„œ๋ฒ„ IP ์ฃผ์†Œ
                )
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true);

    }
}

CreatedAt, updatedAt - JPA๋กœ ์“ฐ๊ธฐ

  • createdAt ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” BaseEntity ์™€ updatedAt ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” BaseEntity๋ฅผ ์ƒ์†๋ฐ›์€ BaseEntityWithUpdate ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์˜€๋‹ค.
  • ์ดํ›„ ์Šคํ”„๋ง๋ถ€ํŠธ๊ฐ€ ์‹คํ–‰๋  ๋•Œ JPA๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ํ•„๋“œ๋ฅผ ์ƒ์†๋ฐ›์€ ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜๊ฑฐ๋‚˜ ํ•  ๋•Œ ํ•ด๋‹น ๋กœ์ง์ด ์‹คํ–‰์ด ๋˜์–ด์•ผ ํ•œ๋‹ค

@EnableJpaAuditing ์ฝ”๋“œ๊ฐ€ application ํŒŒ์ผ์— ํ•„์š”ํ•˜๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ฐ ํด๋ž˜์Šค๋Š”

package com.hanium.diarist.common.entity;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createdAt;
}
  • EntityListners ๋ฅผ ํ†ตํ•ด CreatedDate๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.