728x90

hello world

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }

위의 코드의 구조를 보면 아래의 구조와 같습니다.

색상 코드 작성 방법

색상을 바꾸는 코드 코드 색상표 구글에 검색하면 헥스 코드가 나온다.

Dart에서 색상을 입힐 때 0xFF작성하고 헥스 코드 # 뒤에부터 나머지를 작성하면 된다.

backgroundColor: Color(0xFFB22222),//Color(0xFF00FF00),//Color(0xFFFFBC00),//Color(0xFFFF0500), //Color(0xFF0034FF),

Text 위젯

Text위젯에는 항상 String이 따라오므로 다른 데이터 타입이 들어오면 안 된다.

title: Text('Welcome to Flutter'),

아래의 코드를 작성하면 Text 위젯 안에 Integer 타입이 들어가 있어 오류가 난다.

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
       body: Text(01234567)
      ),
    );
  }
}

Flutter의 Widget은 SPA의 컴포넌트 개념과 유사하다고 표현할 수 있습니다.

화면의 틀, 가운데 정렬을 위한 Center, 여백을 위한 Padding, 그리고 텍스트 삽입을 위한 Text 등 모두가 Widget이라고 이해하면 됩니다.

MaterialApp

MaterialApp은 구글 스타일의 디자인 틀을 제공하는 위젯입니다.

MaterialApp을 사용하기 위해서는 아래의 코드를 import해주어야합니다.

import 'package:flutter/material.dart';

CupertinoApp

CupertinoApp 애플 스타일의 디자인 틀을 제공하는 위젯입니다.

CupertinoApp을 사용하기 위해서는 아래의 코드를 import 해주어야 합니다.

import 'package:flutter/cupertino.dart';

Container 위젯

Container 위젯은 화면의 요소들을 담고 있는 하나의 박스로 비유할 수 있습니다.

width, height, color 등의 속성을 이용하여 박스의 크기와 색상을 설정할 수 있고, 내부에 여러 위젯들을 활용하여 내용을 구성하면 됩니다.

child

Material Design에서 body는 오로지 하나의 위젯만을 가집니다. 여러 가지 위젯을 내부에 포함시키기 위해서는, 위젯들의 배열을 내부에 가질 수 있는 타입의 위젯을 body의 child로 설정하는 것이 필요합니다. Flutter에서 위젯의 배열을 child로 가질 수 있는 여러가지 위젯들이 존재합니다. 여기서 사용하는 Row/Column 위젯도 이들 중 하나입니다.

Column은 세로로 배치하는 위젯이고, Row는 가로로 배치하는 위젯입니다.

보통 CrossAxisAlignment와 MainAxisAlignment의 여러 속성들 (ex. start, end, center, spaceEvently, spaceBetween 등)을 활용하여 알맞은 간격을 조정합니다.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext ctxt) {
    return new MaterialApp(
      title: "MySampleApplication",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Hello Flutter App"),
        ),
        body: new Center(
          child: new Row(
            children: <Widget>[
              new Text("Hello Flutter"),
              new Text("Hello Flutter - "),
            ],
          ),
        ), 
      )
    );
  }
}
728x90

'개발 > Flutter' 카테고리의 다른 글

Flutter 다섯 번째 강의 내용  (0) 2022.10.27
Flutter 네 번째 강의 내용  (0) 2022.10.24
Flutter 세 번째 강의 내용  (0) 2022.10.21
Flutter 두 번째 강의 내용  (0) 2022.10.20
Flutter 환경 구성 및 설치  (2) 2022.10.14
728x90

개요

로컬과 서버의 결괏값이 다름

로컬에서와 서버에서 특정 링크(네이버 쇼핑몰)를 테스트했을 때 로컬에서는 문제가 없지만 서버에서는 og tag로 이미지 파일과 title이 null 값으로 동일한 URL인데 저장이 안 되는 문제가 발생했다.

2주 넘게 고민하다 이유를 모르겠어서 매주 주말에 참가하는 부천 모각코 모임에 여쭤봤더니 경력이 높으신 분이 문제를 접근하는 방법과 해결방법에 대해 말씀해주셨다. 이번 문제에 대해 접근하는 방법과 해결 과정 등 많은 것을 배울 수 있는 시간이었다.

로컬과 서버의 차이점

   1. 서버와 로컬에서 차이점은 서버는 앞단에 nginx가 하나 더 있음

   2. 결괏값 URL이 다름

로컬

baseURL = <https://search.shopping.naver.com/book/catalog/32490794178>
postDataSaveReq.getLink() = <https://msearch.shopping.naver.com/book/catalog/32490794178>
postDataSaveReq.getImgUrl() = <https://shopping-phinf.pstatic.net/main_3249079/32490794178.20220527093651.jpg>

서버

baseURL = <https://search.shopping.naver.com/book/catalog/32490794178>
postDataSaveReq.getLink() = <https://search.shopping.naver.com/book/catalog/32490794178>
postDataSaveReq.getImgUrl() = null

   3. 서버와 로컬의 ip 주소가 다르다.

이유

og tag 라이브러리를 보면 baseURL로 한번 더 요청을 보낸다. 아래와 같이 로컬에서는 curl 요청이 문제없이 되지만 ec2 서버에서 네이버 쇼핑몰로 curl request를 보내면 307 Temporary Redirect 상태 코드가 나온다. 307 상태 코드는 요청된 URL이 잠시 다른 URL로 바뀐 것을 알리는 상태 코드이다. (Location: header로 지정된) 바뀐 URL은 GET method로 접근해야 한다. 하지만 서버에서 요청 보낸 헤더 location을 보낸 URL을 보면 https://search.shopping.naver.com/blocked.html로 비정상적 요청이 감지되었습니다.라는 페이지가 나온다.

로컬에서 보낸 curl 요청

curl https://search.shopping.naver.com/book/catalog/32490794178

<!DOCTYPE html>
<html lang="ko">
<head><meta charSet="utf-8"/><meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>서울 시 : 네이버 도서</title><link rel="shortcut icon" href="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/img/favicon.ico"/>
<link rel="apple-touch-icon" href="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/img/favicon_60.png"/>
<meta property="og:title" content="서울 시 : 네이버 도서"/>
<meta property="og:description" content="네이버 도서 상세정보를 제공합니다."/>
<meta property="og:image" content="https://shopping-phinf.pstatic.net/main_3249079/32490794178.20220527093651.jpg"/>
<meta property="og:url" content="https://search.shopping.naver.com/book/catalog/32490794178"/>
<meta name="next-head-count" content="10"/>
<link rel="preconnect" href="https://static.nid.naver.com"/>
<link rel="preconnect" href="https://lcs.naver.com"/>
<link rel="preconnect" href="http://shopping.phinf.naver.net"/>
<link rel="preconnect" href="https://ssl.pstatic.net"/>
<link rel="preconnect" href="https://shopping-phinf.pstatic.net"/>
<link rel="preconnect" href="https://volts.shopping.naver.com"/>
<link rel="preload" href="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/_next/static/css/c1c28aedd09b2c2d.css" as="style"/>
<link rel="stylesheet" href="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/_next/static/css/c1c28aedd09b2c2d.css" data-n-g=""/>
<link rel="preload" href="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/_next/static/css/05d5e4267a2caa1a.css" as="style"/>
<link rel="stylesheet" href="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/_next/static/css/05d5e4267a2caa1a.css" data-n-p=""/>
<link rel="preload" href="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/_next/static/css/bda4bc7d3c476bd4.css" as="style"/>
<link rel="stylesheet" href="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/_next/static/css/bda4bc7d3c476bd4.css"/><noscript data-n-css="">
</noscript><script defer="" nomodule="" src="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/_next/static/chunks/polyfills-5cd94c89d3acac5f.js">
</script><script defer="" src="https://ssl.pstatic.net/shoppingsearch/static/book-catalog/book-catalog-221016-124516/_next/static/chunks/315.75129e479e1f82a7.js">
</script>

서버에서 보낸 curl 요청

curl -i https://search.shopping.naver.com/book/catalog/32490794178

HTTP/2 307 
date: Sun, 16 Oct 2022 08:33:54 GMT
content-type: text/html; charset=utf-8
content-length: 164
location: https://search.shopping.naver.com/blocked.html
referrer-policy: unsafe-url
server: nfront

<html>
<head><title>307 Temporary Redirect</title></head>
<body>
<center><h1>307 Temporary Redirect</h1></center>
<hr><center>nginx</center>
</body>
</html>

https://search.shopping.naver.com/blocked.html 화면

해결 방법

  1. 네이버 쪽에서 접속이 차단되지 않은 ip 대역의 클라우드를 찾는다.
  2. 사용자가 있는 경우 프론트에서 처리를 해주어 request를 보낸다. 서버에서 request를 보내면 ec2 ip대역이라 차단이 되지만 사용자에서 요청을 보내면 각자 기기의 ip가 있어서 차단되지 않을 수 있다.
728x90
728x90

개요

이번에 스나이퍼 팩토리에서 주관하는 Flutter 앱 개발 과정 교육에 참여하게 되어서 Flutter 환경을 제 맥북 M1에 세팅하려고 한다.

Flutter 환경을 구성하려면 아래와 같은 6가지의 설치 과정이 필요하다.

  1. Android Studio
  2. Xcode
  3. Chrome
  4. Flutter SDK
  5. VSCode
  6. VSCode Extension - Flutter

1. Android Studio 설치 과정

https://developer.android.com/studio에 접속한다.

녹색 표시의 다운로드 버튼을 누른다.

 

녹색 버튼을 누르게 되면 위와 같은 창이 나온다. 저는 Mac의 Apple chip M1 이므로 흰 바탕의 버튼을 눌러서 설치를 진행했다.

다운로드한 위치에 android-studio-2021.3.1.16-mac_arm.dmg 파일이 생성되는데 이 파일을 실행시켜준다. 설치 중에 나오는 체크사항은 모두 디폴드로 진행한다.

 

2. Xcode 설치 과정

 

Xcode는 App Store에서 설치를 진행하면 된다.

 

App store에 접속해서 검색창에 xcode를 검색하면 Apple Developer와 Xcode가 나오는데 Xcode만 설치를 진행한다.

 

3. Chrome 설치

Chrome은 다들 설치되어있을 것이라 생각하고 생략하겠습니다.

 

4. Flutter SDK를 설치한다.

https://docs.flutter.dev/get-started/install/macos 이 링크에 접속한다.

링크에 접속하면 위와 같은 화면이 나오는데 Apple Slilcon인 flutter_macos_arm64_3.3.4-stable.zip을 설치한다.

설치가 완료되면 압축을 해제해줍니다.

터미널에 접속해서 Flutter SDK 압축 풀어준 경로로 접속합니다.

 

export PATH="$PATH:`pwd`/flutter/bin"

위의 환경 변수를 추가해줍니다.

 

터미널에서 zshrc 파일을 열고

open ~/.zshrc

 

Path 추가하는 라인을 복붙 해주고 파일을 저장한다. 경로를 잘 확인합시다.

export PATH=$PATH:~/Downloads/flutter/bin

 

그리고 터미널에서 source 명령어를 입력해서 zshrc파일을 재실행한다.

source ~/.zshrc

 

플러터 명령어가 잘 실행되는지 확인하기 위해 아래의 코드를 터미널에 입력해 봅니다.

flutter --version

 

출력 내용

Flutter 3.3.4 • channel stable • <https://github.com/flutter/flutter.git>
Framework • revision eb6d86ee27 (8일 전) • 2022-10-04 22:31:45 -0700
Engine • revision c08d7d5efc
Tools • Dart 2.18.2 • DevTools 2.15.0

 

5. VSCode 설치

https://code.visualstudio.com/ 이 링크에 접속해서 VSCode를 설치합니다.

VSCode를 실행시켜줍니다.

 

6. VSCode Extension - Flutter 설치

VSCode를 실행시킨 화면에서 왼쪽 하단에 있는 사각형 4개 있는 아이콘을 클릭합니다.

 

검색창에 flutter를 검색해서 다운로드하여줍니다.

 

7. Flutter가 잘 설치되어있는지 점검을 해주는 Run Flutter Doctor를 실행한다.

Shift+Command+P 단축키로 Pallete를 실행하고 Flutter: Run Flutter Doctor를 실행한다.

 

만약 SDK 경로를 못 찾는다고 나오면 경로 찾아서 Set 해주면 된다. 경로는 Flutter SDK 압축 해제한 폴더에서 flutter/bin 까지 추가해준다.

output 영역에 실행 결과가 나오는데 2개의 카테고리에 문제가 있다고 나올 것이다 현재 저는 오류를 수정해서 오류 화면이 나오지 않습니다.

 

8. Android SDK command-line Tools를 설치해야 한다.

일단 Android Studio를 실행하고 상단에 Preferences 메뉴를 선택한다.

 

9. 왼쪽의 Android SDK 메뉴의 중간에 있는 SDK Tools 메뉴에서 Android SDK command-line Tools를 체크하고 Apply 하면 된다.

 

10. flutter doctor --android-licenses를 터미널에서 실행한다.

y / n이 나오면 y를 입력해준다.

 

11. 이제 Xcode 이슈를 해결한다.

아래의 명령어를 터미널에서 실행시켜 cocoapods를 설치한다.

sudo gem install cocoapods -n /usr/local/bin

 

pod도 설치한다.

pod setup

 

아래의 코드를 실행시킨다.

sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch

 

12. 최종 확인

다시 flutter doctor를 실행해서 issues가 없음을 확인한다.

 

728x90

'개발 > Flutter' 카테고리의 다른 글

Flutter 다섯 번째 강의 내용  (0) 2022.10.27
Flutter 네 번째 강의 내용  (0) 2022.10.24
Flutter 세 번째 강의 내용  (0) 2022.10.21
Flutter 두 번째 강의 내용  (0) 2022.10.20
Flutter 첫 번째 강의 내용  (0) 2022.10.18
728x90

개요

전에 했던 SkyLife_Transformation 프로젝트를 로컬에서 테스트를 해보고 AWS EC2 서버에 올렸는데 로컬에서는 되던 게 AWS EC2에서는 안되는 것입니다. 스프링은 오류 없이 서버에서 실행되고 매핑이 잘 되어있는데 404 Not Found Error로 페이지를 못 찾는 에러가 발생했다.

문제점

컨트롤러에 연결되는 jsp파일을 찾지 못하는 오류가 발생합니다. jsp로 만든 프로젝트를 jar로 패키징하면 jsp 파일이 패키징이 안되어 나는 오류인 것 같다.

해결 방법

프로젝트가 빌드될때 war로 빌드되게 변경하고 war패키지 안에 jsp 파일이 잘 들어있는지 확인한다.

 

1. build.gradle 파일에 war설정

plugins {
	id 'org.springframework.boot' version '2.7.0'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'org.asciidoctor.convert' version '1.5.8'
	id 'java'
	id 'war' // 추가
}
apply plugin: 'war'  // 추가

 

2. 인텔리제이에서 오른쪽 상단에 있는 gradle 탭에서 bootWar에서 오른쪽 클릭을 하고 애플리케이션 실행한다.

 

3. AWS EC2에서 실행

./gradlew bootwar

nohup java -jar ./build/libs/스냅샷파일.war &

 

4. 서버로 접속해본다. 화면이 잘 나오는 것을 확인할 수 있습니다.

728x90
728x90

개요

GitHub의 프로젝트 레파지토리에 application.yml이나 application.properties 파일에 DB의 유저 정보와 비밀번호 또는 키값을 명시해두는 경우가 있습니다. 또한 yml이나 properties을. gitignore에 추가할 경우 aws에서 프로젝트를 실행하는 경우나 docker로 실행하는 경우 이와 같은 오류가 발생한다. "Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. Reason: Failed to determine a suitable driver class” 위의 오류는 database 설정 파일을 찾지 못해서 나는 오류이다. 하지만 DB 정보를 명시해두는 경우 GitHub 특성상 오픈 소스이므로 정보가 노출되어 보안에 심각한 문제를 초래할 수 있다는 점입니다. 위와 같은 사항들을 해결하기 위해 보안에 민감한 값들을 암호화시켜 저장해야 한다.

 

암호화 방식

YML파일 암호화 방법으로는 여러 방법이 있지만 저는 Java의 Jasypt(Java Simplified Encryption) 패키지를 사용하여 설정 파일들의 암호화를 진행하겠습니다.

 

장점

Jasypt를 설정하려면 여러 코드들이 추가되지만 로컬,배포 환경의 설정 파일을 동일하게 사용할 수 있고 설정 파일이 외부 유출되어도 암호화되어있기에 비교적 안전하다는 장점이 있다.


암호화 진행 순서

1. build.gradle파일에 jasypt implementation을 작성한다.

// yml 설정 파일 암호화
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.3'

 

2. jasyptStringEncryptor로 Bean을 등록합니다. 이 이름은 application.yml의 jasypt bean으로 등록할 때 사용합니다. private String PASSWORD는 yml 설정 파일에서 jasypt.encryptor.password으로 읽어서 가져옵니다.

import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JasyptConfig {

		@Value("${jasypt.encryptor.password}")
    private String PASSWORD;

    @Bean(name = "jasyptStringEncryptor")
    public StringEncryptor stringEncryptor() {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword(PASSWORD); // 암호화할 때 사용하는 키
        config.setAlgorithm("PBEWithMD5AndDES"); // 암호화 알고리즘
        config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수
        config.setPoolSize("1"); // 인스턴스 pool
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스
        config.setStringOutputType("base64"); //인코딩 방식
        encryptor.setConfig(config);
        return encryptor;
    }
}

 

3. application.yml에 암호화된 값을 적어주기 전에 미리 해당 값들의 암호화된 값을 알기위해 Test코드를 작성해서 출력한다.

import org.assertj.core.api.Assertions;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.junit.jupiter.api.Test;

class JasyptConfigTest {

    @Test
    void jasypt(){
        String url = "자신의 DB URL";
        String username = "자신의 DB USER";
        String password = "자신의 DB PASSWORD";

        String encryptUrl = jasyptEncrypt(url);
        String encryptUsername = jasyptEncrypt(username);
        String encryptPassword = jasyptEncrypt(password);

        System.out.println("encryptUrl : " + encryptUrl);
        System.out.println("encryptUsername : " + encryptUsername);
        System.out.println("encryptPassword : " + encryptPassword);

        Assertions.assertThat(url).isEqualTo(jasyptDecryt(encryptUrl));
    }

    private String jasyptEncrypt(String input) {
        String key = "암호화에 쓰일 키값";
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setAlgorithm("PBEWithMD5AndDES");
        encryptor.setPassword(key);
        return encryptor.encrypt(input);
    }

    private String jasyptDecryt(String input){
        String key = "복호화에 쓰일 키값";
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setAlgorithm("PBEWithMD5AndDES");
        encryptor.setPassword(key);
        return encryptor.decrypt(input);
    }
}

 

4. 위의 TestCode를 작성하면 암호화된 값이 출력된다.

 

5. application.yml에 암호화된 값과 jasyptStringEncryptor bean 설정

spring:
  datasource:
    url: ENC(o9FcC1LtWYlFw88yy/5Ilg==)
    username: ENC(cp/1/Ok8sPGkYEJi27Jknw==)
    password: ENC(9vlmkROOsAgMTk3rFkbiQxXFwFaQoew0)
jasypt:
  encryptor:
    bean: jasyptStringEncryptor
    password : 1234

 

jasyptStringEncryptor를 jasypt bean으로 등록하고, 각 속성값에 ENC( 암호화 값 ) 형식으로 입력해줍니다. 위와 같은 과정을 통해 application.yml 파일의 암호화를 설정해 주었습니다. 하지만, application.yml 파일 내 jasypt.encryptor.password가 노출되어있다면, 암호화는 해주었지만 암호화를 풀 수 있는 열쇠를 지어준 꼴이 된다. 이와 같은 문제를 해결하는 방법은 다음에 작성해 보겠습니다.

728x90
728x90

개요

Swagger를 서버에 배포했을 때 기존 코드로 API 명세서를 실행시키면 curl이 curl -x GET “https://127.0.0.1:8081/”로 실행이 되어서 상태 코드 200인데 안에 데이터가 비어있는 오류가 발생했다. 이를 해결하고자 Swgger 코드를 수정했다.

application.yml에 아래와 같은 코드 작성

servers:
  - url: https://server address/
    description: Production server

Swagger version 2.9.2에서 3.0으로 수정

//기존코드
//implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
//implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'

//수정 코드
implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0'

SwaggerConfig파일 수정

import springfox.documentation.service.Server;
import springfox.documentation.spi.DocumentationType;

@Configuration
public class SwaggerConfig {
	@Bean
	public Docket restAPI() {
        Server serverLocal = new Server("local", "http://localhost:8081", "for local usages", Collections.emptyList(), Collections.emptyList());
        Server testServer = new Server("test", "https://서버주소", "for testing", Collections.emptyList(), Collections.emptyList());
        return new Docket(DocumentationType.OAS_30)
                .servers(serverLocal, testServer)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("j2kb.ponicon.scrap")) // 특정 패키지경로를 API문서화 한다. 1차 필터
                .paths(PathSelectors.any()) // apis중에서 특정 path조건 API만 문서화 하는 2차 필터
                .build()
                .groupName("API 1.0.0") // group별 명칭을 주어야 한다.
                .useDefaultResponseMessages(false); // 400,404,500 .. 표기를 ui에서 삭제한다.
	}
	private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot REST API test")
                .version("v0.0.1")
                .description("스크랩 JPA swagger api 입니다.")
                .build();
	}
}

Workaround 파일을 작성한다.

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.stereotype.Component;
import springfox.documentation.oas.web.OpenApiTransformationContext;
import springfox.documentation.oas.web.WebMvcOpenApiTransformationFilter;
import springfox.documentation.spi.DocumentationType;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Component
public class Workaround implements WebMvcOpenApiTransformationFilter {

    @Override
    public OpenAPI transform(OpenApiTransformationContext<HttpServletRequest> context) {
        OpenAPI openApi = context.getSpecification();
        Server localServer = new Server();
        localServer.setDescription("local");
        localServer.setUrl("http://localhost:8081");

        Server testServer = new Server();
        testServer.setDescription("test");
        testServer.setUrl("https://serveraddress");
        openApi.setServers(Arrays.asList(localServer, testServer));
        return openApi;
    }

    @Override
    public boolean supports(DocumentationType documentationType) {
        return documentationType.equals(DocumentationType.OAS_30);
    }
}

servers에 보면 local과 서버 두 개가 추가된 것을 확인할 수 있다.

아래와 같이 curl이 서버 주소로 잘 실행되고 데이터 값도 잘 나오는 것을 확인할 수 있다.

728x90
728x90

Valid Parentheses 문제

https://leetcode.com/problems/valid-parentheses/

 

Valid Parentheses - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

An input string is valid if:

  1. Open brackets must be closed by the same type of brackets.
  2. Open brackets must be closed in the correct order.
  3. Every close bracket has a corresponding open bracket of the same type.

Example 1:

Input: s = "()"
Output: true

Example 2:

Input: s = "()[]{}"
Output: true

Example 3:

Input: s = "(]"
Output: false

풀이

백준이나 프로그래머스에서 흔히 볼 수 있는 올바른 괄호 찾기 문제와 유사합니다. Map.of를 이용해서 Map을 만들어 주었습니다. Map.of는 둘 이상의 (k, v) 쌍을 호출하게 되면 ImmutableCollections 안에 있는 MapN<K, V> 객체를 만들어 내게 됩니다. Map.of(key1, value1, key2, value2...) 이런 식으로 만든다. String으로 받은 s를 toCharArray()를 이용해서 Character로 변환해주면서 for문을 반복합니다. 만약 chr이 (, {, [ 인 여는 괄호이면 stack.push 해줍니다. 만약 stack이 비어있거나 stack.pop 했을 때 map.get(chr)인 value값과 같지 않으면 false를 return 합니다. for문이 끝나고 stack이 비어있는지 확인해 그 값을 return 한다.

 

java 9 부터 도입된 Map.of 메소드를 알아봅시다.

 java 9 버전 부터 Map.of 메소드가 생겼습니다. 이것을 언제 쓸 법 한지 알아보고, 간단하게 내부를 보도록 하겠습니다.  보시면, unmodifiable map을 리턴하게끔 되어 있습니다. 수정할 수 없는 맵을

codingdog.tistory.com


코드

class Solution {
    public boolean isValid(String s) { 
        boolean answer = true;
        Map<Character, Character> map = Map.of(')', '(', '}', '{', ']', '[');
        Stack<Character> stack = new Stack<>();
        for(Character chr : s.toCharArray()) {
            if(chr == '(' || chr == '{' || chr == '[') {
                stack.push(chr);
            }
            else if(stack.isEmpty() || stack.pop() != map.get(chr)){
                return answer = false;
            }
        }
        answer = stack.isEmpty();
        return answer;
    }
}

Merge Two Sorted Lists 문제

https://leetcode.com/problems/merge-two-sorted-lists/

 

Merge Two Sorted Lists - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

You are given the heads of two sorted linked lists list1 and list2.

Merge the two lists in a one sorted list. The list should be made by splicing together the nodes of the first two lists.

Return the head of the merged linked list.

Example 1:

스크린샷 2022-09-30 오후 7 56 10

Input: list1 = [1,2,4], list2 = [1,3,4]
Output: [1,1,2,3,4,4]

Example 2:

Input: list1 = [], list2 = []
Output: []

Example 3:

Input: list1 = [], list2 = [0]
Output: [0]

Constraints:

  • The number of nodes in both lists is in the range [0, 50].
  • -100 <= Node.val <= 100
  • Both list1 and list2 are sorted in non-decreasing order.

풀이

LeetCode에서 알고리즘 문제를 풀다 보면 ListNode를 이용해 풀어야 하는 문제가 있습니다. ListNode로 list1, list2를 받아서 p1, p2에 각각 넣어줍니다. while을 이용해서 p1과 p2가 null이 아닐 때까지 반복하는데 만약 p1의 값이 p2의 값과 비교했을 때 p2의 값이 크면 p.next에 p1을 대입한다. p1의 값을 p1.next의 값으로 바꾸어주고 p에 p.next 값을 대입한다. 만약 p1의 값이 더 클 경우 p.next에 p2의 값을 대입한다. p2.next 값을 p2에 대입하고 p에 p.next값을 대입한다. while 문이 끝나고도 남은 ListNode가 발생할 수 있어 p1이 null이 아니면 p.next에 p1을 대입하고 p2가 null이 아니면 p.next에 p2를 대입한다.

 

[LeetCode] 자주 사용되는 자료구조 - ListNode 구현

모든 소스 코드는 여기 있습니다. LeetCode 에서 알고리즘 문제를 풀다보면 ListNode 를 이용해 테스트 해야할 일이 많이 있습니다. 테스트 코드나 main 메서드 내에서 객체를 생성하고 ListNode 를 파라

jaime-note.tistory.com


코드

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode head = new ListNode();
        ListNode p = head;

        ListNode p1 = list1;
        ListNode p2 = list2;

        while(p1 != null && p2 != null) {
            if(p1.val < p2.val) {
                p.next = p1;
                p1 = p1.next;
            }
            else {
                p.next = p2;
                p2 = p2.next;
            }
            p = p.next;
        }
        if(p1 != null) {
            p.next = p1;
        }
        if(p2 != null) {
            p.next = p2;
        }
        return head.next;
    }
}
728x90

'알고리즘 > 알고리즘 문제풀이' 카테고리의 다른 글

프로그래머스 최솟값 만들기, JadenCase 문자열 만들기  (0) 2022.09.23
백준) DP, DFS  (0) 2022.08.27
백준) HashMap  (0) 2022.07.23
백준) BFS, 다익스트라  (0) 2022.07.19
백준) 우선순위 큐, DP  (0) 2022.07.18
728x90

개요

Postman Team 사용이 4명 이상일 경우 해당 기간 동안만 무료로 사용할 수 있어서 방안을 찾던 중 방안 중 하나인 API 명세서 만들어주는 Swagger를 설정 방법과 사용법에 대해 작성하려고 한다.

Swagger란?

Swagger를 사용하면 @어노테이션 코드 몇 줄 추가하여 간단하게 API별로 문서화 및 API테스트 가능한 UI 까지 제공하여 문서 작성 시간을 극도로 단축하여 개발에 집중할 수 있다.

Swagger 버전

Swagger는 버전별로 차이가 있다.

Swagger 3.x.x 이후 부터 접속 url 변경 등 2.x.x 와 3.x.x 는 차이점이 있으며 해당 프로젝트는 Swagger 2.9.2 사용한다.

Swagger 설정

  • Gradle에 Swagger 의존성을 추가해준다.
//swagger
	implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
	implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
  • Swagger 버전 호환 이슈 해결
    1. org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
    2. Caused by: java.lang.NullPointerException: null

Swagger 버전 호환 이슈로 위와 같은 오류가 발생했다.

해결법

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

application.yml이나 application.propertise에 위와 같은 코드를 작성한다.

  • Swagger config class 생성
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket restAPI() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("j2kb.ponicon.scrap")) // 특정 패키지경로를 API문서화 한다.
                .paths(PathSelectors.any()) // apis중에서 특정 path조건 API만 문서화 하는 2차 필터
                .build()
                .groupName("API 1.0.0") // group별 명칭을 주어야 한다.
                .useDefaultResponseMessages(false); // 400,404,500 .. 표기를 ui에서 삭제한다.
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot REST API test")
                .version("v0.0.1")
                .description("스크랩 JPA swagger api 입니다.")
                .build();
    }
}

useDefaultResponseMessages(false)를 사용하면 아래와 같은 화면이 출력되지 않는다.

적용 전

적용 후

private ApiInfo apiInfo()는 아래와 같은 Swagger 화면을 작성하는 메서드이다.

  • Swagger-ui 실행

현재 우리는 2.x.x 버전을 사용하므로 http://localhost:8081/swagger-ui.html

  • API에 Swagger @Annotation 추가
@Api(tags = "카테고리와 관련된 API") -> class
@ApiOperation(value = "카테고리 조회 API", notes = "UserId를 RequestParam으로 받아서 categoryService.categories 후 카테고리를 조회하는 역할을 합니다. /category/all?id=") -> Method
@ApiParam(value = "User의 id 값", example = "2") -> parameter
  • @Api tags : 해당하는 controller.java 에 대한 Title명칭 부여
  • @ApiOperation value : API에 대한 요약하여 작성 notes : API에 대한 자세한 설명 작성
  • @ApiParam value= 파라미터에 대한 설명 descriptionexample = 파라미터의 default 값이 있을 때 사용 required = true : 파라미터의 필수인지 표기 필수는 true, 아니면 false

위의 @Api 어노테이션을 통해 작성한 Swagger 화면

위의 @ApiOperation 어노테이션을 통해 작성한 Swagger 화면

위의 @ApiParam 어노테이션을 통해 작성한 Swagger 화면

Swagger 사용법

  • 위의 사진의 오른쪽 상단에 보면 Try it out이 있다. Try it out을 클릭한다.

  • 만약 Default값으로 값이 들어가 있다면 바로 Execute버튼을 클릭하면 실행이 된다. Default값 외의 다른 값으로 Test해보고 싶을 때는 다른 값을 넣어준다.

  • 위의 파라미터 값을 넣어서 실행한 화면이다.

단점

Swagger 코드가 들어가서 코드가 길어보이고 지저분해 보인다.

 

참고 블로그

https://velog.io/@dkatlf900/swagger

 

Swagger API DOC 구축부터 실행까지

Spring Boot REST API, Swagger 구축.

velog.io

 

728x90

+ Recent posts