반응형

https://www.acmicpc.net/problem/1103

 

1103번: 게임

줄에 보드의 세로 크기 N과 가로 크기 M이 주어진다. 이 값은 모두 50보다 작거나 같은 자연수이다. 둘째 줄부터 N개의 줄에 보드의 상태가 주어진다. 쓰여 있는 숫자는 1부터 9까지의 자연수 또는

www.acmicpc.net

dfs + dp 문제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <algorithm>
#define rep(i,n) for(int i=0;i<n;i++)
using namespace std;
int n, m, arr[51][51], ans;
int dx[] = { -1010 };
int dy[] = { 010-1 };
int visited[51][51];
void input();
void dfs(int x, int y, int cnt) {
    // 무한번 이동가능
    if (cnt > n*m) {
        cout << -1;
        exit(0);
    }
 
    // dp 체크
    if (cnt <= visited[x][y]) return;
 
    // 방문 처리
    ans = max(ans, cnt);
    visited[x][y] = cnt;
    rep(i, 4) {
        int nx = x + dx[i]*arr[x][y];
        int ny = y + dy[i]*arr[x][y];
        if (nx < 0 || nx >= n || ny < 0 || ny >= m || arr[nx][ny] == -1continue;
        dfs(nx, ny, cnt+1);
    }
}
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    input();
    dfs(001);
    cout << ans;
}
 
void input() {
    cin >> n >> m;
    rep(i, n) {
        rep(j, m) {
            char c;
            cin >> c;
            if (c == 'H') arr[i][j] = -1;
            else arr[i][j] = c - '0';
        }
    }
}
cs
반응형

'백준 > DP' 카테고리의 다른 글

백준 1890  (0) 2021.07.23
백준 2293  (0) 2021.07.20
백준 11054  (0) 2021.02.19
백준 13398 [복습 필수]  (0) 2021.02.18
백준 2133 [복습 필수] (점화식)  (0) 2021.02.17
반응형

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

타임리프 스프링 통합으로 추가되는 기능들

  • 스프링의 SpringEL 문법 통합
  • ${@myBean.doSomething()} 처럼 스프링 빈 호출 지원
  • 편리한 폼 관리를 위한 추가 속성

        - th:object (기능 강화, 폼 커맨드 객체 선택)

        - th:field , th:errors , th:errorclass

  • 폼 컴포넌트 기능

        - checkbox, radio button, List 등을 편리하게 사용할 수 있는 기능 지원

  • 스프링의 메시지, 국제화 기능의 편리한 통합
  • 스프링의 검증, 오류 처리 통합
  • 스프링의 변환 서비스 통합(ConversionService)

입력 폼 처리

Controller에서 빈 item 객체를 생성해서 넘겨줌

th:field="${item.~~~~}" 사용하면 id = "~~~~", name = "~~~~", value = "~~~~" 자동으로 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<form action="item.html" th:object="${item}" th:action method="post">
    <div>
        <label for="id">상품 ID</label>
        <input type="text" id="id" name="id" class="form-control" value="1" th:value="${item.id}" readonly>
    </div>
    <div>
        <label for="itemName">상품명</label>
        <input type="text" id="itemName" name="itemName" class="form-control" value="상품A" th:value="${item.itemName}">
    </div>
    <div>
        <label for="price">가격</label>
        <input type="text" id="price" name="price" class="form-control" value="10000" th:value="${item.price}">
    </div>
    <div>
        <label for="quantity">수량</label>
        <input type="text" id="quantity" name="quantity" class="form-control" value="10" th:value="${item.quantity}">
    </div>
....
</form>
cs

form에서 th:object="${item}" 사용

th:field="*{itemName}" -> item.itemName

th:field="*{price}" -> item.price

th:field="*{quantity}" -> item.quantity

 

체크 박스 - 단일1

check 하면 on 넘어옴 -> true로 변환

check 안하면 check가 아예 사라짐-> null로 넘어감

 

수정 시 문제 발생 가능

체크 했다가 해제 -> null 넘어감 -> 아무 작업도 안 한 것으로 판단할 수 있음 -> 값 수정 안됨

 

Spring에서 체크 해제를 인식하기 위한 히든 필드 기능

<input type="checkbox" id="open" name="open" class="form-check-input">

<input type="hidden" name="_open" value="on"/>

 

실행 로그

item.open=true // 선택 O

item.open=false // 선택 X

※ 단점: 일일히 추가해야 함

 

체크 박스 - 단일2

th:field="*{open}": 히든 필드 생략 가능 (자동 생성됨)

<input type="checkbox" th:field="*{open}" class="form-check-input">

 

타임리프의 체크 확인

checked="checked"

체크 박스에서 판매 여부를 선택해서 저장하면, 조회시에 checked 속성이 추가된 것을 확인할 수 있다.

이런 부분을 개발자가 직접 처리하려면 상당히 번거롭다. 타임리프의 th:field 를 사용하면, 값이 true

인 경우 체크를 자동으로 처리해준다.

 

체크박스 - 등록, 수정

등록, 수정은 form에서 처리. form 태그에서 th:object="${item}" 추가했으므로 *{price}, *{quantity} 등 사용 가능

1
2
3
4
5
6
7
<div>판매 여부</div>
<div>
    <div class="form-check">
        <input type="checkbox" th:field="*{open}" class="form-check-input">
        <label for="open" class="form-check-label">판매 오픈</label>
    </div>
</div>
cs

 

체크박스 - 조회

조회는 form 태그 사용 안함. th:object="${item}" 없으므로 ${item.price}, ${item.quantity} 등으로 사용해야 함.

조회 -> 변경하면 안되므로 disabled 추가

1
2
3
4
5
6
7
<div>판매 여부</div>
<div>
    <div class="form-check">
        <input type="checkbox" th:field="${item.open}" class="form-check-input" disabled>
        <label for="open" class="form-check-label">판매 오픈</label>
    </div>
</div>
cs

 

체크박스 값 변경 후 수정 안되는 현상

POST/ edit 할 때 변경된 item이 제대로 넘어오는 지 확인하기 위해  log 출력 -> 잘 넘어옴

itemRepository.update()에 setOpen() 추가 안되어있어서 그럼

 

체크 박스 - 멀티

HashMap(): 순서 보장 안됨

LinkedHashMap(): 순서 보장 됨

 

지역 추가하는 코드 등록, 수정, 조회 다 들어감

1
2
3
4
5
Map<StringString> regions = new LinkedHashMap<>();
regions.put("SEOUL""서울");
regions.put("BUSAN""부산");
regions.put("JEJU""제주");
model.addAttribute("regions", regions);
cs

@ModelAttribute(): 컨트롤러 호출 시 항상 자동으로 model.addAttribute() 실행

1
2
3
4
5
6
7
8
@ModelAttribute("regions")
public Map<StringString> regions() {
    Map<StringString> regions = new LinkedHashMap<>();
    regions.put("SEOUL""서울");
    regions.put("BUSAN""부산");
    regions.put("JEJU""제주");
    return regions;
}
cs

 

label 클릭해도 체크박스 활성화 -> 체크박스의 id를 알아야 함

th:field="*{regions}"에서 체크박스 id 자동 생성

th:for="${#ids.prev('open')}"

멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 있다. 그런데 문제는 이렇게 반복해서 HTML 태그를 생성할 때, 생성된 HTML 태그 속성에서 name 은 같아도 되지만, id 는 모두 달라야 한다. 따라서 타임리프는 체크박스를 each 루프 안에서 반복해서 만들 때 임의로 1 , 2 , 3 숫자를 뒤에 붙여준다.

 

each로 체크박스가 반복 생성된 결과 - id 뒤에 숫자가 추가됨

1
2
3
<input type="checkbox" value="SEOUL" class="form-check-input" id="regions1" name="regions">
<input type="checkbox" value="BUSAN" class="form-check-input" id="regions2" name="regions">
<input type="checkbox" value="JEJU" class="form-check-input" id="regions3" name="regions">
cs

 

HTML의 id 가 타임리프에 의해 동적으로 만들어지기 때문에 <label for="id 값"> 으로 label 의 대상이 되는 id 값을 임의로 지정하는 것은 곤란하다. 타임리프는 ids.prev(...) , ids.next(...) 을 제공해서 동적으로 생성되는 id 값을 사용할 수 있도록 한다.

 

th:value="${region.key}" 가 존재하면 checked, 없으면 체크안함

<input type="checkbox" th:field="${item.regions}" th:value="${region.key}" class="form-check-input" disabled>

 

라디오 버튼

라디오 박스: 히든 필드 안만들어도 됨 -> 한 번 선택하면 절대 null로 만들 수 없기 때문

 

메시지, 국제화

스프링 부트 메시지 소스 기본 값: spring.messages.basename=messages

 

MessageSource를 스프링 빈으로 등록하지 않고, 스프링 부트와 관련된 별도의 설정을 하지 않으면 messages 라는 이름으로 기본 등록된다. 따라서 messages_en.properties, messages_ko.properties, messages.properties 파일만 등록하면 자동으로 인식된다.

 

스프링 메시지 소스 사용

메시지가 없으면 오류 발생

1
2
3
4
5
@Test
void notFoundMessageCode() {
    assertThatThrownBy(() -> messageSource.getMessage("no_code"nullnull))
                            .isInstanceOf(NoSuchMessageException.class);
}
cs

 

default 메시지 설정 가능

1
2
3
4
5
@Test
void notFoundMessageCodeDefaultMessage() {
    String result = messageSource.getMessage("no_code"null"기본 메시지"null);
    assertThat(result).isEqualTo("기본 메시지");
}
cs

 

hello.name의 argument가 new Object[]에 넣은 값으로 치환됨

1
2
3
4
5
@Test
void argumentMessage() {
    String message = messageSource.getMessage("hello.name"new Object[]{"Spring"}, null);
    assertThat(message).isEqualTo("안녕 Spring");
}
cs
반응형
반응형

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

텍스트 - text, utext

<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>

 

escape: HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경. 타임리프는 기본적으로 escape를 제공함.

ex) "<": &lt;

 

unescape

1. th:text -> th:utext

2. [[...]] -> [(...)]

 

변수 - SpringEL

  • ${user.username} = userA
  • ${user['username']} = userA
  • ${user.getUsername()} = userA
  • ${users[0].username} = userA
  • ${users[0]['username']} = userA
  • ${users[0].getUsername()} = userA
  • ${userMap['userA'].username} = userA
  • ${userMap['userA']['username']} = userA
  • ${userMap['userA'].getUsername()} = userA

 

지역 변수 - (th:with)

1
2
3
<div th:with="first=${users[0]}">
    <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
cs

 

기본 객체들

기본 객체

  • ${#request}
  • ${#response}
  • ${#session}
  • ${#servletContext}
  • ${#locale}

편의 객체

HTTP 요청 파라미터 접근: param    예) ${param.paramData}

HTTP 세션 접근: session               예) ${session.sessionData}

스프링 빈 접근: @                       예) ${@helloBean.hello('Spring!')}

 

식 기본 객체 (Expression Basic Objects)

1
2
3
4
5
6
7
<ul>
    <li>request = <span th:text="${#request}"></span></li>
    <li>response = <span th:text="${#response}"></span></li>
    <li>session = <span th:text="${#session}"></span></li>
    <li>servletContext = <span th:text="${#servletContext}"></span></li>
    <li>locale = <span th:text="${#locale}"></span></li>
</ul>
cs

편의 객체

1
2
3
4
5
<ul>
    <li>Request Parameter = <span th:text="${param.paramData}"></span></li>
    <li>session = <span th:text="${session.sessionData}"></span></li>
    <li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></li>
</ul>
cs

유틸리티 객체와 날짜

1
2
3
4
<li>default = <span th:text="${localDateTime}"></span></li>
<li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDateTime,'yyyy-MM-dd HH:mm:ss')}"></span></li>
<li>${#temporals.day(localDateTime)} = <span th:text="${#temporals.day(localDateTime)}"></span></li>
<li>${#temporals.month(localDateTime)} = <span th:text="${#temporals.month(localDateTime)}"></span></li>
cs

 

URL 링크

 

/hello

/hello?param1=data1&param2=data2

/hello/{param1 }/{param2}

/hello/{param1}?param2=data2

1
2
3
4
<th:href="@{/hello}">
<th:href="@{/hello(param1=${param1}, param2=${param2})}">
<th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">
<th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">
cs

 

리터럴: 소스 코드 상에 고정된 값

  • String a = "hello"     // 문자 리터럴
  • int a = 10 * 20        // 숫자 리터럴

문자 리터럴은 항상 ' (작은 따옴표)로 감싸야 함

<span th:text="'hello'">

공백 없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지해서 다음과 같이 작은 따옴표를 생략할 수 있음.

룰: A-Z , a-z , 0-9 , [] , . , - , _

<span th:text="hello">

1
2
3
4
<li>'hello' + ' world!' = <span th:text="'hello' + ' world!'"></span></li>
<li>'hello world!' = <span th:text="'hello world!'"></span></li>
<li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li>
<li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li>
cs

※ 공백 포함하는데 ' 없으면 오류!!

 

연산

Elvis 연산자: 조건식의 편의 버전

No-Operation: _ 인 경우 마치 타임리프가 실행되지 않는 것 처럼 동작한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
<li>Elvis 연산자
    <ul>
        <li>${data}?: '데이터가 없습니다.' = <span th:text="${data}?: '데이터가 없습니다.'"></span></li>
        <li>${nullData}?: '데이터가 없습니다.' = <span th:text="${nullData}?:'데이터가 없습니다.'"></span></li>
    </ul>
</li>
 
<li>No-Operation
    <ul>
        <li>${data}?: _ = <span th:text="${data}?: _">데이터가 없습니다.</span></li>
        <li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가 없습니다.</span></li>
    </ul>
</li>
cs

 

속성 값 설정

속성 설정

ht:* 속성 지정 -> 기존 속성을 th:*로 지정한 속성으로 대체

 

속성 추가

- th:attrappend = <input type="text" class="text" th:attrappend="class='large'" /><br/>
- th:attrprepend = <input type="text" class="text" th:attrprepend="class='large'" /><br/>
- th:classappend = <input type="text" class="text" th:classappend="large" /><br/>

 

checked 처리

HTML에서 checked 속성은 checked 속성의 값과 상관없이 checked 라는 속성만 있어도 체크가 된다.

타임리프의 th:checked 는 값이 false 인 경우 checked 속성 자체를 제거한다.

<input type="checkbox" name="active" th:checked="false" />

-> <input type="checkbox" name="active" />

 

th:checked="true" -> 체크 O
th:checked="false" -> 체크 X
checked="false" -> 체크 O

 

반복

반복 상태 유지

<tr th:each="user, userStat : ${users}">

반복의 두번째 파라미터를 설정해서 반복의 상태를 확인 할 수 있습니다.

두번째 파라미터는 생략 가능한데, 생략하면 지정한 변수명( user ) + Stat 가 됨

여기서는 user + Stat = userStat 이므로 생략 가능

 

반복 상태 유지 기능

  • index : 0부터 시작하는 값
  • count : 1부터 시작하는 값
  • size : 전체 사이즈
  • even , odd : 홀수, 짝수 여부( boolean )
  • first , last :처음, 마지막 여부( boolean )
  • current : 현재 객체

조건부 평가

조건 만족하지 않으면 태그 자체가 사라짐

<span th:text="'미성년자'" th:if="${user.age lt 20}"></span>

<span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>

 

주석

 

1. 표준 HTML 주석: 그대로 표시, 렌더링 안함

<!-- ㅇㅁㄴㄻㄴㅇㄻ -->

2. 타임리프 파서 주석: 표시 안함

<!--/* ㄴㅁㅇㄻㄴㅇㄻ */-->

3. 타임리프 프로토타입 주석: 

<!--/*/ ㅁㄴㅇㄻㄴㅇㄻㅇㄴㄹ /*/-->

 

타임리프 프로토타입 주석 (잘 안씀)

웹 브라우저에서 파일 직접 열 때는 안 보임

타임리프로 렌더링 된 경우에만 보여줌

 

블록 - <th:block>

HTML 태그가 아닌 타임리프의 유일한 자체 태그

 

<div>를 두 개씩 th:each 돌리기

1
2
3
4
5
6
7
8
9
<th:block th:each="user : ${users}">
<div>
사용자 이름1 <span th:text="${user.username}"></span>
사용자 나이1 <span th:text="${user.age}"></span>
</div>
<div>
요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
</div>
</th:block>
cs

 

자바스크립트 인라인

자바스크립트에서 타임리프를 편리하게 사용

<script th:inline="javascript">

1
2
3
4
5
6
7
8
9
10
<script th:inline="javascript">
    var username = [[${user.username}]];
    var age = [[${user.age}]];
 
    //자바스크립트 내추럴 템플릿
    var username2 = /*[[${user.username}]]*/ "test username";
 
    //객체
    var user = [[${user}]];
</script>
cs

 

인라인 X

var username = "UserA";

var age = 10;

var username2 = /*UserA*/ "test username";

var user = BasicController.User(username=UserA, age=10);

 

인라인 O

var username = "UserA";

var age = 10;

var username2 = "UserA";

var user = {"username":"UserA", "age":10};

 

 

자바스크립트 인라인 each

1
2
3
4
5
<script th:inline="javascript">
    [# th:each="user, stat : ${users}"]
    var user[[${stat.count}]] = [[${user}]];
    [/]
</script>
cs

결과

1
2
3
4
5
<script>
    var user1 = {"username":"userA","age":10};
    var user2 = {"username":"userB","age":20};
    var user3 = {"username":"userC","age":30};
</script>
cs

 

템플릿 조각

공통 영역 처리

fragmentMain.html에서 footer.html 불러서 사용

{template/fragment/footer}의 copy를 가져다가 씀

1
2
3
4
5
6
7
8
9
10
11
12
13
<h1>부분 포함</h1>
<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div>
 
<h2>부분 포함 replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div>
 
<h2>부분 포함 단순 표현식</h2>
<div th:replace="template/fragment/footer :: copy"></div>
 
<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div>
 
cs

insert: <div> 살아있음

1
2
3
4
5
<div>
    <footer>
        footer 테스트
    </footer>
</div>
cs

replace: <div>를 교체함

1
2
3
<footer>
    footer 테스트
</footer>
cs

 

템플릿 레이아웃1

/template/layout/base에 있는 common_header를 불러옴 (title, link를 넘김)

1
2
3
4
5
<head th:replace="template/layout/base :: common_header(~{::title},~{::link})">
    <title>메인 타이틀</title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
    <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
cs

 

헤더를 이걸로 대체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html xmlns:th="http://www.thymeleaf.org">
 
    <head th:fragment="common_header(title,links)">
        <title th:replace="${title}">레이아웃 타이틀</title>
 
        <!-- 공통 -->
        <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
        <link rel="shortcut icon" th:href="@{/images/favicon.ico}">
        <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
 
        <!-- 추가 -->
        <th:block th:replace="${links}" />
    </head>
...
</html>
cs

 

메인 타이틀이 전달한 부분으로 교체됨

공통 부분은 그대로 유지되고, 추가 부분에 전달한 <link>들이 포함됨

 

템플릿 레이아웃2

layoutExtendMain에서 title과 section을 넘기면 html 자체를 layoutFile로 교체함 (title과 section만 바꿔서).

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title},~{::section})}"
xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>메인 페이지 타이틀</title>
    </head>
    <body>
        <section>
            <p>메인 페이지 컨텐츠</p>
            <div>메인 페이지 포함 내용</div>
        </section>
    </body>
</html>
cs
반응형
반응형

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

HTTP 요청 메시지 - JSON

json 형식을 HelloData 객체로 바로 받을 수 있음

객체를 return하면 json 형식으로 출력

1
2
3
4
5
6
@ResponseBody
@PostMapping("/request-body-json-v5")
    public HelloData requestBodyJsonV5(@RequestBody HelloData helloData throws IOException {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    return helloData;
}
cs
 

응답 - 정적 리소스, 뷰 템플릿

@ResponseBody 붙이면 화면에 "response/hello" 출력됨

1
2
3
4
5
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
    model.addAttribute("data""hello");
    return "response/hello";
}
cs

view 이름과 url 같으면 return 생략 가능

하지만, 명시성이 떨어지고 일치하는 경우가 별로 없음

1
2
3
4
@RequestMapping("/response/hello")
public void responseViewV3(Model model) {
    model.addAttribute("data""hello");
}
cs

 

HTTP 응답 - HTTP API, 메시지 바디에 직접 입력

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(11);
 
    return new ResponseEntity<>(helloData, HttpStatus.OK);
}
 
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(11);
 
    return helloData;
}
cs

 

@ResponseBody를 사용하면 HelloData를 바로 return 할 수 있음.

하지만, @ResponseStatus()를 어노테이션으로 지정해야하므로, HttpStatus 동적으로 변경 불가.

응답 코드를 동적으로 변경해야 한다면, V1처럼 ResponseEntity<>()를 return함

 

HTTP 메시지 컨버터

스프링 MVC는 다음의 경우에 HTTP 메시지 컨버터를 적용한다.

HTTP 요청: @RequestBody , HttpEntity(RequestEntity) ,

HTTP 응답: @ResponseBody , HttpEntity(ResponseEntity) ,

 

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

타임리프 (Thymeleaf)

속성 변경 - th:href

th:href="@{/css/bootstrap.min.css}"

href="value1" 을 th:href="value2" 의 값으로 변경한다.

 

URL 링크 표현식 - @{...},

th:href="@{/css/bootstrap.min.css}"

 

속성 변경 - th:onclick

onclick="location.href='addForm.html'"

th:onclick="|location.href='@{/basic/items/add}'|"

 

리터럴 대체 - |...|

<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
-> <span th:text="|Welcome to our application, ${user.name}!|">

location.href='/basic/items/add'
-> th:onclick="'location.href=' + '\'' + @{/basic/items/add} + '\''"
-> th:onclick="|location.href='@{/basic/items/add}'|"

 

반복 출력 - th:each

<tr th:each="item : ${items}">

 

변수 표현식 - ${...}

<td th:text="${item.price}">10000</td>

 

내용 변경 - th:text

<td th:text="${item.price}">10000</td>

 

URL 링크 표현식2 - @{...},

th:href="@{/basic/items/{itemId}(itemId=${item.id})}"

 

쿼리 파라미터 생성 가능

예) th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"

생성 링크: http://localhost:8080/basic/items/1?query=test

 

URL 링크 간단히

th:href="@{|/basic/items/${item.id}|}"

1
2
3
4
5
6
<tr th:each="item : ${items}">
    <td><a href="item.html" th:href="@{/basic/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
    <td><a href="item.html" th:href="@{|/basic/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
    <td th:text="${item.price}">가격</td>
    <td th:text="${item.quantity}">수량</td>
</tr>
cs

 

상품 등록 폼

th:action에 값이 없으면 현재 url 전송

1
2
<form action="item.html" th:action="/basic/items/add" method="post">
<form action="item.html" th:action method="post">
cs

상품 등록 처리 - @ModelAttribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
                        @RequestParam int price,
                        @RequestParam Integer quantity,
                        Model model) {
    Item item = new Item();
    item.setItemName(itemName);
    item.setPrice(price);
    item.setQuantity(quantity);
 
    itemRepository.save(item);
    model.addAttribute("item", item);
    return "basic/item";
}
cs

 

 

@ModelAttribute() 사용하면 model.addAttribute()까지 같이 해줌

1
2
3
4
5
@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item) {
    itemRepository.save(item);
    return "basic/item";
}
cs

 

괄호를 지우면 클래스명 첫 글자를 소문자로 바꿔서 model.addAttribute()

1
2
3
4
5
@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item) {
    itemRepository.save(item);
    return "basic/item";
}
cs

 

@ModelAttribute 생략 가능

1
2
3
4
5
@PostMapping("/add")
public String addItemV4(Item item) {
    itemRepository.save(item);
    return "basic/item";
}
cs

PRG Post/Redirect/Get

상품 등록을 완료하고 웹 브라우저의 새로고침 버튼을 클릭해보자.

상품이 계속해서 중복 등록되는 것을 확인할 수 있다.

 

새로고침 시 마지막에 서버에 전송한 데이터를 다시 전송

마지막 행위: POST /add

 

POST 처리 후 Redirect하면 마지막 요청이 GET으로 변경됨

새로고침하면 GET 호출됨

 

※ 주의

"redirect:/basic/items/" + item.getId() redirect에서 +item.getId() 처럼 URL에 변수를

더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험하다.

 

RedirectAttributes

상품 등록 후에 "저장되었습니다" 띄우기

-> Redirect 한 곳에서 처리

 

redirectAttributes.addAttribute()에 넣은 값으로 치환 가능
URL 인코딩 문제 해결 가능
치환 안하면 쿼리 파라미터로 넘어감

1
2
3
4
5
6
7
@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes) {
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status"true);
    return "redirect:/basic/items/{itemId}";
}
cs
반응형
반응형

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

요청 받기

1. PathVariable: @PathVariable

1
2
3
4
@GetMapping("/{userId}")
    public String findUser(@PathVariable String userId) {
    return "get userId: " + userId;
}
cs

2. QueryParameter: @RequestParam

1
2
3
4
5
6
7
8
@GetMapping("/search")
public ResponseEntity<Message> searchCourses(@RequestParam String keyword,
                                            @RequestParam String order,
                                            Pageable pageable){
    Page<CoursePreviewDto> result = courseService.searchCourses(keyword, order, pageable);
    message.setData(result.getContent());
    return new ResponseEntity<>(message, OK);
}
cs

Body와 Parameter가 동일할 경우 @RequestParam 제거 가능

1
2
3
4
5
6
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}
cs

 

3. Header: @RequestHeader, HttpServletRequest.getHeader()

1
2
3
4
5
6
7
@RequestMapping("/headers")
public String headers(@RequestHeader MultiValueMap<StringString> headerMap,
                    @RequestHeader("host"String host){
    log.info("headerMap={}", headerMap);
    log.info("header host={}", host);
    return "ok";
}
cs
1
2
3
4
5
6
@GetMapping("/user")
public ResponseEntity<Message> getUserProfile(HttpServletRequest request){
    String jwtToken = request.getHeader("Authorization").replace("Bearer """);
    User user = userService.getUserFromJWT(jwtToken);
    return new ResponseEntity<>(message, message.getStatus());
}
cs

4. Body: @RequestBody

1
2
3
4
5
6
7
@PostMapping("/user")
public Map<StringString> updateProfile(@RequestBody Map<StringString> req){
    String nickname = req.get("nickname");
    int height = Integer.parseInt(req.get("height"));
    int weight = Integer.parseInt(req.get("weight"));
    return message;
}
cs

 

HttpServletRequest request

1. request.getParameter(): Query Parameter 조회

2. request.getHeader(): Header 조회

※ HttpServletRequest request.getParameter() == @RequestParam()

 

@Controller + return String 일 경우

return "~~~~"에 해당하는 View를 찾음.

String 자체를 return하고 싶다면 

1. @ResponseBody를 붙임

2. @Controller를 @RestController로 변경

 

Body와 Parameter가 일치할 경우 @RequestParam 제거 가능

1
2
3
4
5
6
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}
cs

 

@RequestParam 옵션

1. required = true / false

2. defaultValue = "~~"

 

ModelAttribute: 데이터 한 번에 받을 수 있음

 

1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class HelloData {
    private String username;
    private int age;
}
 
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    return "ok";
}
cs

 

Body의 String 받기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    @PostMapping("/request-body-string-v1")
    public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
 
        log.info("messageBody={}", messageBody);
        response.getWriter().write("ok");
    }
 
    @PostMapping("/request-body-string-v2")
    public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
 
        log.info("messageBody={}", messageBody);
        responseWriter.write("ok");
    }
 
    @PostMapping("/request-body-string-v3")
    public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
        log.info("messageBody={}", httpEntity); // Http의 body를 String으로 변환해줌
        return new HttpEntity<>("ok");
    }
 
    @ResponseBody
    @PostMapping("/request-body-string-v4")
    public String requestBodyStringV3(@RequestBody String messageBody) throws IOException {
        log.info("messageBody={}", messageBody);
        return "ok";
    }
cs
반응형
반응형

졸업 프로젝트로 등산로 추천 및 측정 애플리케이션을 만들고 있다.

GPX 파일을 읽어서 네이버맵 API에 표시해야 하는데,

어떤 GPX파일은 잘 열리는데, 또 다른 GPX 파일들은 따라가기 하면 앱이 튕기는 현상이 발생했다.

되는거
안되는거

구글링해도 딱히 쓸 만한 정보가 없었다.

 

깃헙에 검색해봐도 별 내용이 없었다.

 

에러 로그에 있는 "XML.java:100"을 눌러보니까 StAXResult, StAXSource가 빨갛게 떴다. Cannot resolve symbol 'StAXResult'라고 떴다. import 자체가 안됐다.

혹시나 해서 jpx 버전을 바꿔봤다. 뭔가 버전문제 일 것 같았다. 아래 사진과 같이 똑같이 변경했다 (2.2.0 -> 1.4.0으로 다운그레이드)

이렇게 하니까 잘 돌아갔다

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

잘 돌아가긴 개뿔...

이상한 태그 있으면 앱이 또 튕긴다......

검색해보니까 1.5.1버전에서 고쳐졌다고 한다.

근데 내 앱에서는 왜 안고쳐지지..................... 된다매!!!!

 

Caused by: java.lang.ClassNotFoundException: Didn't find class "javax.xml.transform.stax.StAXSource" on path

2.2.0

2.1.0

2.0.0

1.7.0

 

Caused by: java.io.InvalidObjectException: Invalid 'gpx' input.

1.6.1

1.6.0

 

Caused by: java.lang.reflect.InvocationTargetException
Caused by: java.io.IOException: javax.xml.stream.XMLStreamException

1.5.3

1.5.2

1.5.1

1.5.0

 

그나마 1.4.0 버전이 제일 나은 것 같다...

반응형

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

jwt 공부 참고  (0) 2021.09.28
@RequestBody  (0) 2021.08.24
gpx 를 이미지로 변환  (0) 2021.05.05
UserProfileDto NPE  (0) 2021.05.04
[연습] querydsl 정렬 포함한 쿼리 실행 시 NPE 발생  (0) 2021.04.29
반응형

github.com/pavel-perina/gpx_to_png

 

pavel-perina/gpx_to_png

Script to create png thumbnails for multiple gpx track files - pavel-perina/gpx_to_png

github.com

 

저 분 깃헙 코드 쓰는데, opencyclemap 쓰면 배경사진에 api key 입력하라는 워터마크가 떠서 조금 바꿨다

썬더포레스트쓰면 없앨 수 있다고 했다. (api key 공짜로 사용가능)

www.frogsparks.com/opencyclemap-api-key-required/

 

OpenCycleMap API KEY REQUIRED - MyTrails

OpenCycleMap appartient à ThunderForest, un service payant. Ils ajoutent le calque « API KEY REQUIRED » sur leurs cartes pour encourager les utilisateurs à s’enregistrer. Vous pouvez créer un compte gratuitement et mettre à jour la définitio

www.frogsparks.com

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# -*- coding: utf-8 -*-
 
import sys as mod_sys
import math as mod_math
import logging as mod_logging
import urllib as mod_urllib
import os as mod_os
import gpxpy as mod_gpxpy
from PIL import Image as mod_pil_image
from PIL import ImageDraw as mod_pil_draw
import glob as mod_glob
osm_cache_base = r"c:\devel-python\.cache\opencyclemap"
osm_tile_res = 256
 
def format_time(time_s):
    if not time_s:
        return 'n/a'
    minutes = mod_math.floor(time_s / 60.)
    hours = mod_math.floor(minutes / 60.)
    return '%s:%s:%s' % (str(int(hours)).zfill(2), str(int(minutes % 60)).zfill(2), str(int(time_s % 60)).zfill(2))
 
def get_tile_url (x, y, z):
    return "http://a.tile.thunderforest.com/{타일종류}/%d/%d/%d.png?apikey={썬더포레스트 api 키}" % (z, x, y)
 
def get_tile_filename (x, y, z):
       return r"c:\devel-python\.cache\opencyclemap\%d\%d\%d.png" % (z, x, y)
 
def get_map_suffix ():
        return "osm-cycle"
 
def osm_lat_lon_to_x_y_tile (lat_deg, lon_deg, zoom):
    """ Gets tile containing given coordinate at given zoom level """
    # taken from http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames, works for OSM maps and mapy.cz
    lat_rad = mod_math.radians(lat_deg)
    n = 2.0 ** zoom
    xtile = int((lon_deg + 180.0/ 360.0 * n)
    ytile = int((1.0 - mod_math.log(mod_math.tan(lat_rad) + (1 / mod_math.cos(lat_rad))) / mod_math.pi) / 2.0 * n)
    return (xtile, ytile)
 
 
def osm_get_auto_zoom_level ( min_lat, max_lat, min_lon, max_lon, max_n_tiles):
    """ Gets zoom level which contains at maximum `max_n_tiles` """
    for z in range (0,17):
        x1, y1 = osm_lat_lon_to_x_y_tile (min_lat, min_lon, z)
        x2, y2 = osm_lat_lon_to_x_y_tile (max_lat, max_lon, z)
        max_tiles = max (abs(x2 - x1), abs(y2 - y1))
        if (max_tiles > max_n_tiles):
            print ("Max tiles: %d" % max_tiles)
            return z
    return 17
 
 
def osm_cache_tile (x,y,z):
    """ Downloads tile x,y,x into cache. Directories are automatically created, existing files are not retrieved. """
    src_url = get_tile_url(x,y,z)
    dst_filename = get_tile_filename(x,y,z)
 
    dst_dir = mod_os.path.dirname(dst_filename)
    if not mod_os.path.exists(dst_dir):
        mod_os.makedirs(dst_dir)
    if mod_os.path.isfile (dst_filename):
        return
 
    print ("Downloading %s ..." % src_url)
    request = mod_urllib.request.Request (src_url)
    response = mod_urllib.request.urlopen (request)
    data = response.read()
    f = open(dst_filename, "wb")
    f.write(data)
    f.close()
 
 
class MapCreator:
    """ Class for map drawing """
 
    def __init__(self, min_lat, max_lat, min_lon, max_lon, z):
        """ constructor """
        x1, y1 = osm_lat_lon_to_x_y_tile (min_lat, min_lon, z)
        x2, y2 = osm_lat_lon_to_x_y_tile (max_lat, max_lon, z)
        self.x1 = min (x1, x2)
        self.x2 = max (x1, x2)
        self.y1 = min (y1, y2)
        self.y2 = max (y1, y2)
        self.w = (self.x2 - self.x1 + 1* osm_tile_res
        self.h = (self.y2 - self.y1 + 1* osm_tile_res
        self.z = z
        print (self.w, self.h)
        self.dst_img = mod_pil_image.new ("RGB", (self.w, self.h))
 
 
    def cache_area(self):
        """ Downloads necessary tiles to cache """
        print ("Caching tiles x1=%d y1=%d x2=%d y2=%d" % (self.x1, self.y1, self.x2, self.y2))
        for y in range (self.y1, self.y2 + 1):
            for x in range (self.x1, self.x2 + 1):
                osm_cache_tile (x, y, self.z)
 
 
    def create_area_background(self):
        """ Creates background map from cached tiles """
        for y in range (self.y1, self.y2+1):
            for x in range (self.x1, self.x2+1):
                try:
                    src_img = mod_pil_image.open (get_tile_filename (x, y, z))
                    dst_x = (x-self.x1)*osm_tile_res
                    dst_y = (y-self.y1)*osm_tile_res
                    self.dst_img.paste (src_img, (dst_x, dst_y))
                except Exception as e:
                    print("Error processing file " + get_tile_filename (x, y, z))
 
 
    def lat_lon_to_image_xy (self, lat_deg, lon_deg):
        """ Internal. Converts lat, lon into dst_img coordinates in pixels """
        lat_rad = mod_math.radians(lat_deg)
        n = 2.0 ** self.z
        xtile_frac = (lon_deg + 180.0/ 360.0 * n
        ytile_frac = (1.0 - mod_math.log(mod_math.tan(lat_rad) + (1 / mod_math.cos(lat_rad))) / mod_math.pi) / 2.0 * n
        img_x = int( (xtile_frac-self.x1)*osm_tile_res )
        img_y = int( (ytile_frac-self.y1)*osm_tile_res )
        return (img_x, img_y)
 
 
    def draw_track (self, gpx):
        """ Draw GPX track onto map """
        draw = mod_pil_draw.Draw (self.dst_img)
        trk = 0         # Just changes color of segment a little
        for track in gpx.tracks:
            for segment in track.segments:
                idx = 0
                x_from = 0
                y_from = 0
                for point in segment.points:
                    if (idx == 0):
                        x_from, y_from = self.lat_lon_to_image_xy (point.latitude, point.longitude)
                    else:
                        x_to, y_to = self.lat_lon_to_image_xy (point.latitude, point.longitude)
                        #                        draw.line ((x_from,y_from,x_to,y_to), (255,0,trk), 2)
                        draw.line((x_from,y_from,x_to,y_to), (111), 10)
                        x_from = x_to
                        y_from = y_to
                    idx += 1
                trk += 32
                if (trk > 160):
                    trk = 0
 
 
    def save_image(self, filename):
        print("Saving " + filename)
        self.dst_img.save (filename)
 
if (__name__ == '__main__'):
    gpx_files = mod_glob.glob (r"{gpx 저장 경로}\*.gpx")
 
    for gpx_file in gpx_files:
        try:
            gpx = mod_gpxpy.parse(open(gpx_file, encoding='UTF8'))
 
            # Print some track stats
            print ('--------------------------------------------------------------------------------')
            print ('  GPX file     : %s' % gpx_file)
            start_time, end_time = gpx.get_time_bounds()
            print('  Started       : %s' % start_time)
            print('  Ended         : %s' % end_time)
            print('  Length        : %2.2fkm' % (gpx.length_3d() / 1000.))
            moving_time, stopped_time, moving_distance, stopped_distance, max_speed = gpx.get_moving_data()
            print('  Moving time   : %s' % format_time(moving_time))
            print('  Stopped time  : %s' % format_time(stopped_time))
            #            print('  Max speed     : %2.2fm/s = %2.2fkm/h' % (max_speed, max_speed * 60. ** 2 / 1000.))
            uphill, downhill = gpx.get_uphill_downhill()
            print('  Total uphill  : %4.0fm' % uphill)
            print('  Total downhill: %4.0fm' % downhill)
            min_lat, max_lat, min_lon, max_lon = gpx.get_bounds()
            print("  Bounds        : [%1.4f,%1.4f,%1.4f,%1.4f]" % (min_lat, max_lat, min_lon, max_lon))
            z = osm_get_auto_zoom_level (min_lat, max_lat, min_lon, max_lon, 6)
            print("  Zoom Level    : %d" % z)
 
            # Create the map
            map_creator = MapCreator (min_lat, max_lat, min_lon, max_lon, z)
            map_creator.cache_area()
            map_creator.create_area_background()
            map_creator.draw_track(gpx)
            map_creator.save_image (gpx_file[:-4+ '.png')
 
        except Exception as e:
            mod_logging.exception(e)
            print('Error processing %s' % gpx_file)
            mod_sys.exit(1)
cs

타일 종류는 다음과 같다

transport

transport-dark

atlas

cycle

landscape

mobile-atlas

neighbourhood

outdoors

pioneer

spinal-map

반응형

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

@RequestBody  (0) 2021.08.24
특정 GPX 파일 실행 시 앱 종료되는 현상 (jenetics/jpx)  (0) 2021.06.14
UserProfileDto NPE  (0) 2021.05.04
[연습] querydsl 정렬 포함한 쿼리 실행 시 NPE 발생  (0) 2021.04.29
[연습] travis-ci  (0) 2021.04.27
반응형

 

rightjoin

leftjoin

 

등산 기록이 없는 경우, tuple.get() == null

null을 나누면 NPE 발생하므로,

null인지 확인 후에, null이면 0.0으로, null 아니면 나눠주기

반응형

+ Recent posts