본문 바로가기
Project/ThreeMovie(영화리뷰및예약도우미)

1. MovieThree 제작기 [Nas / Docker / Jenkins / Nginx / React / Typescript / Spring Boot / Kotlin]

by HDobby 2023. 6. 19.

MovieThree

 

MovieThree

 

moviethree.synology.me

#주의 절대로 최신 버전으로 코딩을 하지 맙시다. 레포가 굉장히 적으며 수 많은 버그에 힘듭니다...

목차

     

     


    사용된 스택

    Nas : DS220+

    OS: DSM 7.1.1

    Docker

    nginx:1.9.15 - alpine 

    node: 16.20.0

    jenkins:jdk17

    redis

    mariaDB 10

     

    Spring-Boot: 3.0.1

    jdk: 17

    Kotlin: 1.7.22

    Jsoup:1.15.4

    querydsl:5.0.0:jakerta

    coroutine:1.7.1

     

    Npm: 16.20.0

    React

    Typescript

     


    대략적인 구조와 설명

    Nginx 의 통신 과정

    moviethree.synology.me로 접속

    1. http로 접속시 nginx가 https로 리다이렉트 시킵니다.
    2. https로 접속 했다면 nginx가 location을 통해 /api 요청인지 프론트 요청인지를 판별합니다.
    3. location /api 라면 localhost:8080으로 proxy pass를 해줍니다.
    4. location /라면 localhost:3000으로 proxyt pass를 해줍니다.

    jenkins의 동작 과정

    Git 업데이트

    1. GitHub main push 시 Git Action으로 GitLab에 push를 동일하게 날립니다.
    2. GitLab main에 push 가 들어올 경우 Jenkins에 push가 왔다는 알림을 보냅니다.
    3. Jenkins 에서 spring build와 node build를 실행 합니다.
    4. build된 백 엔드 서버는 docker file을 읽어 docker로 실행합니다.
    5. 프론트 서버의 경우 build가 되면 nodemon이 걸려 있는 폴더로 이동합니다.
    6. nodemon이 프론트의 변경점을 확인하고 반영 해줍니다.

    nas와 도커 네트워크

    DB

    1. Jwt관리 용 Redis
    2. 일반적인 정보 저장 용 MariaDB

    Frond End 설정

     

    package.json

    {
      "name": "threemovieweb",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "@apollo/react-hooks": "^4.0.0",
        "@emotion/react": "^11.10.6",
        "@emotion/styled": "^11.10.6",
        "@mui/icons-material": "^5.11.11",
        "@mui/lab": "^5.0.0-alpha.124",
        "@mui/material": "^5.11.14",
        "@mui/x-date-pickers": "^6.2.0",
        "@testing-library/jest-dom": "^5.16.5",
        "@testing-library/react": "^13.4.0",
        "@testing-library/user-event": "^13.5.0",
        "@types/jest": "^27.5.2",
        "@types/node": "^16.18.11",
        "@types/qs": "^6.9.7",
        "@types/react": "^18.0.26",
        "@types/react-dom": "^18.0.10",
        "@types/react-router-dom": "^5.3.3",
        "apollo-boost": "^0.4.9",
        "axios": "^1.3.4",
        "dayjs": "^1.11.7",
        "eslint-plugin-unused-imports": "^2.0.0",
        "graphql": "^16.6.0",
        "prettier": "^2.8.2",
        "qs": "^6.11.1",
        "react": "^18.2.0",
        "react-cookie": "^4.1.1",
        "react-dom": "^18.2.0",
        "react-router-dom": "^6.6.2",
        "react-scripts": "5.0.1",
        "recoil": "^0.7.7",
        "sass": "^1.62.0",
        "styled-components": "^5.3.9",
        "styled-reset": "^4.4.5",
        "swiper": "^9.1.1",
        "typescript": "^4.9.4",
        "v6": "^0.0.0",
        "web-vitals": "^2.1.4",
        "yarn": "^1.22.19"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
      },
      "eslintConfig": {
        "extends": [
          "react-app",
          "react-app/jest"
        ]
      },
      "browserslist": {
        "production": [
          ">0.2%",
          "not dead",
          "not op_mini all"
        ],
        "development": [
          "last 1 chrome version",
          "last 1 firefox version",
          "last 1 safari version"
        ]
      },
      "devDependencies": {
        "@types/styled-components": "^5.1.26",
        "@typescript-eslint/eslint-plugin": "^5.48.0",
        "@typescript-eslint/parser": "^5.48.0",
        "eslint": "8.22.0",
        "eslint-config-airbnb": "^19.0.4",
        "eslint-config-prettier": "^8.6.0",
        "eslint-plugin-import": "^2.26.0",
        "eslint-plugin-jsx-a11y": "^6.6.1",
        "eslint-plugin-prettier": "^4.2.1",
        "eslint-plugin-react": "^7.31.11",
        "eslint-plugin-react-hooks": "^4.6.0"
      },
      "compilerOptions": {
        "typeRoots": [
          "node_modules/@types",
          "src/types"
        ]
      }
    }

    eslintrc

    module.exports = {
        parser: '@typescript-eslint/parser',
        plugins: ['@typescript-eslint', 'prettier', 'import', 'unused-imports'],
        extends: [
            'airbnb',
            'plugin:import/errors',
            'plugin:import/warnings',
            'plugin:prettier/recommended',
            'plugin:@typescript-eslint/recommended',
        ],
        rules: {
            camelcase: 'off',
            'linebreak-style': 0,
            'import/prefer-default-export': 0,
            'prettier/prettier': 0,
            'import/extensions': 0,
            'no-use-before-define': 0,
            'import/no-unresolved': 0,
            'import/no-extraneous-dependencies': 0,
            'no-shadow': 0,
            'react/prop-types': 0,
            'react/jsx-filename-extension': [2, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
            'jsx-a11y/no-noninteractive-element-interactions': 0,
            'react/function-component-definition': [2, { namedComponents: 'arrow-function' }],
            'import/order': [
                'error',
                {
                    groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
                    pathGroups: [
                        {
                            pattern: 'react',
                            group: 'builtin',
                            position: 'before',
                        },
                    ],
                    pathGroupsExcludedImportTypes: ['react'],
                },
            ],
            'no-unused-vars': 'off',
            'unused-imports/no-unused-imports': 'error',
            'unused-imports/no-unused-vars': [
                'warn',
                { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' },
            ],
        },
    };

    prettierrc

    {
      "singleQuote": true,
      "semi": true,
      "useTabs": false,
      "tabWidth": 4,
      "trailingComma": "all",
      "printWidth": 120,
      "arrowParens": "always"
    }

     


     

    Back End 설정

     

    build.gradle.kts

    import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
    
    plugins {
    	id("org.springframework.boot") version "3.0.1"
    	id("io.spring.dependency-management") version "1.1.0"
    	id("org.jetbrains.kotlin.plugin.noarg") version "1.7.22"
    	kotlin("jvm") version "1.7.22"
    	kotlin("plugin.spring") version "1.7.22"
    	kotlin("plugin.jpa") version "1.7.22"
    	kotlin("kapt") version "1.7.22"
    }
    
    noArg {
    	annotation("jakarta.persistence.Entity")
    }
    
    allOpen {
    	annotation("jakarta.persistence.Entity")
    	annotation("jakarta.persistence.MappedSuperclass")
    	annotation("jakarta.persistence.Embeddable")
    }
    
    group = "com.threemovie"
    version = "1.0.0"
    java.sourceCompatibility = JavaVersion.VERSION_17
    
    repositories {
    	mavenCentral()
    }
    
    dependencies {
    	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    	implementation("org.springframework.boot:spring-boot-starter-mustache")
    	implementation("org.springframework.boot:spring-boot-starter-graphql")
    	implementation("org.springframework.boot:spring-boot-starter-web")
    	implementation("org.springframework.boot:spring-boot-starter-security")
    	implementation("org.springframework.boot:spring-boot-starter-mail")
    	implementation("org.springframework.boot:spring-boot-starter-validation")
    	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    	implementation("org.jetbrains.kotlin:kotlin-reflect")
    	implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    	developmentOnly("org.springframework.boot:spring-boot-devtools")
    	runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
    	testImplementation("org.springframework.boot:spring-boot-starter-test")
    	implementation("org.jsoup:jsoup:1.15.4")
    	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    	implementation("org.springframework.boot:spring-boot-starter-webflux")
    	implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
    	implementation("org.json:json:20230227")
    	implementation("org.ehcache:ehcache:3.10.8")
    	implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4")
    	implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")
    	implementation("io.jsonwebtoken:jjwt-api:0.11.5")
    	implementation("com.github.f4b6a3:ulid-creator:5.2.0")
    	implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
    	runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
    	runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
    	
    	kapt("com.querydsl:querydsl-apt:5.0.0:jakarta")
    }
    
    tasks.withType<Jar> {
    	duplicatesStrategy = DuplicatesStrategy.INCLUDE
    }
    
    tasks.withType<KotlinCompile> {
    	kotlinOptions {
    		freeCompilerArgs = listOf("-Xjsr305=strict")
    		jvmTarget = "17"
    	}
    }
    
    tasks.withType<Test> {
    	useJUnitPlatform()
    }

     

    application.properties (메인 properties와 graphql 설정)

    spring.profiles.include=smtp-config, db-config, jwt-config, security-config
    spring.graphql.path=/api/graphql
    spring.graphql.graphiql.path=/api/graphiql
    spring.graphql.graphiql.enabled=true
    spring.graphql.schema.locations=classpath:graphql/**/
    spring.graphql.schema.file-extensions=.graphqls,.gqls
    spring.graphql.schema.introspection.enabled=true
    spring.graphql.schema.printer.enabled=true
    spring.graphql.websocket.connection-init-timeout=60s

    application-db-config.properties (db 아이디와 접속 설정)

    spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
    spring.datasource.hikari.data-source-properties.rewriteBatchedStatements=true
    spring.datasource.hikari.data-source-properties.useConfigs=maxPerformance
    spring.jpa.properties.hibernate.jdbc.batch_size=300
    spring.datasource.hikari.validationTimeout=300000
    spring.datasource.hikari.connection-timeout=58000
    spring.datasource.hikari.max-lifetime=580000
    spring.jpa.properties.hibernate.order_updates=true
    spring.jpa.properties.hibernate.order_inserts=true
    spring.datasource.url=jdbc:mariadb://호스트:포트/DB명?serverTimezone=Asia/Seoul&useUnicode=true&characterEncoding=utf8mb4
    spring.datasource.username=아이디
    spring.datasource.password=비밀번호
    spring.data.redis.host=호스트
    spring.data.redis.port=포트
    spring.data.redis.password=비밀번호

    application-jwt-config.properties (jwt token 발급 관련 설정)

    jwt.secret.key=비밀키
    jwt.secret.refreshtoken-validity-in-seconds=50400
    jwt.secret.access-token-validity-in-seconds=7200

    application-security-config.properties (spring-security 설정)

    spring.security.user.name=이름
    spring.security.user.password=비밀번호

    application-smtp-config.properties (메일 보내기 관련 설정 - 네이버)

    spring.mail.host=smtp.naver.com
    spring.mail.default-encoding=UTF-8
    spring.mail.port=465
    spring.mail.properties.mail.smtp.auth=true
    spring.mail.properties.mail.smtp.starttls.enable=true
    spring.mail.properties.mail.smtp.ssl.enable=true
    spring.mail.properties.mail.smtp.ssl.trust=smtp.naver.com
    spring.mail.username=아이디
    spring.mail.password=비밀번호


     

     

     

    정리

    처음으로 만들어 본 제대로 된 웹 서버 겸 프로젝트 이며 부족함이 많습니다.

    코드도 지저분 할 수 있으며 절대로 최신 버전의 스프링 부트와 코틀린으로 코딩을 추천드리지 않습니다..

    일주일에 시간이 날 때마다 작성할 예정입니다.

     

    피드백을 말씀 해주시면 감사하겠습니다!

     

    https://github.com/Hangeulkim/ThreeMovie

     

    GitHub - Hangeulkim/ThreeMovie

    Contribute to Hangeulkim/ThreeMovie development by creating an account on GitHub.

    github.com

     

    728x90

    댓글