반응형

 

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

 

1890번: 점프

첫째 줄에 게임 판의 크기 N (4 ≤ N ≤ 100)이 주어진다. 그 다음 N개 줄에는 각 칸에 적혀져 있는 수가 N개씩 주어진다. 칸에 적혀있는 수는 0보다 크거나 같고, 9보다 작거나 같은 정수이며, 가장

www.acmicpc.net

DP 써야하는 이유

Priority Queue 사용한 이유

(1, 2)가 3이 된 상태에서 (1, 3)과 (2, 2)를 업데이트 해야 하는데

(1, 2)가 1일 때 (1, 3)과 (2, 2)를 업데이트 한 뒤에 (1, 2)가 3이 되면 값이 이상해진다.

 

반례

4

1 1 1 1

2 1 1 1

1 1 1 1

1 1 1 0

 

 

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
#include <iostream>
#include <queue>
#define rep(i,n) for(int i=0;i<n;i++)
#define ll long long
using namespace std;
struct Node {
    int x;
    int y;
};
struct cmp {
    bool operator()(Node a, Node b) {
        return a.x + a.y > b.x + b.y;
    }
};
int n, arr[101][101];
int dx[] = { 10 };
int dy[] = { 01 };
bool visited[101][101];
ll dp[101][101];
void func() {
    priority_queue<Node, vector<Node>, cmp> q;
    q.push({ 0,0 });
    dp[0][0= 1;
    visited[0][0= 1;
    while (!q.empty()) {
        Node now = q.top(); q.pop();
        int x = now.x;
        int y = now.y;
        if (arr[x][y] == 0continue;
        rep(i, 2) {
            int nx = x + dx[i] * arr[x][y];
            int ny = y + dy[i] * arr[x][y];
            if (nx >= n || ny >= n) continue;
            if (!visited[nx][ny]) {
                visited[nx][ny] = 1;
                q.push({ nx, ny });
            }
            dp[nx][ny] += dp[x][y];
        }
    }
}
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> n;
    rep(i, n) {
        rep(j, n)
            cin >> arr[i][j];
    }
    func();
    cout << dp[n - 1][n - 1];
}
cs
반응형

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

백준 11066  (0) 2021.07.27
백준 1937  (0) 2021.07.26
백준 2293  (0) 2021.07.20
백준 1103  (0) 2021.07.08
백준 11054  (0) 2021.02.19
반응형

정보처리기사

1과목 소프트웨어 설계

1장 요구사항 확인

섹션 001. 소프트웨어 생명 주기

- 폭포수 모형

- 프로토타입 모형

- 나선형 모형

- 애자일 모형

 

섹션 002. 스크럼 기법

백로그 -> 제품 책임자가 담당 (PO, Product Owner)

팀원 -> 백로그 우선순위 지정 불가

소멸 차트 -> 남은 작업 시간을 그래프로 표현

 

섹션 007. 요구사항 분석

구조적 분석 기법: 자료의 흐름과 처리 중심

- 도형 중심

- 하향식 방법

- 도구

① 자료 흐름도 (DFD, Data Flow Diagram)

  -> 프로세스(Process) / 자료 흐름 (Flow) / 자료 저장소 (Data Store) / 단말 (Terminal)

② 자료 사전 (DD, Data Dictionary)

③ 소단위 명세서 (Mini-Spec.)

④ 개체 관계도 (ERD)

⑤ 상태 전이도 (STD)

⑥ 제어 명세서

 

섹션 008. 요구사항 분석 CASE와 HIPO

CASE

종류

① SADT (Structured Analysis and Design Technique): SoftTech 사

② SREM (Software Requirements Engineering Methodology): TRW사

  - RSL (Requirement Statement Language)

  - REVS (Requirement Engineering and Validation System)

③ PSL / PSA: 미시간 대학

  - PSL (Problem Statement Language)

  - PSA (Problem Statement Analyzer)

④ TAGS (Technology for Automated Generation of Systems)

 

HIPO (Hierarchy Input Process Output): 입력, 처리, 출력의 기능을 나타냄

① 가시적 도표 (Visual Table of Contents): 전체적인 기능과 흐름

② 총체적 도표 (Overview Diagram): 전반적인 정보

③ 세부적 도표 (Detail Diagram): 상세 정보

 

섹션 009. UML (Unified Modeling Language)

관계

① 연관(Association) 관계

② 집합(Aggregation) 관계

③ 포함(Composition) 관계

④ 일반화(Generalization) 관계

⑤ 의존(Dependency) 관계

⑥ 실체화(Realization) 관계

 

구조적(Structural) 다이어그램

① 클래스(Class) 다이어그램

② 객체(Object) 다이어그램

③ 컴포넌트(Component) 다이어그램

④ 배치(Deployment) 다이어그램

⑤ 복합체 구조(Composite Structure) 다이어그램

⑥ 패키지(Package) 다이어그램

 

행위(Behavioral) 다이어그램

① 유스케이스(Use Case) 다이어그램

② 시퀀스(Sequence) 다이어그램

③ 커뮤니케이션(Communication) 다이어그램

④ 상태(State) 다이어그램

⑤ 활동(Activity) 다이어그램

⑥ 상호작용 개요(Interaction Overview) 다이어그램

⑦ 타이밍(Timing) 다이어그램

반응형
반응형

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

 

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

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

www.inflearn.com

공통 관심사 (Cross-cutting concern)

애플리케이션 여러 로직에서 공통으로 관심이 있는 것

-> 등록, 수정, 삭제, 조회 등 여러 로직에서 공통으로 인증에 대해서 관심을 가짐

 

필터 제한

로그인 O: HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러

로그인 X: HTTP 요청 -> WAS -> 필터 (서블릿 호출 X)

필터 적용하면 필터 호출한 뒤에 서블릿 호출됨. 모든 고객의 요청 로그 남길 때 필터를 사용하면 됨

필터에 특정 URL 패턴 적용 가능

 

필터 인터페이스

필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리한다.

① init(): 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.

② doFilter(): 고객의 요청이 올 때 마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.

③ destroy(): 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.

※ 싱글톤 -> 하나만 등록됨

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Filter {
    public default void init(FilterConfig filterConfig) throws ServletException {
 
    }
 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 
    }
 
    public default void destroy() {
    
    }
}
cs

 

로그 필터

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
@Slf4j
public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("로그 필터 초기화");
    }
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("로그 필터 doFilter");
        HttpServletRequest httpRequest = (HttpServletRequest) request;
 
        // 요청 URI 확인
        String requestURI = httpRequest.getRequestURI();
 
        // 요청을 사용자별로 구분
        String uuid = UUID.randomUUID().toString();
 
        try {
            log.info("REQUEST [{}][{}]", uuid, requestURI);
            // 있으면 다음 필터
            // 없으면 서블릿 호출
            chain.doFilter(request,response);
        } catch (Exception e){
            throw e;
        } finally {
            log.info("RESPONSE [{}][{}]", uuid, requestURI);
        }
 
    }
 
    @Override
    public void destroy() {
        log.info("로그 필터 소멸");
    }
}
cs

인증 체크 필터

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
    // 로그인 안해도 접근 가능한 uri
    private static final String[] whiteist = {"/""/members/add""/login""/logout""/css/*"};
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();
 
        HttpServletResponse httpResponse = (HttpServletResponse) response;
 
        try{
            log.info("인증 체크 필터 시작{}", requestURI);
 
            // 요청 url이 white list에 없으면 인증 체크 필요
            if (isLoginCheckPath(requestURI)) {
                log.info("인증 체크 로직 실행 {}", requestURI);
                HttpSession session = httpRequest.getSession(false);
                if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null){
                    log.info("미인증 사용자 요청 {}", requestURI);
 
                    // 로그인 페이지로 보냄
                    // 로그인 성공 시 요청한 URI로 다시 redirect
                    // LoginController에서 처리하는 로직 추가 필요
                    httpResponse.sendRedirect("/login?redirectURL="+requestURI);
                    return;
                }
            }
 
            chain.doFilter(request, response);
        } catch (Exception e){
            throw e;
        } finally {
            log.info("인증 체크 필터 종료 {}", requestURI);
        }
    }
 
    /**
     * 화이트 리스트의 경우 인증 체크 X
     */
    private boolean isLoginCheckPath(String requestURI){
        return !PatternMatchUtils.simpleMatch(whiteist, requestURI);
    }
cs

※ chain.doFilter(request, response) 호출 안하면 다음으로 진행 안됨!!

필터 등록

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);                 // 필터간 순서 지정
        filterRegistrationBean.addUrlPatterns("/*");        // 적용할 URL 지정
 
        return filterRegistrationBean;
    }
    @Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginFilterCheck());
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.addUrlPatterns("/*"); // 필터의 white list에 적어놨기 때문에 모든 경로로 설정해도 됨 (유지보수 편리)
 
        return filterRegistrationBean;
    }
cs

인증 성공 시, 처음 요청한 URL로 redirect

1
2
3
4
5
6
7
8
9
@PostMapping("/login")
public String loginV4(@Valid @ModelAttribute("loginForm") LoginForm form,
                      BindingResult bindingResult,
                      @RequestParam(defaultValue = "/"String redirectURL,
                      HttpServletRequest request){
 
    ...
    return "redirect:"+redirectURL;
}
cs

 

스프링 인터셉터

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 1(비 로그인 사용자는 여기서 종료) -> 인터셉터 2 -> ... -> 컨트롤러

 

호출 순서

HTTP 요청 -> Dispatcher Servlet -> preHandle() -> 핸들러 어댑터 -> 핸들러 (컨트롤러) -> postHandle() -> View -> afterCompletion()

 

예외 발생 시

preHandle: 컨트롤러 호출 전에 호출됨

postHandle: 컨트롤러에서 예외 발생하면 postHandle 호출 안됨

afterComletion: 예외가 발생해도 항상 호출 됨. Exception ex를 파라미터로 받아서 어떤 예외가 발생했는 지 출력 가능

 

인터셉터 인터페이스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, 
                              HttpServletResponse response, 
                              Object handler) throws Exception {
        
        
    }
                              
    default void postHandle(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler, 
                            @Nullable ModelAndView modelAndView) throws Exception {
        
    }
    default void afterCompletion(HttpServletRequest request, 
                                 HttpServletResponse response, 
                                 Object handler, 
                                 @Nullable Exception ex) throws Exception {
        
    }
}
cs

로그 인터셉터

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
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
 
    public static final String LOG_ID = "logId";
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        String uuid = UUID.randomUUID().toString();
 
        // afterCompletion에서 사용하기 위해 request에 전달
        request.setAttribute(LOG_ID, uuid);
 
        // @RequestMapping: HandlerMethod
        // 정적 리소스: ResourceHttpRequestHandler
        if(handler instanceof HandlerMethod){
            // 호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다
            HandlerMethod hm = (HandlerMethod) handler;
 
        }
        log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
        return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle [{}]", modelAndView);
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String requestURI = request.getRequestURI();
 
        // preHandle에서 생성한 uuid를 request를 통해서 전달받음
        Object logId = request.getAttribute(LOG_ID);
        log.info("RESPONSE [{}][{}][{}]", logId, requestURI, handler);
 
        // 에러 출력
        if(ex!=null){
            log.error("afterCompletion error!!", ex);
        }
    }
}
cs

● 종료 로그를 postHandle이 아니라 afterCompletion에서 실행하는 이유

-> 예외 발생하면 postHandle이 호출되지 않기 때문

 

● 요청 로그 구분하기 위해 uuid 생성

서블릿 필터는 지역 변수로 해결 가능하지만, 스프링 인터셉터는 호출 시점이 분리되어 있음. preHandle에서 생성한 값을 postHandle, afterCompletion에서 사용하려면 request에 담아두어야 함. (싱글톤처럼 사용되기 때문에 멤버 변수에 담아두면 위험함)

request.getAttribute(LOG_ID)로 찾아서 사용

 

인증 체크 인터셉터

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String requestURI = request.getRequestURI();
    log.info("인증 체크 인터셉터 실행 {}", requestURI);
 
    HttpSession session = request.getSession();
    if(session==null || session.getAttribute(SessionConst.LOGIN_MEMBER) ==null){
        log.info("미인증 사용자 요청");
        // 로그인으로 redirect
        response.sendRedirect("/login?redirectURL="+requestURI);
        return false;
    }
    return true;
}
cs

인터셉터 등록

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LogInterceptor())
            .order(1)
            .addPathPatterns("/**")
            .excludePathPatterns("/css/**""/*.ico""/error");
 
    registry.addInterceptor(new LoginCheckInterceptor())
            .order(2)
            .addPathPatterns("/**")
            .excludePathPatterns("/""/members/add""/login""/logout""/css/**""/*.ico");
}
cs

ArgumentResolver 활용

1
2
3
4
@GetMapping("/")
public String homeLoginV3ArgumentResolver(
        @SessionAttribute(name=SessionConst.LOGIN_MEMBER, required = false) Member loginMember,
        Model model) {
cs

1
2
3
4
    @GetMapping("/")
    public String homeLoginV3ArgumentResolver(
            @Login Member loginMember,
            Model model) {
cs

@Login이 붙어있으면 ModelAttribute가 동작하는 것이 아니라 argumentResolver가 동작하도록 변경

 

LoginMemberArgumentResolver

① supportsParameter(): 해당 어노테이션 지원 여부 확인. 지원하면 ArgumentResolver 사용

  - @Login 붙어있는가?

  - 파라미터가 Member 타입인가?

② resolveArgument(): 컨트롤러 호출 직전에 호출되어서 필요한 파라미터 정보 생성. 세션에 있는 로그인 회원 정보인 member 객체 찾아서 반환

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
 
@Slf4j
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
    // 해당 어노테이션 지원 여부 확인
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        log.info("supportsParameter 실행");
        
        // @Login이 붙어있는가?
        boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
 
        // 파라미터가 Member 타입인가?
        boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());
        return hasLoginAnnotation && hasMemberType;
    }
 
    // 세션에 저장된 정보 찾기
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        log.info("resolveArgument 실행");
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        HttpSession session = request.getSession(false);
        if(session==null){
            return null;
        }
        return  session.getAttribute(SessionConst.LOGIN_MEMBER);
    }
}
 
cs

ArgumentResolver 등록

1
2
3
4
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(new LoginMemberArgumentResolver());
}
cs

 

필터보다 인터셉터가 사용하기 편함

ArgumentResolver 사용하면 호출 편리

반응형
반응형

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

 

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

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

www.inflearn.com

쿠키정보

항상 서버에 전송됨

- 네트워크 트래픽 추가 유발

- 최소한의 정보만 사용 (세션 id, 인증 토큰)

- 보안에 민감한 데이터는 저장하면 안됨

 

쿠키 생명주기 (expires, max-age)

세션 쿠키: 만료 날짜 생략 -> 브라우저 종료시 까지만 유지

영속 쿠키: 만료 날짜 입력 -> 해당 날짜까지 유지

 

쿠키 도메인

생성한 쿠키가 아무 사이트에 전부 전송되면 안됨

도메인 명시: 명시한 문서 기준 도메인 + 서브 도메인

도메인 생략: 현재 문서 기준 도메인만 적용

 

쿠키 생성

1
2
Cookie idCookie = new Cookie("memberId"String.valueOf(loginMember.getId()));
response.addCookie(idCookie);
cs

쿠키 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping("/")
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId, Model model) {
    if(memberId == null){
        return "home";
    }
 
    Member loginMember = memberRepository.findById(memberId);
    if(loginMember==null){
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}
cs

쿠키 제거

1
2
3
4
5
6
7
8
9
10
11
@PostMapping("/logout")
public String logout(HttpServletResponse response) {
    expireCookie(response, "memberId");
    return "redirect:/";
}
 
private void expireCookie(HttpServletResponse response, String cookieName) {
    Cookie cookie = new Cookie(cookieName, null);
    cookie.setMaxAge(0);
    response.addCookie(cookie);
}
cs

쿠키 문제점

1. 쿠키 값 임의로 변경 가능

2. 쿠키에 보관된 정보 훔쳐갈 수 있음

3. 해커가 쿠키 한 번 훔쳐가면 평생 사용가능

 

대안 -> 세션으로 해결 가능

1. 쿠키에 중요한 값 노출 x, 예측 불가능한 임의의 토큰을 노출하고

   서버에서 토큰 <-> 사용자 id 맵핑해서 인식. 서버에서 토큰 관리

2. 토큰은 해커가 임의의 값을 넣어도 찾을 수 없도록 예상 불가능 해야 함

3. 토큰 만료 시간을 짧게 설정

 

세션

중요한 정보를 서버에 보관하고 연결을 유지하는 방법

쿠키 문제 해결 -> 중요한 정보를 모두 서버에 저장.

추정 불가능한 임의의 식별자 값으로 클라이언트 <-> 서버 연결

 

세션ID: 추정 불가능해야함 -> UUID는 추정 불가

서버의 세션 저장소에 {세션 ID, 보관할 값(memberA)} 보관

 

회원과 관련된 정보는 전혀 클라이언트에 전달하지 않음

추정 불가능한 세션 ID만 쿠키를 통해 클라이언트에 전달

 

클라이언트: 요청 시 항상 mySessionId 쿠키를 전달

서버: mySessionId 쿠키 정보로 세션 저장소를 조회 -> 로그인 시 보관한 세션 정보 사용

세션 id는 털려도 여기에는 중요한 정보 없음

토큰 털려도 만료시간 짧게 하거나 세션 강제로 제거

 

세션 관리

1. 생성

  ① 세션 id 생성

  ② 세션 저장소에 {세션 id, 보관할 값} 저장

  ③ 세션 id로 응답 쿠키 생성해서 클라이언트에 전달

 

2. 조회

요청한 세션 id 쿠키 값으로 세션 저장소에 보관한 값 조회

 

3. 만료

요청한 세션 id 쿠키 값으로 세션 저장소에 보관한 세션 id, 값 제거

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
/**
 * 세션 생성
 * 1. 세션 id 생성
 * 2. 세션 저장소에 보관할 값 저장
 * 3. 세션 id로 응답 쿠키 생성 -> 클라이언트에게 전달
 */
public void createSession(Object value, HttpServletResponse response) {
    String sessionId = UUID.randomUUID().toString();
    sessionStore.put(sessionId, value);
 
    Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
    response.addCookie(mySessionCookie);
}
 
/**
 * 세션 조회
 */
public Object getSession(HttpServletRequest request) {
    Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
    if(sessionCookie==null){
        return null;
    }
    return sessionStore.get(sessionCookie.getValue());
}
 
/**
 * 세션 만료
 */
public void expire(HttpServletRequest request) {
    Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
    if(sessionCookie != null){
        sessionStore.remove(sessionCookie.getValue());
    }
}
 
public Cookie findCookie(HttpServletRequest request, String cookieName) {
    if(request.getCookies()==null){
        return null;
    }
    return Arrays.stream(request.getCookies())
            .filter(cookie->cookie.getName().equals(cookieName))
            .findFirst()
            .orElse(null);
}
cs

호출

1
2
sessionManager.createSession(loginMember, response);
sessionManager.expire(request);
cs

 

세션 id 임의로 변경 -> getSession()에서 못찾고 null 반환

HttpSesion

로그인

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
    @PostMapping("/login")
    public String loginV4(@Valid @ModelAttribute("loginForm") LoginForm form,
                          BindingResult bindingResult,
                          @RequestParam(defaultValue = "/"String redirectURL,
                          HttpServletRequest request){
        if(bindingResult.hasErrors()){
            return "login/loginForm";
        }
        Member member = new Member();
 
        Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
 
        // 로그인 실패
        if(loginMember==null){
            bindingResult.reject("loginFail""아이디 또는 비밀번호가 맞지 않습니다.");
            return "login/loginForm";
        }
 
        // 로그인 성공
        // 세션 있음 -> 있는 세션 반환
        // 세션 없음 -> 신규 세션 생성
        HttpSession session = request.getSession();
 
        // 세션에 로그인 회원 정보 보관
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
        return "redirect:"+redirectURL;
    }
cs

로그아웃

1
2
3
4
5
6
7
8
9
    @PostMapping("/logout")
    public String logoutV3(HttpServletRequest request) {
        // 세션 없으면 만들면 안됨
        HttpSession session = request.getSession(false);
        if(session != null){
            session.invalidate();
        }
        return "redirect:/";
    }
cs

홈 화면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    @GetMapping("/")
    public String homeLoginV3(HttpServletRequest request, Model model) {
        // 처음 사용자 (로그인 안한) 세션 만들면 안됨
        HttpSession session = request.getSession(false);
 
        if(session==null){
            return "home";
        }
        Member loginMember = (Member)session.getAttribute(SessionConst.LOGIN_MEMBER);
 
        // 세션에 회원 데이터 없으면 home으로
        if(loginMember==null){
            return "home";
        }
 
        // 세션이 유지되면 로그인 한 home으로 이동
        model.addAttribute("member", loginMember);
        return "loginHome";
    }
cs

@SessionAttribute (세션 생성 안함)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    @GetMapping("/")
    public String homeLoginV3Spring(
            @SessionAttribute(name=SessionConst.LOGIN_MEMBER, required = false) Member loginMember,
            Model model) {
 
        // 세션에 회원 데이터 없으면 home으로
        if(loginMember==null){
            return "home";
        }
 
        // 세션이 유지되면 로그인 한 home으로 이동
        model.addAttribute("member", loginMember);
        return "loginHome";
    }
cs

 

세션 타임아웃 설정

세션 삭제: 로그아웃 클릭 시 session.invalidate() 호출됨

but, 대부분의 사용자 -> 로그아웃 안하고 웹 브라우저 종료

http는 비연결성(ConnectionLess)이므로, 서버 입장에서는 해당 사용자가 웹 브라우저를 종료한 것인지 아닌지 알 수 없음.

  -> 서버는 메모리에 계속 저장 중. 메모리 터질 수 있음

  -> 쿠키 탈취당하면 오랜 시간이 지나도 해당 쿠키로 요청 가능

 

세션 종료 시점: 생성 시점 기준 (x), 최근 요청 시간 기준으로 30분 정도 유지

LastAccessedTime 이후로 timeout 시간이 지나면, WAS가 내부에서 해당 세션 제거

 

세션에는 최소한의 데이터만 보관해야 함

보관한 데이터 용량 * 사용자 수로 세션의 메모리 사용량이 급격하게 늘어나서 장애로 이어질 수 있음

세션 시간 너무 길면 메모리 사용 계속 누적됨 (기본 30분)

 

문제점

URL만 알면 로그아웃한 상태라도 상품관리 접근 가능 -> 막아야 함

 

반응형
반응형

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

 

2293번: 동전 1

첫째 줄에 n, k가 주어진다. (1 ≤ n ≤ 100, 1 ≤ k ≤ 10,000) 다음 n개의 줄에는 각각의 동전의 가치가 주어진다. 동전의 가치는 100,000보다 작거나 같은 자연수이다.

www.acmicpc.net

0123

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#define rep(i,n) for(int i=1;i<=n;i++)
using namespace std;
int n, k, arr[101], dp[10001];
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> n >> k;
    rep(i, n) 
        cin >> arr[i];
 
    rep(i, n) {
        int coin = arr[i];
        rep(value, k) {
            if (value == coin)
                dp[value]++;
            if (value < coin) continue;
            dp[value] = dp[value] + dp[value - coin];
        }
    }
    cout << dp[k];
 
}
cs
반응형

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

백준 1937  (0) 2021.07.26
백준 1890  (0) 2021.07.23
백준 1103  (0) 2021.07.08
백준 11054  (0) 2021.02.19
백준 13398 [복습 필수]  (0) 2021.02.18
반응형

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

 

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

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

www.inflearn.com

Bean Validation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
public class Item{
    @NotNull
    private Long id;
 
    @NotBlank
    private String itemName;
 
    @NotNull
    @Range(min = 1000, max = 1000000)
    private Integer price;
 
    // 수정에서는 수량 자유롭게 변경 가능
    private Integer quantity;
 
}
cs

Controller에 @Validated 추가

1
2
3
4
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, 
BindingResult bindingResult,
RedirectAttributes redirectAttributes,
Model model) {
    ...
}
  cs

 

검증 순서

① @ModelAttribute 각각의 필드에 타입 변환 시도

    1. 성공하면 다음으로

    2. 실패하면 typeMismatch 로 FieldError 추가

 

② Validator 적용

    - 바인딩에 성공한 필드만 Bean Validation 적용

    - BeanValidator는 바인딩에 실패한 필드는 BeanValidation을 적용하지 않는다.

 

예)

itemName 에 문자 "A" 입력 타입 변환 성공 itemName 필드에 BeanValidation 적용

price 에 문자 "A" 입력 "A"를 숫자 타입 변환 시도 실패 typeMismatch FieldError 추가

price 필드는 BeanValidation 적용 X

 

에러 코드

어노테이션 이름으로 에러 코드 생성됨

① @NotBlank

NotBlank.item.itemName

NotBlank.itemName

NotBlank.java.lang.String

NotBlank

 

② @Range

Range.item.price

Range.price

Range.java.lang.Integer

Range

 

도메인 객체 vs 별도의 객체

① 폼 데이터 전달에 Item 도메인 객체 사용

HTML Form -> Item -> Controller -> Item -> Repository

  - 장점: Item 도메인 객체를 컨트롤러, 리포지토리 까지 직접 전달해서 중간에 Item을 만드는 과정이

없음

  - 단점: 간단한 경우에만 적용 가능. 수정시 검증이 중복될 수 있고, groups를 사용해야 함

 

② 폼 데이터 전달을 위한 별도의 객체 사용

HTML Form -> ItemSaveFrom -> Controller -> Item 생성 -> Repository

  - 장점: 전송하는 폼 데이터가 복잡해도 거기에 맞춘 별도의 폼 객체를 사용해서 데이터를 전달 받을 수

있음. 보통 등록과, 수정용으로 별도의 폼 객체를 만들기 때문에 검증이 중복되지 않음

  - 단점: 폼 데이터를 기반으로 컨트롤러에서 Item 객체를 생성하는 변환 과정이 추가됨

 

※ Item은 검증 안함. ItemSaveForm과 ItemUpdateForm 검증

 

API 호출 Validation 실패 시 400 bad request 뜨는 이유

-> json request를 ItemSaveForm 객체로 변환하지 못함 (컨트롤러 호출도 안됨)

 

API 호출 3가지 경우

① 성공 요청: 성공

② 실패 요청: JSON을 객체로 생성하는 것 자체가 실패함 (400 bad request)

③ 검증 오류 요청: JSON을 객체로 생성하는 것은 성공했고, 검증에서 실패함

 

 @ModelAttribute vs @RequestBody

① @ModelAttribute: 각각의 필드 단위로 세밀하게 적용. 특정 필드에 타입이 맞지 않는 오류가 발생해도 나머지 필드는 정상 처리 가능 (Validator를 사용한 검증도 적용 가능)

 

② @RequestBody: 전체 객체 단위로 적용. 메시지 컨버터의 작동이 성공해서 Item 객체를 만들어야 @Valid , @Validated 가 적용됨. HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후

단계 자체가 진행되지 않고 예외 발생. 컨트롤러도 호출되지 않고, Validator도 적용 불가.

반응형
반응형

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

 

17143번: 낚시왕

낚시왕이 상어 낚시를 하는 곳은 크기가 R×C인 격자판으로 나타낼 수 있다. 격자판의 각 칸은 (r, c)로 나타낼 수 있다. r은 행, c는 열이고, (R, C)는 아래 그림에서 가장 오른쪽 아래에 있는 칸이다.

www.acmicpc.net

슈도 코드

● 낚시왕이 오른쪽으로 한 칸 이동한다.

rep(pos, c) {

        ① 지면과 가장 가까운 상어 찾기

 

        ② 상어의 다음 위치 계산 (어려움)

            -> 큐에 상어의 다음 위치 삽입: q.push({ nr, nc, ... })          ------------------- ★

            -> 현재 위치에서 제거: arr[row][col] = {0, 0, 0, 0, 0}

 

        ③ 큐에 있는 상어를 하나씩 꺼내서 상어의 새 위치를 배열에 반영

            -> (현재 상어 크기) > (기존에 있는 상어 크기) ? 현재 상어 : 기존 상어

}

 

★ 큐에 상어의 다음 위치를 삽입하는 이유

상어의 다음 위치 계산 한 뒤에 (nr, nc) 상어를 바로 이동시키면 기존 (nr, nc) 위치에 있던 상어의 정보가 덮어씌워져서 계산을 할 수 없다. 이를 방지하기 위해 상어의 새 위치를 계산 즉시 반영시키지 않고 Queue에 저장해놨다가, 모든 상어의 새 위치가 계산된 후에 상어의 새 위치를 반영한다.

 

ex)

A 상어가 (4, 2) -> (4, 3)으로 이동하고, B 상어가 (4, 3)에 위치할 경우

A 상어를 (4, 3)으로 이동시키면 B 상어의 새 위치를 계산하지 못함

 

상어의 다음 위치 계산

C = 5

속도가 0일 때와 8일 때 방향, 위치 모두 동일한 것을 알 수 있다.

(현재 위치) mod 8 일 때 동일 -> (현재 위치) mod (5 - 1) * 2 = (현재 위치) mod (c - 1) * 2

이렇게 하면 방향 전환 하는 경우를 두 번만 고려하면 된다.

 

 

 

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
#include <iostream>
#include <algorithm>
#include <queue>
#define rep(i,n) for(int i=0;i<n;i++)
using namespace std;
struct Node {
    int r;
    int c;
    int speed;
    int direction;
    int size;
};
int r, c, m, ans;
Node arr[101][101];
bool isSharkExist(int i, int j);
void removeShark(int i, int j);
void input();
void catchShark(int i, int j) {
    ans += arr[i][j].size;
    removeShark(i, j);
}
queue<Node> q;
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    input();
 
    // 1. 낚시왕이 오른쪽으로 한 칸 이동한다.
    rep(pos, c) {
        // 2. 낚시왕이 있는 열에 있는 상어 중에서 땅과 제일 가까운 상어를 잡는다.상어를 잡으면 격자판에서 잡은 상어가 사라진다.
        rep(row, r) {
            if (isSharkExist(row, pos)) {
                catchShark(row, pos);
                break;
            }
        }
        // 3. 상어가 이동한다.
        rep(row, r) {
            rep(col, c) {
                if (isSharkExist(row, col)) {
                    Node& now = arr[row][col];
                    int nr = row;
                    int nc = col;
                    int move = now.speed;
 
                    // 상어가 이동하려고 하는 칸이 격자판의 경계를 넘는 경우에는 방향을 반대로 바꿔서 속력을 유지한채로 이동한다.
                    // 1: 상, 2: 하, 3: 우, 4: 좌
                    if (now.direction == 1) {
                        move %= (r - 1* 2;
                        nr = row - move;
                        if (nr < 0) {
                            now.direction = 2;
                            nr *= -1;
                            if (nr >= r) {
                                now.direction = 1;
                                nr = (r - 1* 2 - nr;
                            }
 
                        }
                    }
                    else if (now.direction == 2) {
                        move %= (r - 1* 2;
                        nr = row + move;
                        if (nr >= r) {
                            now.direction = 1;
                            nr = (r - 1* 2 - nr;
                            if (nr < 0) {
                                now.direction = 2;
                                nr *= -1;
                            }
                        }
                    }
                    else if (now.direction == 3) {
                        move %= (c - 1* 2;
                        nc = col + move;
                        if (nc >= c) {
                            now.direction = 4;
                            nc = (c - 1* 2 - nc;
                            if (nc < 0) {
                                now.direction = 3;
                                nc *= -1;
                            }
                        }
                    }
                    else if (now.direction == 4) {
                        move %= (c - 1* 2;
                        nc = col - move;
                        if (nc < 0) {
                            now.direction = 3;
                            nc *= -1;
                            if (nc >= c) {
                                now.direction = 4;
                                nc = (c - 1* 2 - nc;
                            }
 
                        }
                    }
 
                    // 상어가 이동할 좌표를 구하고 난 뒤, 즉시 새 위치로 이동시키면 (arr[nr][nc])
                    // 원래 [nr][nc]에 있는 상어를 덮어쓰게 됨. 따라서, 해당 상어는 이동하지 못함.
                    // 이를 방지하기 위해서 상어의 새 좌표를 큐에 삽입한 뒤, 나중에 한 번에 이동시킴
                    q.push({ nr, nc, now.speed, now.direction, now.size });
 
                    // 이동 완료
                    removeShark(row, col);
                }
            }
        }
        // 크기가 가장 큰 상어가 나머지 상어를 모두 잡아먹는다.
        while (!q.empty()) {
            Node now = q.front(); q.pop();
            arr[now.r][now.c] = (now.size > arr[now.r][now.c].size ? now : arr[now.r][now.c]);
        }
    }
    cout << ans;
}
 
bool isSharkExist(int i, int j) {
    Node now = arr[i][j];
    if (now.direction != 0 && now.size != 0return true;
    return false;
}
void removeShark(int i, int j) {
    Node& now = arr[i][j];
    now.r = 0;
    now.c = 0;
    now.size = 0;
    now.direction = 0;
    now.speed = 0;
}
void input() {
    cin >> r >> c >> m;
    rep(i, m) {
        int a, b, s, d, z;
        cin >> a >> b >> s >> d >> z;
        arr[a - 1][b - 1= { a - 1,b - 1,s,d,z };
    }
}
cs
반응형

'백준 > 삼성기출' 카테고리의 다른 글

백준 17779 [복습 필수]  (0) 2021.08.06
백준 17142  (0) 2021.08.04
백준 5373 [복습필수]  (0) 2021.04.30
백준 16234  (0) 2021.04.30
백준 15683  (0) 2021.04.30
반응형

오늘은 세그먼트 트리와 lazy propagation 문제 위주로 풀었다.

드디어 solved.ac 플레를 찍었다. 세그먼트 트리, lazy propagation 문제들은 난이도가 높아서 한 문제만 풀어도 경험치가 많이 올랐다. 알고리즘 자체가 어려워서 그렇지, 한 번 이해하니까 평소에는 힘들게 풀었던 골드1, 플레5 문제들도 풀 만했다. 하지만 응용 문제는 아직 공부를 많이 해야할 것 같다.

반응형

+ Recent posts