나의 풀이

n = int(input())
dictGrade = {}

for i in range(n):
    name, grade = input().split(' ')
    dictGrade[name] = grade

sDictGrade = sorted(dictGrade, key = lambda x : dictGrade[x])

for key in sDictGrade:
    print(key, end=' ')

 

정답

n = int(input())

array = []
for i in range(n):
    input_data = input().split()
    array.append((input_data[0], int(input_data[1])))
    
array = sorted(array, key=lambda student: student[1])

for student in array:
    print(student[0], end = ' ')

이번 문제같은 경우 정답이 조금 해깔린다.

array = sorted(array, key=lambda student: student[1])

for student in array:
    print(student[0], end = ' ')

위 부분에 lambda 식에 처음 사용된 student가 갑자기 for문을 통해 정렬된 값을 나타내는 것으로 사용된다.

정렬된 값 array를 내버려두고 선언도 안한 student가 사용 되는 것은 혼란스러웠다

'알고리즘' 카테고리의 다른 글

부품 찾기  (0) 2021.12.19
두 배열의 원소 교체  (0) 2021.12.19
숫자 카드 게임 그리디  (0) 2021.12.15
큰 수의 법칙 그리디  (0) 2021.12.14
미로 탈출 - bfs  (0) 2021.12.13

로그인 기능을 사용하기 위해서 oauth2 를 임포트 합니다.

compile('org.springframework.boot:spring-boot-starter-oauth2-client')

깃 이그노어 파일에 아래와같이 인증 키 정보가 담긴 application-oauth 파일을 제거합니다.

인증 키의 노출은 보안에 위험하기 때문입니다.

application-oauth.properties

application.properties에서 oauth 사용을 허락합니다.

spring.profiles.include=oauth

index.mutache에 아래에 해당되는 파일을 추가합니다. 

                {{#userName}}
                    Logged in as: <span id="user">{{userName}}</span>
                    <a href="/logout" class="btn btn-info active" role="button">Logout</a>
                {{/userName}}
                {{^userName}}
                    <a href="/oauth2/authorization/google"
                       class="btn btn-success active" role="button">Google Login</a>
                {{/userName}}

{{}} 문법은 머스테치 문법입니다. 컨트롤러에서 index.mustache 파일을 호출할때 userName 정보를 index.mustache파일에 넘겨주게 되면 로그인한 유저 정보와 logout 버튼을 나타내고, userName이 넘어오지않게 된다면 {{^userName}}이 실행되게 됩니다. ^표식이 들어가면 없을때 안의 값을 반환합니다.

 

 

package com.bell_bell.book.springboot.config.auth;

import com.bell_bell.book.springboot.config.auth.dto.OAuthAttributes;
import com.bell_bell.book.springboot.config.auth.dto.SessionUser;
import com.bell_bell.book.springboot.domain.user.User;
import com.bell_bell.book.springboot.domain.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import java.util.Collections;

@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final UserRepository userRepository;
    private final HttpSession httpSession;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
                .getUserInfoEndpoint().getUserNameAttributeName();

        OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());
        User user = saveOrUpdate(attributes);
        httpSession.setAttribute("user", new SessionUser(user));

        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
                attributes.getAttributes(),
                attributes.getNameAttributeKey());

    }


    private User saveOrUpdate(OAuthAttributes attributes) {
        User user = userRepository.findByEmail(attributes.getEmail())
                .map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
                .orElse(attributes.toEntity());

        return userRepository.save(user);
    }
}

위 클래스 CustomOAuth2UserService에는 loadUser와 saveOrUpdate 메소드가 존재합니다.

loadUser 메소드는 구글 로그인 이후 호출되게 됩니다. 해당 클래스는SecurityConfig 에서 호출합니다. loadUser가 호출되면 loadUser 내부에는 saveOrUpdate가 실행되게 됩니다. userRequest에는 유저 이름과 아이디에 관한 정보가 포함되 있는 것으로 보이며 해당 정보를 saveOrUpdate 함수에게 전달합니다. saveOrUpdate 는 userRepository에게 해당 유저가 존재하는지 여부를 물어보고 존재 유무는 해당 유저의 이메일로 유저를 찾게 됩니다. orElse의 기능으로 인해서 해당 유저가 없다면 attributes.toEntity() 호출로 인해서 유저 클래스에 값을 넘기게 되고 해당 유저는 save 되는 것으로 보입니다. 

 

그냥 간단하게 요약하자면 구글 로그인이 진행되면 해당 클래스에 있는 메소드를 통해서 httpSession에 유저 정보를 저장하고 User table에 유저 정보를 저장하는 거로 보면 됩니다.

 

import com.bell_bell.book.springboot.domain.user.Role;
import com.bell_bell.book.springboot.domain.user.User;
import lombok.Builder;
import lombok.Getter;

import java.util.Map;

@Getter
public class OAuthAttributes {
    private Map<String, Object> attributes;
    private String nameAttributeKey;
    private String name;
    private String email;
    private String picture;

    @Builder
    public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name,
                           String email, String picture) {
        this.attributes = attributes;
        this.nameAttributeKey = nameAttributeKey;
        this.name = name;
        this.email = email;
        this.picture = picture;
    }

    public static OAuthAttributes of(String registrationId, String userNameAttributeName,
                                     Map<String, Object> attributes) {
        return ofGoogle(userNameAttributeName, attributes);
    }

    private static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String, Object> attributes) {
        return OAuthAttributes.builder()
                .name((String) attributes.get("name"))
                .email((String) attributes.get("email"))
                .picture((String) attributes.get("Picture"))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    public User toEntity() {
        return User.builder()
                .name(name)
                .email(email)
                .picture(picture)
                .role(Role.GUEST)
                .build();
    }
}

위 클래스는 dto에 속하는 클래스로 각 계증간 유저 정보를 주고 받을 때 사용됩니다.

toEntity를 자세히 보면 .role(Role.GUEST)로 되어있습니다. 이는 가입할 때 기본 권한을 GUEST로 주기 위해서 role 빌더 값에는 Role.GEUSET를 사용합니다.

 

package com.bell_bell.book.springboot.config.auth.dto;

import com.bell_bell.book.springboot.domain.user.User;
import lombok.Getter;

import java.io.Serializable;

@Getter
public class SessionUser implements Serializable {
    private String name;
    private String email;
    private String picture;

    public SessionUser(User user) {
        this.name = user.getName();
        this.email = user.getEmail();
        this.picture = user.getPicture();
    }
}

위 클래스가 사용되는 곳을 살펴보겠습니다.

        httpSession.setAttribute("user", new SessionUser(user));

위와 같이 사용되고 있고 httpSession에 유저 정보를 저장 시키게 하기 위해서 사용 되는 것으로 보입니다.

 

httpSession에 유저 정보를 저장할 때 왜 User 클래스 대신 SessionUser를 사용하는 걸까요? httpSession에 User를 담기위해서는 Serialize(직렬화)를 사용해야 되는데 User를 직렬화 하게 되면 나중에 자식 엔티티가 생겨서 관계를 가지게 되면 직렬화 대상에 자식까지 포함되어서 성능 이슈, 부수 효가가 발생할 수 있기 때문입니다. 

 

다음으로 아래 클래스 정보를 알아보겠습니다.

package com.bell_bell.book.springboot.config.auth;

import com.bell_bell.book.springboot.domain.user.Role;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomOAuth2UserService customOAuth2UserService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .headers().frameOptions().disable()
                .and()
                    .authorizeRequests()
                    .antMatchers("/", "/css/**", "/images/**",
                            "/js/**", "/h2-console/**").permitAll()
                    .antMatchers("/api/v1/**").hasRole(Role.USER.name())
                    .anyRequest().authenticated()
                .and()
                    .logout()
                        .logoutSuccessUrl("/")
                .and()
                    .oauth2Login()
                        .userInfoEndpoint()
                            .userService(customOAuth2UserService);
    }
}

1. @EnableWebSecurity

 - Spring Security 설정들을 활성화시켜 줍니다.

 

2. csrf().disable().headers().frameOptions().disable()

- h2-console 화면을 사용하기 위해 해당 옵션들을 disable 합니다. 

 

3. authorizeRequests

- URL별 권한 관리를 설정하는 옵션의 시작지점입니다. 

- authorizeRequests가 선언되어야만 antMatchers 옵션을 사용할 수 있습니다. 

 

4. andMachers

- url을 지정하고 해당되는 url에게 전체 열람부터 특정 권한을 할당합니다. 

- 권한 관리 대상을 지정하는 옵션입니다.

- URL, HTTP 메소드별로 관리가 가능합니다.

- "/"등 지정된 URL들은 permitAll()옵션을 통해 전체 열람 권한을 주었습니다.

- "/api/v1/**" 주소를 가진 api는 USER 권한을 가진 사람만 가능하도록 했습니다.

 

5. anyRequest

- 설정된 값들 이외 나머지 URL들을 나타냅니다. 

- 여기서는 authenticated()을 추가하여 나머지 URL들은 모두 인증된 사용자들에게 만 허용하게 합니다.

- 인증된 사용자 즉, 로그인한 사용자들을 이야기합니다.

 

6. logout().logoutSuccessUrl("/")

 - 로그아웃 기능에 대한 여러 설정의 진입점입니다.

 - 로그아웃 성공 시 / 주소로 이동합니다.

 

7. oauth2Login

 - OAuth 2 로그인 기능에 대한 여러 설정의 진입점입니다. 

 

8. userInfoEndpoint

 - OAuth2  로그인 성공 이후 사용자 정보를 가져올 때의 설정들을 담당합니다. 

 

9. userService

 - 소셜 로그인 성공 시 후속 조치를 진행할 UserService 인터페이스의 구현체를 등록합니다. 리소스 서버(즉, 소셜 서비스들)에서 사용자 정보를 가져온 상태에서 추가로 진행하고자 하는 기능을 명시할 수 있습니다. 

시작화면

위 화면은 시작화면입니다. 

 

 

글 등록을 클릭하면 게시글 등록 화면이 생성됩니다. 

글을 성공적으로 등록하면 다시 메인 화면으로 이동합니다. 메인 화면에서 등록한 값의 테이블을 확인할 수 있습니다.

제목을 보면 글자가 파란색인데 링크입니다. 클릭하면 수정 화면으로 이동합니다.

 

 

 

수정 화면에서는 글 번호와 작성자를 수정할 수 없습니다. 

 

delete 버튼을 클릭해보겠습니다.

 

delete가 성공하면 alert기능이 수행되고 article is deleted!! 글이 나타납니다. 

 

글이 삭제된 것을 확인할 수 있습니다. 

 

이번엔 수정 기능을 시험 해보겠습니다. 수정 기능을 시험해보기 위해서 글을 등록해보곘습니다.

 

 

 

 

 

아래 화면을 통해 수정이 된 것을 확인할 수 있습니다.

'Today I Learned' 카테고리의 다른 글

orm, ibatis, Spring Data JPA  (0) 2022.01.31
gitignore 자동 생성 및 인텔리제이에서 gitignore 생성  (0) 2021.12.24
kendogrid 한번에 update, create 하기  (0) 2021.12.16
KendoGrid empty  (0) 2021.12.15
kendogrid batch  (0) 2021.12.14

위와같이 readOnly가 빨강색으로 나타났다. 

 

그 이유는

import javax.transaction.Transactional;

javax의 Transactional를 사용해서라고한다. javax는 readOnly 기능을 지원하지 않는 것 같다.

위와같이 javax말고 org.springframework.transaction.annotation을 선택하자

KendoGrid는 행을 추가하거나 행을 변경하면 행의 개수만큼 update, create 함수가 실행된다. 하지만 update나 create를 많이 해야 하는 상황이 올 수록 update와 create가 호출될 때마다 서버에 요청을 보낸다면 한 번에 다량의 요청을 보내게 된다. 이를 해결하기 위해서 데이터를 한 번에 보내는 방법을 생각해보았다. 

 

한 가지 방법으로 전역 변수를 생성하고 update와 create을 호출할 때마다 호출했던 AJAX를 빼고 JSON 형식으로 update와 create에서 받는 데이터를 전역 변수로 할당한다. 그리고 save 버튼을 클릭하면 update와 create 함수가 호출되는데 saveChange기능을 호출한 다음 AJAX를 사용하여 서버에 호출하면 된다. 

 

나의 풀이

n,m = map(int, input().split())
mins = list()

for i in range(n):
    min = 10001
    nums = list(map(int, input().split(' ')))
    for j in range(m):
        if min > nums[j]:
            min = nums[j]
    if min < 10001:
        mins.append(min)
print(max(mins))

답안

n, m = map(int, input().split())

result = 0
for i in range(n):
    data = list(map(int, input().split()))
    min_value = min(data)
    result = max(result, min_value)
print(result)

해당 문제 풀이를 통해서 max와 min 사용법을 다시 익힐 수 있었고 해당 문제는 그리디 문제중 하 정도 되는 문제이다. 하급이라도 아직은 배울게 많은거 같다.

현재 kendoGrid를 통해서 reorder 기능을 사용하고 있습니다. 테이블을 reorderable 하게 변경하여 열의 순서를 변경할 수 있습니다. 하지만 순서를 변경한 다음 조회 버튼을 클릭하면 값들은 월래 순서를 찾아가지만 위 속성의 이름을 가르키는 곳은 그대로 있는 문제가 발생합니다. 해당 문제를 해결하기 위해서 empty 기능을 사용했습니다.

 

empty 기능을 해당 테이블을 clear 해주는 기능이 있습니다.

 

kendoGrid에 empty 기능을 사용하기 전 상태입니다. 조회 버튼을 클릭하면 아래 컬럼들은 변경되지만 이름은 변경되지 않는 문제가 있습니다.

kendoGrid에 empty를 사용한 후입니다. 조회 버튼을 클릭하면 컬럼의 순서를 변경하였어도 다시 월래대로 돌아오는 것을 확인할 수 있습니다.

 

empty는 아래와 같은 방식으로 사용할 수 있습니다.

$("#GridMain").empty();

 

n, m, k = map(int, input().split())
nums = list(map(int, input().split()))
result = 0
nums.sort(reverse = True)

for i in range(2,m+2):
    if (k+1)%i != 0 :
        result += nums[0]
    else :
        result += nums[1]

print(result)

위와 같은 방식으로 문제를 해결하였다. 

range의 시작값과 끝 값을 각각 2를 더해준 이유는 0,1은 값이 나누어 떨어져서 시작부터 작은값을 넣고 시작해서 값에 오차가 발생해버린다. 이제 정답을 보겠다.

n, m, k = map(int, input().split())
data = list(map(int, input().split()))

data.sort()
first = data[n - 1]
second = data[n - 2]

result = 0

while True:
    for i in range(k):
        if m == 0:
            break;
        result += first
        m -= 1
    if m == 0:
        break
    result += second
    m -= 1

위와같이 효율적으로 처리하는 것이 정답이다.

k번 for문을 통해 최고의 값만 더하고 한번은 2번째 값을 더하는 것이 핵심 원리이다.

kendogrid batch의 값을 true로 설정하면 update나 create 함수가 변화한 행의 개수만 큼 실행되던 것이 한번만 실행되고 받는 데이터는 배열 형식으로 받는 것을 확인하였다.

 

그래서 data에 접근할 때 for문을 돌리도록 변경하였다.

                create: function (e) {

                    var GridKey = "GridMain";
                    var RowStatus = "INSERT";

                    companyid = "<%=Session["LOGINCOMPANYID"]%>";
                    divisionid = "<%=Session["LOGINDIVISIONID"]%>";
                    createuserid = "<%=Session["LOGINUSERID"]%>";
                    modifyuserid = "<%=Session["LOGINUSERID"]%>";

                    for (let i = 0; i < e.data.models.length; i++) {
                        let item = e.data.models[i];
                        Main_param += "{ RowStatus: '" + RowStatus + "'";
                        Main_param += ", GROUPID: '" + item.GROUPID + "'";
                        Main_param += ", GROUPNAME: '" + item.GROUPNAME + "'";
                        Main_param += ", AUTHSEQUENCE: '" + item.AUTHSEQUENCE + "'";
                        Main_param += ", REMARK: '" + item.REMARK + "'";
                        Main_param += ", COMPANYID: '" + companyid + "'";
                        Main_param += ", DIVISIONID: '" + divisionid + "'";
                        Main_param += ", ISVALID: '" + item.ISVALID + "'";
                        Main_param += ", ISUSE: '" + item.ISUSE + "'";
                        Main_param += ", CREATEUSERID: '" + createuserid + "'";
                        Main_param += ", CREATETIME: 'CREATETIME'";
                        Main_param += ", MODIFYUSERID: '" + modifyuserid + "'";
                        Main_param += ", MODIFYTIME: 'MODIFYTIME'";
                        Main_param += ", LASTHISTORYKEY: 'LASTHISTORYKEY'";
                        Main_param += "},";
                    }    
                    e.success();
                }

'Today I Learned' 카테고리의 다른 글

kendogrid 한번에 update, create 하기  (0) 2021.12.16
KendoGrid empty  (0) 2021.12.15
음료수 얼려먹기 나의 답안  (0) 2021.12.09
kendogrid-isNew()  (0) 2021.12.09
이것이 코딩테스트이다 - 게임 개발  (0) 2021.12.08

해당 내용은 스프링 부트와 AWS로 혼자 구현하는 웹서비스 책 내용을 복습한 내용입니다.

 

API를 만들기 위해 총 3개의 클래스가 필요합니다.

* Request 데이터를 받을 Dto

* API 요청을 받을 Controller

* 트랜잭션, 도메인 기능 간의 순서를 보장하는 Service

tip ) service는 비지니스 로직을 처리하지 않습니다. 그 역할은 Domain이 실행합니다.

 

* Web Layer

  * 흔히 사용하는 컨트롤러(@Controller)와 JSP/Freemaker 등의 뷰 템플릿 영역입니다.

  * 이외에도 필터(@Filter), 인터셉터, 컨트롤러 어드바이스(@ControllerAdvice) 등 외부 요청과 응답에 대한 전반적인 영역을 이야기합니다.

 

* Service Layer

  * @Service에 사용되는 서비스 영역입니다.

  * 일반적으로 Controller와 Dao의 중간 영역에서 사용됩니다.

  * @Transactional이 사용되어야 하는 영역이기도 합니다.

 

* Repository Layer

  * Database와 같이 데이터 저장소에 접근하는 영역입니다.

  * 기존에 개발하셨던 분들이라면 Dao(Data Access Object) 영역으로 이해하시면 쉬울것입니다.

 

* Dtos

  * Dto(Data Transfer Object)는 계층간 데이터를 교환하기 위한 객체를 이야기하며 Dtos는 이들의 영역을 얘기합니다.

  * 예를 들어 뷰 템플릿 엔진에서 사용될 객체나 Repository Layer에서 결과로 넘겨준 객체 등이 이들을 이야기합니다.

 

* Domain Model

  * 도메인이라 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고 공유할 수 있도록 단순화시킨 것을 도      메인 모델이라고 합니다.

 

  * 이를테면 택시 앱이라고 하면 배차, 탑승, 요금 등이 모두 도메인이 될 수 있습니다.

  * @Entity를 사용해보신 분들은 @Entity가 사용된 영역 역시 도메인 모델이라고 이해 해주시면 됩니다.

  * 다만, 무조건 데이터베이스의 테이블과 관계가 있어야만 하는 것은 아닙니다.

  * VO처럼 값 객체들도 이 영역에 해당하기 떄문입니다.

 

 

 

'스프링' 카테고리의 다른 글

@Entity  (0) 2022.02.01
spring boot 에서 google login을 사용해보았다.  (0) 2021.12.18
스프링 서버 단에서 데이터 처리하는 방식  (0) 2021.11.27
Auditing @CreatedDate @LastModifiedDate  (0) 2021.11.27
h2 console  (0) 2021.11.27

+ Recent posts