출처: https://bumcrush.tistory.com/182 [맑음때때로 여름]

회사에서 소소하게 프로젝트를 시작했는데
기존에 원래 되어있던게 스프링도 아닌 정말 기본 자바 프로젝트;;였다
로그인할땐 특정 uuid를 생성해서 인증key같은 걸로 사용하다가
세션이 종료 되었을때 사라지게 해달라고 요청받음..
방법을 찾다보니 HttpSessionListener를 쓰면 된다고..

 

1. web.xml에 추가하기

 

    <listener>
        <listener-class>egovframework.dev3.common.web.SessionListener</listener-class>
    </listener>

ㅎ.. sessiontimeout은 그냥 테스트하느라 1로 했음

    <session-config>
    		<session-timeout>1</session-timeout>
	</session-config>

 

2. 맨들기

public class SessionListener implements HttpSessionListener {

	@Override
    public void sessionCreated(final HttpSessionEvent event) {

		LocalTime now = LocalTime.now();
	    HttpSession session = event.getSession();
	    System.out.println(session.getId());
	    System.out.println("세션 IN   :   " + now);

    }


	@Override
	public void sessionDestroyed(HttpSessionEvent event) {
		LocalTime now = LocalTime.now();
	    HttpSession session = event.getSession();
	    System.out.println("세션 OUT   :   " + now);
	  }
}

 

 

3. 결과

 

그렇다고 한다.

 

https://stackoverflow.com/questions/47166425/how-to-force-javamailsenderimpl-to-use-tls1-2

 

How to force JavaMailSenderImpl to use TLS1.2?

Have a JDK7 app running on Tomcat and it does have the following env settings: -Dhttps.protocols=TLSv1.1,TLSv1.2 The above setting ensures that we don't use TLS 1.0 when connecting over HTTPS wh...

stackoverflow.com

<dependency>
        <groupId>javax.mail</groupId>
        <artifactId>mail</artifactId>
        <version>1.5.0-b01</version>
</dependency>
Properties prop = new Properties();
prop.setProperty("mail.smtp.auth", "true");
prop.setProperty("mail.smtp.starttls.enable", "true");
prop.setProperty("mail.smtp.ssl.protocols", "TLSv1.2"); // Added this line
prop.setProperty("mail.smtp.ssl.trust", mailUri.getHost());
mailSender.setJavaMailProperties(prop);
        List<Output> rst = new ArrayList<>();

        Gson gson = new GsonBuilder()
                .serializeNulls()
                .create();
        String json = gson.toJson(rst);
        System.out.println(json);

 

 

null값이 전송되지 않고 값이 있는 파라미터만 전송될때..?

사실 null값을 전송하지 말자! 하는

@JsonInclude(value = Include.NON.NULL)

어노테이션을 더 많이 사용하고 있어서

새롭게 찾았는데 안까먹으려고 메모~~~~

const requestIp = require('request-ip');
$ npm install request-ip
let Ip = requestIp.getClientIp(req)

 

- 끗 -

 

----------- 더 간단하게는 

const ip = req.headers['x-forwarded-for'] ||  req.connection.remoteAddress;

그리고 만약 로컬에서 ::1로 사용자 IP가 출력되는 경우가 있다. ::1은 IpV6에서 로컬 호스트를 의미한다. 그래서 express 서버를 생성할 때 IPV4를 사용하도록 설정하면 된다.

 


 ex) localhost는 ::1로 들어오고 다른건 ::fff:000.00.00.0. 이런식..

 

if (params.dlIp.substr(0, 7) == "::ffff:") {
    params.dlIp = params.dlIp.substr(7)
}

 

 

 

 

뭔가 이렇게 바로 다운로드 시키고 싶었음

router.route('/sdk/download').get(async (req, res)=> {
    logger.info('request => [get] /sdk/download');
    
    // 필요한 작업 솰라솰라하고나서   
    
    // 다운로드
    let filepath = '파일경로';
    res.download(filepath)
});

 

그냥 이게 끝이었다 ^_^

 

 

라고 생각했는데 뭔가 구리게 파일이 떨어짐..

파일명 같은게 32ru3ifeiosfhsiodf923이런식으로;

 

const path = require('path');
 let filepath = '파일경로 + 파일명' // ex) /dsdfdsf/dfdfd/dfdf/dd.zip
 res.download(filePath, encodeURIComponent(path.basename(filePath)));

 

이렇게 해주면 파일명도 깰끔~~

'nodejs' 카테고리의 다른 글

node ip 클라이언트 ip 가져오기  (0) 2022.02.14
NODE 에서 파일 저장하기 :: FS 이용  (2) 2022.02.07
[nodejs] 데이터 받기 body-parser  (0) 2021.02.24
[nodejs] nodejs / expressjs 설치  (0) 2021.02.24

스프링에서 파일 저장할때는 그냥 multipart 를 이용해서 슝슝 저장하면 됐는데

노드의 파일 시스템은 fs... 예.. 가져와서 쓴다.

const fs = require('fs');

 

 

파일은 BASE64 STRING으로 받는다~

버퍼를 이용해서 파일 저장..

 

 

저장해따

let file = fs.readFileSync('./message.js', 'utf-8'); //message.js 파일 읽기
let encode = Buffer.from(file).toString('base64'); //파일 base64로 인코딩
let mk = fs.writeFileSync('./encodeFile', encode); // 인코딩된 파일 만들기
let file2 = fs.readFileSync('./encodeFile', 'utf-8'); // 인코딩된 파일 읽기
let decode = Buffer.from(file2, 'base64').toString('utf-8'); //파일 디코딩
let mk2 = fs.writeFileSync('./decodeFile.js', decode); //디코딩된 파일 만들기

 

구글 OAuth

https://console.cloud.google.com/

 

Google Cloud Platform

하나의 계정으로 모든 Google 서비스를 Google Cloud Platform을 사용하려면 로그인하세요.

accounts.google.com

 

1. 새 프로젝트 생성

2. 사용자 동의화면 필요한 정보 입력

3. OAuth 클라리언트 ID 만들기

3-1 웹에서 사용할 거니까 웹 애플리케이션으로 해줌

리다이렉션 URI는 라이브러리를 사용하면 따로 컨트롤러에서 mapping을 해주지 않아도

/login/oauth2/code/google로 고정이댜~ 라이브러리가 알아서 처리를 해준다!

완료하면 클라이언트 ID와 비밀번호가 뜨는데 노출되지 않도록 하자~

 

 

OAuth2.0 라이브러리 사용을 위해 의존성 추가추가!

 

https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-clien0

<!--OAUTH-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

application.yml 파일도 수정

 

/oauth2/authorization/google 주소 역시 /login/oauth2/code/google와 같이 이 주소를 써야한다.

하지만 이거만 쓰면 냅다 404가 떠버린다

 

security config 파일에 Oauth로그인페이지 설정을 해주어야한다. 구럼 잘뜸!

.and()
.oauth2Login()
.loginPage("/loginForm"); // oauth로그인페이지

 

로그인은 잘 되지만 권한이 없는 manager 페이지 같은 경우에는 403이뜬다.

이 경우에는 구글 로그인이 완료된 뒤의 후처리가 필요하다.

[ webSecurityConfigurerAdapter를 상속한 sercurityConfig ]

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) 추가

// secured 어노테이션 활성화 & preAuthorize/PostAuthorize 어노테이션 활성화

package com.cos.security1.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity // 활성화 - 스프링 시큐리티 필터가 스프링 필터체인에 등록이 된다
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // secured 어노테이션 활성화 & preAuthorize/PostAuthorize 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable(); // /login으로 갈때 스프링 시큐리티가 낚아채지 않도록

        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()// 다른주소는 OK
                .and()
                .formLogin()
                .loginPage("/loginForm") // 로그인안햇을때 403페이지가 뜨는게 아니라 login페이지를 뜨게만들어줌
                .loginProcessingUrl("/login") //login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행
                .defaultSuccessUrl("/"); // 로그인이 완료되면 이동할 주소


    }

    @Bean // 해당 메서드의 리턴되는 오브젝트를 ioc로 등록
    public BCryptPasswordEncoder encodePwd(){
        // 비밀번호 암호화를 위해
        return new BCryptPasswordEncoder();
    }
}

[ Contoller ]

postAuthorize는 잘 사용하지 않는다.

실행시점?의 차이. 

@Secured("ROLE_ADMIN")
@GetMapping("/info")
public @ResponseBody String info(){
    return "개인정보";
}

@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
// @PostAuthorize()
@GetMapping("/data")
public @ResponseBody String data(){
    return "데이터정보";
}

로그인폼과 회원가입폼 HTML

더보기

로그인폼과 회원가입폼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>로그인 페이지</title>
</head>
<body>
    <h1>로그인 페이지</h1>
<hr>
 <form action="/login" method="POST">
     <input type="text" name="username" placeholder="UserName"/><br/>
     <input type="password" name="password" placeholder="Password"/><br/>
     <button>로그인</button>
 </form>

<a href = "/joinForm">회원가입을 아직 않으셨나요?</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>회원가입 페이지</title>
</head>
<body>
<h1>회원가입 페이지</h1>
<form action="/join" method="POST">
    <input type="text" name="username" placeholder="UserName"/><br/>
    <input type="password" name="password" placeholder="Password"/><br/>
    <input type="email" name="email" placeholder="Email"/><br/>
    <button>회원가입</button>
</form>

</body>
</html>

 

[ 회원가입 Contoller ]

  @PostMapping("/join")
    public String join(User user){

        System.out.println(user);


        // 2. 패스워드 암호화를 추가해줌 securityConfig에도 BcryptpasswordEncoder를 @Bean으로 등록
        String rawPassword = user.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);
        user.setPassword(encPassword);

        
        // 1. userRepository.save(user); // 회원가입 잘됨. 비밀번호 : 1234 => 시큐리티로 로그인을 할수 없음,
        // :: 패스워드가 암호화가 안되었기 때문
        user.setRole("ROLE_USER");
        userRepository.save(user);


        return "redirect:/loginForm";
    }

[ USER MODEL ]

package com.cos.security1.model;

import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.sql.Timestamp;

@Entity
@Data
public class User {

    @Id // primary Key
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private  int id;
    private String username;
    private String password;
    private String email;
    private String role; // ROLE_USEr, ROLE_ADMIN
    @CreationTimestamp
    private Timestamp createdate;

}

 

[ USER REPORSITORY - JPA ]

package com.cos.security1.repository;

import com.cos.security1.model.User;
import org.springframework.data.jpa.repository.JpaRepository;


// CRUD 함수를 JPArepository가 들고 있음
// @Repository라는 어노테이션이 없어도 Ioc가 됨 (자동 빈등록)
// -> JpaRepository를 상속했기 때문
public interface UserRepository extends JpaRepository<User, Integer> {


    // findBy규칙 -> username 문법
    // seelct * from user where username = #{username}
    public User findByUsername(String username);
}

 

[ config 수정 ]

밑 두줄 추가.

 


public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable(); // /login으로 갈때 스프링 시큐리티가 낚아채지 않도록

        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()// 다른주소는 OK
                .and()
                .formLogin()
                .loginPage("/loginForm") // 로그인안햇을때 403페이지가 뜨는게 아니라 login페이지를 뜨게만들어줌
                .loginProcessingUrl("/login") //login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행
                .defaultSuccessUrl("/"); // 로그인이 완료되면 이동할 주소


    }

 

 

[ userDetails를 상속하는 클래스 생성 ]

유저정보를 저장.

package com.cos.security1.config.auth;

// 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킴
// 로그인 진행이 완료되면 seisson을 만들어줌 (Security ContextHolder)
// 세션에 들어갈 수 있는 정보(오브젝트) > authentication 타입의 객체
// authentication 안에 User정보가 있어야 함.
// user 오브젝트 타입 > UserDetails 타입 객체

// Security Session => Authentication => UserDetails(PrincipalDetails)

import com.cos.security1.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

public class PrincipalDetails implements UserDetails {

    private User user; // 콤포지션

    public PrincipalDetails(User user){
        this.user = user;
    }


    // 해당 user의 권한을 리턴하는 곳
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getRole();
            }
        });
        return collect;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }


    // 계정 만료
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 계정 잠김
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 계정 유효기간
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // 계정 정지
    @Override
    public boolean isEnabled() {

        // ex) 휴면계정
        // 현재시간 - 로그인시간 => 1년초과하면 return false

        return true;
    }
}

 

[ userDetailsService를 상속한 클래스 만들기 ]

이름 그대로 유저의 정보를 저장해주는 서비스

// 시큐리티 설정에서 loginProcessingUrl("/login")
// /login 요청이 오면 자동으로 UserDetailsService 타입으로 ioc되어있는 loadUserByUsername가 함수 실행
package com.cos.security1.config.auth;

import com.cos.security1.model.User;
import com.cos.security1.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;


// 시큐리티 설정에서 loginProcessingUrl("/login")
// /login 요청이 오면 자동으로 UserDetailsService 타입으로 ioc되어있는 loadUserByUsername가 함수 실행

@Service
public class PrincaipalDetailsService implements UserDetailsService{


    @Autowired
    private UserRepository userRepository;

    // 시큐리티 session > authentication > userDetails(principalDetails)
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // html에 꼭 "username"으로 해야함 (String username)과 매칭이 안됨
        // 바꾸고 싶다면 security config에 .usernameParameter("username2")
        // 그냥 username을 쓰자 ^^

        User userEntity = userRepository.findByUsername(username);
        if(userEntity != null){
            return new PrincipalDetails(userEntity);
        }

        return null;
    }
}

[ userRepository 에는 유저의 정보를 조회하는 부분 추가 ]

    // findBy규칙 -> username 문법
    // seelct * from user where username = #{username}
    public User findByUsername(String username);

 

spring-boot-starter-security 의존성 주입 

 

 

[ controller ]

package com.cos.security1.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class indexController {

    @GetMapping({"","/"})
    public String index(){
        // mustache 머스태치
        // 머스태치 기본폴더 src/main/resources/
        // viewResolver 설정 : templates (prefix), mustache (suffix)
        return "index"; //  src/main/resources/templates/index.mustache
    }

    @GetMapping("/user")
    public  @ResponseBody String user(){
        return "user";
    }

    @GetMapping("/admin")
    public  @ResponseBody String admin(){
        return "admin";
    }

    @GetMapping("/manager")
    public  @ResponseBody String manager(){
        return "manager";
    }

    // 스프링시큐리티가 해당주소를 낚아채버림
    // SecurityConfig 파일에서 설정으로 낚아채지 않도록 !
    @GetMapping("/login")
    public  @ResponseBody String login(){
        return "login";
    }

    @GetMapping("/join")
    public  @ResponseBody String join(){
        return "join";
    }

    @GetMapping("/joinProc")
    public @ResponseBody String joinProc(){
        return "회원가입 완료됨!";
    }
}

 

[ config ] 

package com.cos.security1.config;


import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableWebSecurity // 활성화 - 스프링 시큐리티 필터가 스프링 필터체인에 등록이 된다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable(); // /login으로 갈때 스프링 시큐리티가 낚아채지 않도록

        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()// 다른주소는 OK
                .and()
                .formLogin()
                .loginPage("/login"); // 권한이 없다면 403페이지가 뜨는게 아니라 login페이지를 뜨게만들어줌

    }
}

 

[ webMvcConfiguer ]

- 머스태치 설정

- view 주소 설정

package com.cos.security1.config;

import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig  implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        MustacheViewResolver resolver = new MustacheViewResolver();
        resolver.setCharset("UTF-8");
        resolver.setContentType("text/html; charset=UTF-8");
        resolver.setPrefix("classpath:/templates/");
        resolver.setSuffix(".html"); // .mustache가 아닌 .html로 바꿔주기 위한 작업

        registry.viewResolver(resolver); // viewResolver 등록
    }
}

https://developers.facebook.com/tools/debug/

 

공유 디버거 - Facebook for Developers

공유 디버거를 사용하면 Facebook에 공유될 때 콘텐츠가 표시되는 모습을 미리 보거나 오픈 그래프 태그를 사용하여 문제를 디버깅할 수 있습니다. 이 도구를 사용하려면 Facebook에 로그인하세요.

developers.facebook.com

 

 

https://developers.kakao.com/tool/clear/og

 

카카오계정 로그인

여기를 눌러 링크를 확인하세요.

accounts.kakao.com

 

여기 들어가서 주소쓰고 삭제해줘야됌

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=cab618&logNo=221056317474 

 

Chart.js - 차트

오픈 소스로 많이 쓰이는 Chart.js 입니다. 상용 차트가 많은 기능과 UI를 지원하고 있지만 여건상 쓰지...

blog.naver.com

<script type="text/javascript" src="chart.bundle.js"></script>
<div id="container" style="border: solid 1px black; width: 100%; 
height: 300px; margin-bottom: 10px;">
        <canvas id="canvas" style="margin-left: 5px;"></canvas>
    </div>

    <script type="text/javascript">
        var ChartHelper = {
            chartColors: {
                red: 'rgb(255, 99, 132)'
                , orange: 'rgb(255, 159, 64)'
                , yellow: 'rgb(255, 205, 86)'
                , green: 'rgb(75, 192, 192)'
                , blue: 'rgb(54, 162, 235)'
                , purple: 'rgb(153, 102, 255)'
                , grey: 'rgb(201, 203, 207)'
            }
        };

        var color = Chart.helpers.color;

        var data1 = null;
        var data2 = null;
        var barChartData = null;

        // todo: data setting
        data1 = ['10', '20', '30', '40', '50', '60', '70', '80'
, '90', '100', '110', '120'];
        data2 = ['120', '110', '100', '90', '80', '70', '60', '50'
, '40', '30', '20', '10'];

        barChartData = {
            labels: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월'
, '9월', '10월', '11월', '12월']
            , datasets: [
                {
                    label: 'DataSet1'
                    , backgroundColor: 
color(ChartHelper.chartColors.blue).alpha(0.5).rgbString()
                    , borderColor: ChartHelper.chartColors.blue
                    , borderWidth: 1
                    , data: data2
                }
                , {
                    label: 'DataSet2'
                    , backgroundColor: 
color(ChartHelper.chartColors.red).alpha(0.5).rgbString()
                    , borderColor: ChartHelper.chartColors.red
                    , borderWidth: 1
                    , data: data1
                }
            ]
        };

        var ctx = document.getElementById('canvas').getContext('2d');

        window.BarChart = new Chart(ctx, {
            type: 'bar'
// 옆으로 누운 bar 차트를 쓰실 경우 바꾸시면 됩니다.
            //type: 'horizontalBar'
            , data: barChartData
            , options: {
// responsive, maintainAspectRatio의 설정이 아래와 같이 해야
// 브라우저의 크기를 변경해도 canvas를 감싸고 있는
// div의 크기에 따라 차트가 깨지지 않고 이쁘게 출력 됩니다. 
                responsive: true   //auto size : true
                , maintainAspectRatio: false
                , legend: {
                    position: 'top'
                }
                , title: {
                    display: true
                    , text: 'Chart Title'
                }
// Bar 하나에 데이터 모두 보여줄 경우 사용
// 주석 처리 할 경우 이 예제에서는 data1, data2 각각 bar가 2개씩 나옵니다.
                //, scales: {
                //    xAxes: [
                //        { stacked: true }
                //    ]
                //    , yAxes: [
                //        { stacked: true }
                //    ]
                //}

// or bar chart 하나씩 보여줄 경우
                  , scales: {
                        yAxes: [{
                            ticks: {
// Y 축 0부터 시작
                                beginAtZero:true
// Y 축 정수로 보여주기 
// 숫자가 작거나 또는 0인 경우 등 자동으로 보여주므로 소숫점으로 나타난다
                                , callback: function (value) {
                                    if (0 === value % 1) {
                                        return value;
                                    }
                                }
                            }
                        }]
                    }
// 아래 주석 처리하여도 포인터를 바에 가져가면 수치가 나옵니다.
// 그러나 min 버젼을 link할 경우 영역에다 가져가면 나오질 않아요
// 또한 툴팁에 해당 데이터의 색도 나오지 않습니다.
// 직접 코딩 후 min 버젼으로 교체하여 테스트 해보시면 차이를 알 수 있습니다.
                // Tooltip 
                //, tooltips: {
                //    mode: 'index',
                //    intersect: false,
                //},
                //hover: {
                //    mode: 'nearest',
                //    intersect: true
                //}
            }
        });

        var colorNames = Object.keys(ChartHelper.chartColors);
    </script>

 

 

 

chartjs - ticks 데이터 없을시 1-0으로나오게하기..

 public String newsExecute(HttpServletRequest request, @Valid Table1 table1, BindingResult error) throws BindException {

        if(table1.getThumbnailFile().getSize()==0){
            error.addError(new ObjectError("thumnailPhoto","썸네일 사진이 없습니다."));
            throw new BindException(error);
            // ValidationUtils.rejectIfEmpty(error,"thumanailPhoto","MSG00001");
        }

 

윗대가리 부분만 잘라옴..

회사 코드라 조금 수정....

exception e안에 BindingResult가 원하는 형식으로 안들어가서(??) 이리저리.. 암튼메모..

 

 

 

https://csslab.tistory.com/67

 

JAVA SPRING에서 TILES 사용하기

Spring에서 Tiles 사용하기 0. 웹 페이지 레이아웃 문제점 지금까지 여러 부분으로 구성된 화면을 구현 시 Header, Menu, Footer 부분을 별도의 jsp 페이지로 구현하고 jsp incude를 이용하여 한 화면으로 구

csslab.tistory.com

 

function nodeToString ( node ) {
   var tmpNode = document.createElement( "div" );
   tmpNode.appendChild( node.cloneNode( true ) );
   var str = tmpNode.innerHTML;
   tmpNode = node = null; // prevent memory leaks in IE
   return str;
}

 

 

 

.idea 의 workspace.xml안에 propertiesComponent를 찾아서 추가

 <property name="dynamic.classpath" value="true" />

 

 

(-_-) 별안간 에러

 

 

javax.mail.MessagingException: Could not convert socket to TLS;
  nested exception is:
   javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
   at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:1907)
   at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:666)
   at javax.mail.Service.connect(Service.java:317)
   at javax.mail.Service.connect(Service.java:176)
   at javax.mail.Service.connect(Service.java:125)
   at javax.mail.Transport.send0(Transport.java:194)
   at javax.mail.Transport.send(Transport.java:124)

하 이거때문에 회사에서 이틀 날렸다~

그냥 자바 버전 낮추니까 됌

그 전까지는 인텔리제이꺼쓰다가 뭐하느라 새로설치했더니..이런

1.8.291에서 1.8.261로 새로 하니까 됨..^^..휴

 

		tooltips: {

			mode: 'label',
			itemSort: function(a, b) {
				return b.datasetIndex - a.datasetIndex
			},

			}

 

 

 

dataset에서 pointStyle을 설정해준다. 

종류는 이렇게 있음

  • 'circle'
  • 'cross'
  • 'crossRot'
  • 'dash'
  • 'line'
  • 'rect'
  • 'rectRounded'
  • 'rectRot'
  • 'star'
  • 'triangle'

 

options - lengend에서 수정 usepointStyle = true 

 

결과물

 

 

 

참고 코드

stackoverflow.com/questions/46439315/change-chartjs-legend-icon-style

 

Change Chartjs Legend Icon Style

I am trying change my Chartjs Icon legend style as shown from my screenshot. Is it possible ?

stackoverflow.com

var data = {
   labels: ['Value [mm]', ''],
   datasets: [{
      label: "Hi-Hi Limit",
      type: 'line',
      data: [5432, 5432],
      backgroundColor: 'rgba(250, 255, 255, 0)',
      borderColor: 'rgba(255, 4, 0, 100)',
      borderWidth: 3,
      radius: 0,
      pointStyle: 'line'
   }, {
      label: "Hi Limit",
      type: 'line',
      data: [5130, 5130],
      backgroundColor: 'rgba(250, 255, 255, 0)',
      borderColor: 'rgba(250, 255, 0, 100)',
      borderWidth: 3,
      radius: 0,
      pointStyle: 'line'
   }, {
      label: "Lo Limit",
      type: 'line',
      data: [905, 905],
      backgroundColor: 'rgba(250, 255, 255, 0)',
      borderColor: 'rgba(250, 255, 0, 100)',
      borderWidth: 3,
      radius: 0,
      pointStyle: 'line'
   }, {
      label: "Lo-Lo Limit",
      type: 'line',
      data: [604, 604],
      backgroundColor: 'rgba(250, 255, 255, 0)',
      borderColor: 'rgba(255, 4, 0, 100)',
      borderWidth: 3,
      radius: 0,
      pointStyle: 'line'
   }, {
      type: 'line',
      label: "Level",
      data: [4800, 4800],
      backgroundColor: 'rgba(0, 119, 220, 1)',
      borderColor: 'rgba(0, 119, 220, 1)',
      borderWidth: 1,
      radius: 0,
      pointStyle: 'rect'
   }],

};

var options = {
   legend: {
      position: 'right',
      labels: {
         usePointStyle: true
      }
   },
   scales: {
      yAxes: [{
         gridLines: {
            display: true,
            color: "rgba(255,99,132,0.2)"
         }
      }],
      xAxes: [{
         gridLines: {
            display: false
         }
      }]
   }
};

Chart.Bar('chart', {
   options: options,
   data: data
});
body {
   background: #1D1F20;
   padding: 16px;
}

.chart-container {
   position: relative;
   margin: auto;
   height: 80vh;
   width: 80vw;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
<div class="chart-container">
   <canvas id="chart"></canvas>
</div>

stackoverflow.com/questions/53764367/how-to-trigger-hover-programmatically-in-chartjs

 

How to trigger hover programmatically in chartjs

I want to set the hover effect in ChartJs programmatically i wish to see both effects hoverBorderWidth, and hoverBorderColor. I know how to activate some tooltips, but I can't apply hover effects. ...

stackoverflow.com

hyung1.tistory.com/17

 

chart.js 로 그래프 그리기

chart.js 를 활용하여 그래프를 그리는 작업을 했다. (chart.js 링크: https://www.chartjs.org/) 기본 bar 그래프를 사용하면 좋으려만 디자인은 그렇게 나오지 않았기에..... 그리하여 이번에 작업하면서 사

hyung1.tistory.com

jsfiddle.net/kf9uLf3x/

 

Edit fiddle - JSFiddle - Code Playground

 

jsfiddle.net

let c = new Chart($('#chart'), {
  type: 'doughnut',
  data: {
    labels: ['a', 'b', 'c', 'd'],
    datasets: [{
      data: [1, 2, 4, 8],
      backgroundColor: ['red', 'blue', 'green', 'orange']
    }]
  },
  options: {
    maintainAspectRatio: false
  }
});
$('#a').on('click', function() { t(0); });
$('#b').on('click', function() { t(1); });
$('#c').on('click', function() { t(2); });
$('#d').on('click', function() { t(3); });

function t(idx) {
  var meta = c.getDatasetMeta(0),
    rect = c.canvas.getBoundingClientRect(),
    point = meta.data[idx].getCenterPoint(),
    evt = new MouseEvent('mousemove', {
      clientX: rect.left + point.x,
      clientY: rect.top + point.y
    }),
    node = c.canvas;
  node.dispatchEvent(evt);
}

 

 

 

위의 코드들을 참조해서

1) 마우스 오버가 아닌 클릭시 툴팁이 팝업되는 형식으로 수정 하고

2) 원하는 칼럼(?)을 클릭하는 이벤트를 만들어줌 (내 경우에는 첫번째인 [0]번)

ㅡ.ㅡ 이거하려고 무슨 chartjs 외부트리거 등등 엄청 찾았네..

여기서 포인트는 options -tooltip의 intersect를 false로 해주는 것이다.

function fn_make_chart_doughnut2(idNm, data, labels, legend_position, tooltipSign){
	if(legend_position == null) legend_position = 'top';
	if(tooltipSign == null) tooltipSign = '%';

	let ctx = document.getElementById(idNm);

	let chart_data = {
		datasets: [{
			data: data,
			backgroundColor: chart_colors,
			hoverBackgroundColor: chart_colors_hover,
			hoverBorderColor: "rgba(234, 236, 244, 1)",
		}],

		// These labels appear in the legend and in the tooltips when hovering different arcs
		labels: labels
	};

	let options = {
		events: ["click"],
		hover: {
			mode: 'nearest'
		},

		maintainAspectRatio: false,
		tooltips: {
			intersect: false,
			backgroundColor: "rgb(255,255,255)",
			bodyFontColor: "#858796",
			borderColor: '#dddfeb',
			borderWidth: 1,
			xPadding: 15,
			yPadding: 15,
			displayColors: true,
			caretPadding: 20,
			callbacks: {

				label: function(tooltipItem, chart) {
					let datasetLabel = chart.labels[tooltipItem.index] || '';
					let rateArr = fn_get_rateArrByList(chart.datasets[0].data, null);
					let rate = rateArr[tooltipItem.index];
					return datasetLabel+ ', ' +rate + " %";

				}
			}
		},
		title: {
			display: false,
			position: 'top',
			fontSize: 40,
			fontColor: "#858796",
			text: ""
		},
		layout: {
			padding: {
				top: 0,
				bottom: 0
			}
		},
		legend: {
			display: true,
			position: legend_position,
			labels: {
				padding: 10,
				boxWidth:12
			}
		},
		rotation:  1.2 * Math.PI,
		cutoutPercentage: 75,	//도넛 구멍 크기
	};

	let myDoughnutChart = new Chart(ctx, {
		type: 'doughnut',
		data: chart_data,
		options: options
	});

	return myDoughnutChart;
}
    function click_first_column() {
        var meta = doughnutRstChart.getDatasetMeta(0),
            rect =doughnutRstChart.canvas.getBoundingClientRect(),
            point = meta.data[0].getCenterPoint(),
            evt = new MouseEvent('click', {
                clientX: rect.left + point.x,
                clientY: rect.top + point.y
            }),
            node = doughnutRstChart.canvas;
        node.dispatchEvent(evt);
    }

    public String makeComma(int val){
        DecimalFormat Commas = new DecimalFormat("#,###");
        String rst = (String)Commas.format(val);

        return rst;

ifuwanna.tistory.com/241

 

[IntelliJ] 필수 단축키 정리 (+ 전체 단축키 표)

인텔리J(IntelliJ IDEA) 에서는 코드 작성, 편집, 검색, 리팩토링, 디버깅 등 IDE내에서  수행하는 대부분의 작업 명령의 키보드 단축키(ShortCut)을 제공합니다. 자주 사용되는 단축키는 숙지해 두시면

ifuwanna.tistory.com

https://www.jetbrains.com

 

JetBrains: Essential tools for software developers and teams

JetBrains is a cutting-edge software vendor specializing in the creation of intelligent development tools, including IntelliJ IDEA – the leading Java IDE, and the Kotlin programming language.

www.jetbrains.com

 General

자주 사용되는 공통 부분 단축키 입니다.

 

⌘0~9(Alt+#[0-9]) : 각 단축키에 해당하는 도구창을 엽니다. 예를 들어 ⌘1 을 사용하면 프로젝트 도구창을 토글할 수 있습니다.

⌘⇧F12(Ctrl+Shift+F12) : 편집기(Editor) 영역을 최대로 크기로 토글합니다.( Toggle maximizing editor)

⌘,(Ctrl+Alt+S) : 설정(Preferences) 창을 엽니다. ( Open Settings dialog )

⌘;(Ctrl+Alt+Shift+S) : 프로젝트 구조를 확인 및 변경 할 수 있는 창을 엽니다. 해당 메뉴에서 Modules, Libraries, Facets, Artifacts, SDK,Project compiler output 등을 확인 및 변경 할 수 있습니다.(open Project Structure dialog

⌘E(Ctrl+E) : 최근 사용한 파일 목록을 조회합니다.(Recent files popup)

A(Ctrl+Shift+A) : 액션을 검색하여 실행합니다. 설정을 변경하거나 단축키 등이 생각나지 않을때 유용한 기능이며 전체 검색인 더블⇧ 로 대체 할 수 있습니다. (Find Action)

Double⇧ : 가장 자주 사용되는 단축키 입니다. 파일, 클래스, 설정 등 키워드에 관련된 가능한 모든 것을 검색해 보여줍니다.( Search everywhere )

 

 

Editing

편집기(Editor) 관련된 주요 단축키 입니다.

 

^Space(Ctrl+Space) : 기본 코드 자동완성 기능입니다.  (Basic code completion)

^⇧Space(Ctrl+Shift+Space) : 소스코드를 분석해서 적합한 자동완성 코드를 추천해줍니다. (Smart code completion)

⌘N,^(Alt+Insert) : 코드를 생성합니다. 생성자나 Getter/Setter, 메서드 오버라이드 등의 코드를 자동생성할때 유용합니다. (Generate Code)

⌘O(Ctrl+O) Override 가능한 메서드 목록을 확인하여 구현하기 위한 코드를 자동 생성해 줍니다.

⌘I(Ctrl+I)  Implement 가능한 메서드 목록을 확인하여 구현하기 위한 코드를 자동 생성해 줍니다.

 

⌘P(Ctrl+P) : 현재 위치한 메서드의 파라미터 정보를 조회합니다. (Parameter info -within method call arguments)

F1(Ctrl+Q) : 도큐멘트를 조회합니다.(Quick Documentation)

⌥Space(Ctrl+Shift+i) : 구현된 코드를 조회합니다. (Quick Definition)

⌘B(Ctrl+B) : 해당 코드의 선언부로 이동.

⌘⌥B(Ctrl+Alt+B) : 해당 코드의 구현부로 이동.

⌥F7(Alt+F7) : 해당 항목이 사용된 위치 검색 

F2 : 오류, 경고 영역으로 점프 

(Alt+⏎) : 퀵픽스 제안 (Show intention actions and quick-fixes)

 

⌘D(Ctrl+D) : 라인 복제 (Duplicate current line)

⌘⌫(Ctrl+Y) : 라인 삭제 (Delete line at caret)

⌥↑,⌥↓(Ctrl+W,Shift+Ctrl+W) : 커서 근처의 코드 선택 영역을 확대하거나 축소합니다.

←,⌥→(Ctrl+←,Ctrl+→) : 단어별 포커스 이동

←,⌥(Ctrl+Shift+←,Ctrl+Shift+→) : 단어별 선택

fn,fn(PageUp,PageDown): Page Up/Down

fn←,fn→(Home, End) : 라인 시작,끝으로 이동

fn←,fn(Shift+Home,Shift+End) : 라인 전체 선택

 

⌘/(Ctrl+/) : 라인단위로 주석처리 합니다. ( Comment / uncomment with line comment )

/(Ctrl+Shift+/) : 블럭단위로 주석처리 합니다. ( Comment / uncomment with block comment )

⌥⌘L(Ctrl+Alt+L) : 해당 프로젝트의 표준 서식에 맞도록 정렬합니다. (Reformat code)

 

Compile and Run

컴파일(Compile) 및 실행(Run) 관련 주요 단축키 입니다.

 

⌘F9(Ctrl + F9) : 프로젝트 빌드(Make project)

⌘R(Shift + F10) : 실행 (Run)  

⌘d(Shift + F9): 디버그 (Debug)

 

Refactoring

소스 리팩토링(Refactoring) 관련 단축키 입니다.

 

^T(Ctrl+Alt+Shift+T) : 리팩토링 관련 전체 항목을 조회합니다. (Refactor this)

F5 : 복사 (Copy)

F6 : 이동 (Move)

⇧F6(Shift+F6) : 이름 변경(Rename)

⌘F6(Ctrl+F6) : 접근제어자, 반환타입, 이름 등 주요 정보를 변경합니다. (Change Signature)

 

 

VCS / Local History

Git 같은 버전 관리 시스템(Version Control System) 관련 단축키 입니다.

 

K(Ctrl+K) : Commit project to VCS

K(Ctrl+Shift+K) : Push commits

T(Ctrl+T) : Update project from VCS

^V(Alt + BackQuote) :  ‘VCS’ quick popup

 

1. chartjs 데이터 label 외에 yAxisID를 추가해준다.

Y축 ID없이 그냥 position을 바꾸면1. chartjs 데이터 label 외에 yAxisID를 추가해준다.

아니면 dataset에서 설정해주던가..

 

Y축 ID없이 그냥 position을 바꾸면 값만 생기고 차트는 오른쪽값을 따라서 그려지게 된다.

 

 

2. options 의 scale에서 설정해준다.

[ options > scales > yAxes ]

position을 바꿔주면 된다.

 

참고코드 : embed.plnkr.co/dtb9tl/

<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js"></script>
  </head>

  <body>
    <h1>Hello Plunker!</h1>
    <div style="width:100%;">
      <canvas id="chart"></canvas>
    </div>
    <script src="script.js"></script>
  </body>

</html>
// Code goes here

var ctx = document.getElementById('chart').getContext('2d');
new Chart(ctx, {
  type: 'line',
  data: {
    labels: ['Visit 1', 'Visit 2', 'Visit 3', 'Visit 4', 'Visit 5'],
    datasets: [{
      label: 'LVEF % (Echo)',
      yAxisID: 'A',
      borderColor: '#ffbaa2',
      backgroundColor: 'white',
      data: [80, 70, 30, 20, 35],
      fill: false
    }, {
      label: 'Rhythm',
      yAxisID: 'B',
      borderColor: '#91cf96',
      backgroundColor: 'white',
      data: [80, 76, 79, 82, 80],
      fill: false
    }, {
      label: 'Height',
      yAxisID: 'C',
      borderColor: '#c881d2',
      backgroundColor: 'white',
      data: [185, 184, 183, 184, 185],
      fill: false
    }, {
      label: 'Weight',
      yAxisID: 'D',
      borderColor: '#29b6f6',
      backgroundColor: 'white',
      data: [65, 65, 65, 65, 65],
      fill: false
    }]
  },
  options: {
    tooltips: {
      mode: 'nearest'
    },
    scales: {
      yAxes: [{
        id: 'A',
        type: 'linear',
        position: 'left',
        ticks: {
          min: 0,
          max: 100,
          stepSize: 20,
          fontColor: '#ffbaa2',
          callback: function(value, index, values) {
            return value + ' %';
          }
        }
      }, {
        id: 'B',
        type: 'linear',
        position: 'right',
        ticks: {
          min: 60,
          max: 140,
          stepSize: 16,
          fontColor: '#91cf96',
          callback: function(value, index, values) {
            return value + ' bpm';
          }
        }
      }, {
        id: 'C',
        type: 'linear',
        position: 'right',
        ticks: {
          min: 160,
          max: 190,
          stepSize: 6,
          fontColor: '#c881d2',
          callback: function(value, index, values) {
            return value + ' cm';
          }
        }
      }, {
        id: 'D',
        type: 'linear',
        position: 'right',
        ticks: {
          min: 50,
          max: 100,
          stepSize: 10,
          fontColor: '#29b6f6',
          callback: function(value, index, values) {
            return value + ' kg';
          }
        },
        scaleLabel: {
          display: false
        },
      }]
    },
    elements: {
      line: {
        tension: 0, // disables bezier curves
      },
      point: {
        radius: 4,
        borderWidth: 2,
        pointStyle: 'circle'
      }
    }

  }
});

 

 

chartjs는 dataset 입력 순서대로 그려지고

가장 처음 그려진 것이 제일 위로 올라온다.

그리고 가장 처음 그려진 것의 범례 맨 앞에 위치함

 

범례는 그대로 내버려두고 차트의 순서만 바꾸고 싶다면

legend 옵션에서 reveser를 true로 바꿔주면된다.

 

 

dataset > order 옵션에서 순서를 정할 수 있다는데 안먹어서 찾아낸 방법.. 여러개인 경우는 모르겠다 ^ㅠ^

option에 responsive : false로 하면

캔버스크기에 맞춰서 그려짐 대충..

없으면 캔버스 크기 무시하고 지맘대로 그려버림.. 죽어..흑흑..

1시간은 날린듯

※ Data Type

- DATETIME : 'YYYY-MM-DD HH:MM:SS'

- DATE : 'YYYY-MM-DD'

- TIME : 'HH:MM:SS'

- CHAR : String

- SIGNED : Integer(64bit), 부호 사용 가능

- UNSIGNED : Integer(64bit), 부호 사용 불가

- BINARY : binary String

 

※ 숫자를 문자로 변환

SELECT CAST(1 AS CHAR(10)) AS testChar

 

※ 문자를 숫자로 변환

SELECT CAST('1' AS SINGNED) AS testInt



'DB > MYSQL' 카테고리의 다른 글

[MYSQL] date_format / left join on / if문 / 시간비교  (0) 2021.03.24
[MYSQL] 기본 - 계정생성 / 테이블생성 / 입력  (0) 2020.11.19
[MYSQL] 설치  (0) 2020.11.19

package java_sort;

 

import java.util.HashMap;

import java.util.Map;

 

public class HashmapGetdata {

 

    public static void main(String[] args) {

        

        Map<String, Object> hashMap = new HashMap();

 

 

        //데이터 넣어줌

        hashMap.put("num"1);

        hashMap.put("name""rios");

        hashMap.put("email""rios0812@naver.com");

        hashMap.put("phone_num""010-1234-1234");

 

 

        // ** value를 통해 key 값얻기 **       

        

        System.out.println(getKey(hashMap,"rios"));

        //rios라는 value 값을 통해서 얻은 key 값 -> name

        System.out.println(getKey(hashMap,"rios0812@naver.com"));

        //rios0812@naver.com 라는 value 값을 통해서 얻은 key 값 -> email

 

 

        // ** key를 통해 value얻기 **

 

        String getName = (String) hashMap.get("name");

        System.out.println(getName);

        //name 이란 key값을 통해서 value 값은 -> rios 

 

        String getPhone = (String) hashMap.get("phone_num");

        System.out.println(getPhone);

        //phone_num 이란 key값을 통해서 value 값은 -> 010-1234-1234

    }

 

 

   // hashmap에 value 로 key 찾기

   public static <K, V> K getKey(Map<K, V> map, V value) {

 

        for (K key : map.keySet()) {

            if (value.equals(map.get(key))) {

                return key;

            }

        }

        return null;

    }

 

devjhs.tistory.com/89

 

[mysql] DATE_FORMAT - 날짜 형식 설정

1. DATE_FORMAT - 역할 DATE_FORMAT(날짜 , 형식) : 날짜를 지정한 형식으로 출력 2. DATE_FORMAT - 구분기호  구분기호 역할 구분기호 역할  %Y 4자리 년도 %m 숫자 월 ( 두자리 )  %y 2자리 년도 %c 숫자..

devjhs.tistory.com

blog.leocat.kr/notes/2017/07/28/sql-join-on-vs-where

 

[SQL] join의 on절과 where절 차이

이전 블로그에서 옮겨온 포스트

blog.leocat.kr

.m.blog.naver.com/PostView.nhn?blogId=rorean&logNo=221594169204&proxyReferer=https:%2F%2Fwww.google.com%2F

 

[mysql] IF 문 사용하기

​​IF 사용법은 엑셀에서 IF 함수 사용하는 것과 동일하다. 엑셀이 친숙하다면 어렵지 않게 사용 가능...

blog.naver.com

 

banasun.tistory.com/entry/mysql-%ED%98%84%EC%9E%AC%EB%A1%9C%EB%B6%80%ED%84%B0-6%EC%8B%9C%EA%B0%84-%EC%9D%B4%EB%82%B4%EC%9D%98-record%EB%A5%BC-select-%ED%95%98%EA%B8%B0

 

[mysql] 현재로부터 6시간 이내의 record를 select 하기

오늘은 SQL query문 중 기초적인 부분에 대해서 써볼까 합니다. 물론 오늘 한 작업 중 한 가지 소스이지요 ㅋㅋ DATETIME 필드를 기준으로 현재로부터 6시간 이내에 발생한 record를 불러오는 겁니다. MY

banasun.tistory.com

 

'DB > MYSQL' 카테고리의 다른 글

[MYSQL] CAST (형변환)  (0) 2021.03.30
[MYSQL] 기본 - 계정생성 / 테이블생성 / 입력  (0) 2020.11.19
[MYSQL] 설치  (0) 2020.11.19

구글링 해보니까 이게 제일 비슷한 것 같아서 이걸로 퍼옴

 

생성과정(start) / 갱신과정(props changed & state changed) / 소멸과정 (componentWillUnmount)

shouldComponentUpdate 함수의 반환값(true/false)에 따라 갱신과정은 생략되기도 한다. (갱신과정생략)

 

 

 

생성 과정의 생명주기 함수들 실행해보기

 

1. constructor(props)함수

맨 처음에 생성될 때 한번만 호출되며 상태(state)를 선언할 떄 사용된다.

항상 super()함수를 가장 위에 호출해야 한다.

=> super() 함수에는 프로퍼티와 생명 주기 상태등을 초기화하는 과정을 포함하고 있기 때문.

 

2. render()함수

데이터가 변경되어 새 화면을 그려야 할 때 자동으로 호출되는 함수

render()가 반환하는 JSX를 화면에 그려준다.

 

3. static getDerivedStateFromProps(props, state)함수 (이름왜케길어)

정적 함수로, 함수안에서 this.props나 this.state와 같은 방법으로 값에 접근이 불가하다.

만약 값에 접근하려면 인자로 전달된 props, state를 이용해야 하는데,

이때 props는 상위 컴포넌트에서 전달된 값이며, state는 현재 컴포넌트의 state값이다.

> 이 함수는 상위 컴포넌트에서 전달받은 프로퍼티로 state값을 연동할 때 주로 사용하며,

   반환 값으로 state를 변경한다.

 

4. componentDidMount()함수

render함수가 JSX를 화면에 그린 이후 호출되는 함수

=> 컴포넌트가 화면에 모두 표횬된 이후에 해야 하는 작업들은 여기서하면 된다.

 

 

 

 

[그림에서는 START부분]

import React from "react";

class LifecycleExample extends React.Component {
  static getDerivedStateFromProps() {
    console.log("getDerivedStateFromProps 호출");
    return {};
  }

  constructor(props) {
    super(props);
    //getDerivedStateFromProps()함수를 사용하므로
    //경고메세지를 건너뛰기 위해 state 초기값 설정

    this.state = {};
    console.log("constructor 호출");
  }

  componentDidMount() {
    console.log("ComponentDidMount 호출");
  }

  componentDidUpdate() {
    console.log("ComponentDidUpdate 호출");
  }

  componentWillUnmount() {
    console.log("ComponentWillUnmount 호출");
    return {};
  }

  getSnapshotBeforeUpdate() {
    console.log("getSnapshotBeforeUpdate 호출");
    return {};
  }

  shouldComponentUpdate() {
    console.log("shouldComponentUpdate 호출");
    return true;
  }

  render() {
    console.log("render 호출");
    return null;
  }
}

export default LifecycleExample;

 

 

변경 과정의 생명주기 함수들 실행해보기

상위 컴포넌트의 프로퍼티나 state의 변화가 생기면 실행된다.

 

1. shouldComponentUpdate(nextProps, nextState)

프로퍼티를 변경하거나, setState()함수를 호출하여 state값을 변경하면 '화면을 새로 출력해야하는지' 판단하는 함수

** 이 함수는 화면을 새로 출력할지 말지 판단하며, 데이터 변화를 비교하는 작업을 포함하므로 리액트 성능에 영향을 많이 준다.

forceUpdate()함수를 호출하여 강제로 화면을 출력하면 이함수는 호출되지 않는다.

 

2. getSnapshotBeforeUpdate(prevProps, prevState)

컴포넌트의 변경된 내용이 가상화면에 완성된 이후에 호출되는 함수

컴포넌트가 화면에 실제로 출력되기 전에 호출되므로 화면에 출력될 엘리먼트의 크기, 또는 스크롤 위치등의 DOM정보에 접근할 때 사용된다.

 

3. componentDidUpdate(prevProps, PrevState, snapshot)함수

실제 화면에 출력된 이후 호출되는 함수

부모 컴포넌트로부터 전달된 이전 프로퍼티(prevProps)와 이전 state값(prevState)과 함께, getSnapshotBeforeUpdate() 함수에서 반환된 값(snapshot)을 인자로 전달 받는다.

이 값들을 이용하여 스크롤의 위치를 옮기거나 커서를 옮기는 등의 DOM정보를 변경할 때 사용된다.

 

 

  componentDidMount() {
    console.log("ComponentDidMount 호출");
    // state값을 변경
    this.setState({updated : true});
  }

 

shouldComponentUpdate() 반환값을 false로 하면

-> 리액트 엔진은 데이터 비교 후 변경 사항이 없다고 판단하므로, 변경과정의 생명주기 함수가 실행되지 않는다.

  shouldComponentUpdate() {
    console.log("shouldComponentUpdate 호출");
    // false로 변경
    return false;
  }

결과값과 상관없이 화면 동기화를 진행하고 싶다면 forceUpdate()함수를 사용해야 한다.

  componentDidMount() {
    console.log("ComponentDidMount 호출");
    this.forceUpdate();
  }

 

소멸 과정의 생명주기 함수들 실행해보기

컴포넌트가 화면에서 생략되면 시작

 

componentWillUnmount()

컴포넌트가 소멸되기 직전에 호출되는 함수

보통 컴포넌트에서 감시하고 있는 작업들을 해제할 때 필요한 함수이다 (뭔말이지?)

예를 들어 컴포넌트에 setInterval()함수가 사용되었다면 이 함수에서 setInterval()함수를 clearInterval()함수로 해제해야 한다. (그렇군..)

이러한 해제 작업이 생략되면 메모리 누수현상이 발생하여 브라우저가 멈추기도 하기 때문이다.

 

 

 

App 컴포넌트에서 LifecycleExample 컴포넌트를 그리지 않도록(null) 코드를 작성

/// app.jsx


import React from "react";
import LifecycleExample from "./03/LifecycleExample";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hadDestroyed: false };
  }
  componentDidMount() {
    this.setState({ hadDestroyed: true });
  }

  render() {
    return (
      <div>
        <div>{this.state.hadDestroyed ? null : <LifecycleExample />}</div>
      </div>
    );
  }
}

export default App;

 

 

카운터 프로그램을 만들며 생명주기 함수 사용해보기

 

 

counter.jsx

: state의 초기값을 설정할 때 props.count와 같이 프로퍼티로 받은 값을 사용한다.

import React, { Component } from "react";

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      // count 초기값을 프로퍼티에서 전달된 값으로 설정한다.
      count: props.count,
    };
    this.increaseCount = this.increaseCount.bind(this);
  }
  increaseCount() {
    this.setState(({ count }) => ({
      count: count + 1,
    }));
  }
  render() {
    return (
      <div>
        현재카운트 : {this.state.count}
        <button onClick={this.increaseCount}>카운트증가</button>
      </div>
    );
  }
}

export default Counter;

NewCount.jsx

gerDerfivedStateFromProps() 함수를 사용하여 변경된 프로퍼티 값으로 state값을 갱신한다.

app컴포넌트가 전달한 최초의 프로퍼티 값은 state.count에 저장되며,

NewCounter 컴포넌트는 state.newCount로 증가값을 따로 분리하여 관리

=> state.count가 아니라 state.newCount로 증가값을  관리하는 이유는,

getDerivedStateFromProps()함수는 다른 프로퍼티가 변경되어도 호출되기 때문이다.

import React, { Component } from "react";

class NewCounter extends Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.increaseCount = this.increaseCount.bind(this);
  }

  static getDerivedStateFromProps(props, state) {
    const { count } = props;
    return {
      //프로퍼티에서 전달된 count값을 보관한다
      count,
      newCount:
        count === state.count
          ? //프로퍼티가 변경되지 않았다면 기존state값으로 설정
            state.newCount
          : //변경되었다면 변경된 값으로 설정
            count,
    };
  }

  increaseCount() {
    this.setState(({ newCount }) => ({
      newCount: newCount + 1,
    }));
  }
  render() {
    return (
      <div>
        현재카운트 : {this.state.newCount}
        <button onClick={this.increaseCount}>카운트증가</button>
      </div>
    );
  }
}

export default NewCounter;

 

 

import React from "react";
import Counter from "./03/Counter";
import NewCount from "./03/NewCounter";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 10 };
    this.resetCount = this.resetCount.bind(this);
  }

  resetCount() {
    this.setState(({ count }) => ({ count: count + 10 }));
  }

  render() {
    return (
      <div>
        <div>
          <Counter count={this.state.count} />
        </div>
        <div>
          <NewCount count={this.state.count} />
        </div>
        <button onClick={this.resetCount}>
          {this.state.count + 10}으로 초기화
        </button>
      </div>
    );
  }
}

export default App;

 

 

NewCounter 컴포넌트만 gerDerivedStateFromProps()함수로 App 컴포넌트부터 갱신된 프로퍼티 값을 동기화 했기 때문에 초기화버튼은 NewCounter에만 적용된다.

Counter 컴포넌트는 처음 생성될때만 프로퍼티 값을 설정하므로 갱신 과정에서는 변경X

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts