no image
[Spring Boot] MyBatis SQL 쿼리 로그 설정
Spring Boot 내에서 연동한 MyBatis의 쿼리 실행 로그를 볼 수 있도록 설정해보자. 1. Dependency 추가 org.bgee.log4jdbc-log4j2 log4jdbc-log4j2-jdbc4.1 1.16Log4jdbc Dependency를 추가한다. 2. application.properties DB 설정 변경spring.datasource.driver-class-name=org.postgresql.Driverspring.datasource.jdbc-url=jdbc:postgresql://localhost:5432/postgresspring.datasource.username=postgresspring.datasource.password=1234기존의 applicati..
2024.06.29
[Spring Boot] Thymeleaf HTML 템플릿 수정 시 브라우저 반영 설정
application.properties에 다음과 같이 설정한다.spring.thymeleaf.cache=falseThymeleaf 템플릿 파일은 캐싱이 적용된 파일을 우선하여 브라우저에 적용이 되기 때문에 HTML 내 수정사항을 바로 브라우저 내에서 확인할 수 없다. 브라우저에서 캐시 비우기 및 강력 새로고침을 해도 어림도 없다.이를 위해서는 캐싱을 비활성화 시켜 수정된 HTML 파일을 바로 브라우저에 반영되도록 한다.
2024.06.26
[eGovFrame 4.2] DB 연결 오류 "No Supported DataSource type found"
eGovFramework 4.2를 이용하여 eGovFrame Boot Web Project를 생성하여 DB 연결을 하려고 하던 중 문제가 발생했다. @Configurationpublic class EgovConfigDatasource { @Bean(name="dataSource") public DataSource dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); return builder.setType(EmbeddedDatabaseType.HSQL).addScript("classpath:/db/sampledb.sql").build(); }}기존 예제에서 DataSource를 설정하는 설정 클래스 파..
2024.06.26
[Spring Boot] MyBatis + PostgreSQL 연동
* 스프링부트에서 MyBatis를 사용하기 위해 다음 dependency를 추가한다. org.postgresql postgresql org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.2  1. 데이터베이스 연결 정보 등록spring.datasource.driver-class-name=org.postgresql.Driverspring.datasource.url = jdbc:postgresql://localhost:5432/postgresspring.datasource.username = postgresspring.datasource.password = 1234다음과 같이 application.properties에 내가 연결하고 싶은 DB 정보 변수를 등록한..
2024.06.25
no image
PPT 고화질 이미지가 안 깨지게 하는 방법!
회사에서 PPT 문서 작업을 하다보면 슬라이드에 이미지를 많이 넣게 되는데 고화질 이미지를 넣고 시간이 좀 지나면 화질이 완전히 깨져버린다.... PPT 문서에 들어가서 상단 '파일' 탭을 통해 옵션으로 들어가면 지금 보이는 창이 뜰 것이다. 여기서 좌측 메뉴의 '고급' 탭을 통해 '파일의 이미지 압축 안 함' 체크를 선택하면 된다. 저 체크가 해제되어 있으면 어떤 이미지든 간에 고화질이면 무조건 내부적으로 압축을 진행해서 화질이 깨지는 것이다.
2024.06.24
no image
[MediaPipe] MediaPipe를 이용한 팔굽히기 모션 인식하기
사람을 인식하고 해당 신체의 모든 부위 얼굴, 팔, 다리, 몸통의 움직임을 추적하는MediaPipe 라이브러리를 사용하여 팔 운동 트레이닝 로직(AI 이두컬 트레이너)을 만들어보자.  1. MediaPipe, OpenCV 설치!pip install mediapipe opencv-python이번 작업에서 가장 중요한 두 가지 라이브러리를 설치한다. 2. 해당 라이브러리 임포트import cv2 # openCVimport mediapipe as mpimport numpy as npmp_drawing = mp.solutions.drawing_utilsmp_pose = mp.solutions.posemp_drawings: 신체를 인식하여 여러 부위를 감지 및 추적하는 것을 시각화 할 때 사용하는 유틸리티mp_p..
2024.06.24
[Spring Boot] 스프링 부트 JWT 인증 방식 구현
스프링 부트에서는 공식적으로 JWT 인증 방식을 스프링 시큐리티와 함께 구현하는 것을 권장하고 있다. 스프링 시큐리티란?스프링 시큐리티는 스프링 프레임워크 기반으로 제작한 프로젝트의 인증과 권한 기능을 담당하는 프레임워크다. 어플리케이션의 로그인, 회원가입 등의 기능을 자체적으로 처리하는 기능이 내장되어 있기 때문에이런 식으로 스프링 시큐리티 없이 로그인 로직을 직접 작성하였다면,if (param.get("USER_PW").toString().equals(member.get("USER_PW").toString())) { system.out.println("로그인 성공!"); return "main"; }  스프링 시큐리티 사용 시 이렇게 한줄 코드로 로그인 처리를 할 수 있다.Member member..
2024.03.04
[Git] 자주 사용하는 명령어들
1. git clonegit clone https://:@특정 깃허브 리포지토리를 연결함과 동시에 메인 브랜치 커밋 내역들을 로컬의 커밋내역으로 병합한다.1. git remote add origin : 해당 주소를 원격 저장소로 연결2. git pull origin main: 원격 저장소의 메인 브랜치만 다운로드 하여 커밋내역을 병합한다.위 2가지가 합쳐진 명령어임 2. git pullgit pull origin 연결된 원격 저장소의 특정 브랜치의 커밋 내역들을 로컬에 현재 접속되어 있는 브랜치에 병합한다.1. git fetch origin: 원격 저장소의 모든 브랜치들을 다운 받는다.2. git merge origin/: 다운받은 원격 저장소의 메인 브랜치를 병합한다.                   ..
2024.02.24
Git
728x90

Spring Boot 내에서 연동한 MyBatis의 쿼리 실행 로그를 볼 수 있도록 설정해보자.

 

1. Dependency 추가

<dependency>
    <groupId>org.bgee.log4jdbc-log4j2</groupId>
    <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
    <version>1.16</version>
</dependency>
  • Log4jdbc Dependency를 추가한다.

 

2. application.properties DB 설정 변경

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.jdbc-url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=1234
  • 기존의 application.properties 내에서 DB 설정 부분이 이렇게 되어 있었다면 수정할 부분은 딱 2가지가 된다.
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.jdbc-url=jdbc:log4jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=1234
  • spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy - 드라이버 클래스 이름 변경
  • spring.datasource.jdbc-url=jdbc:log4jdbc:postgresql://localhost:5432/postgres - url 주소에 'log4jdbc' 삽입

 

3. log4jdbc.log4j2.properties 생성

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.dump.sql.maxlinelength=0
  • resources 폴더 바로 밑에 생성해준다.
  • 다음 설정을 입력해둔다.

 

4. logback.xml 생성 및 설정

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<appender name="STDOUT"
		class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%d{yyyyMMdd HH:mm:ss.SSS} [%thread] %-3level %logger{5} - %msg %n</pattern>
		</encoder>
	</appender>
	<logger name="jdbc" level="OFF" />
	<logger name="jdbc.sqlonly" level="OFF" />
	<logger name="jdbc.sqltiming" level="DEBUG" />
	<logger name="jdbc.audit" level="OFF" />
	<logger name="jdbc.resultset" level="OFF" />
	<logger name="jdbc.resultsettable" level="DEBUG" />
	<logger name="jdbc.connection" level="OFF" />
	<root level="INFO">
		<appender-ref ref="STDOUT" />
	</root>
</configuration>
  • 쿼리 로그를 어떻게 보여줄지에 대해 직접적으로 설정하는 파일이다.
  • 이것도 마찬가지로 resources 폴더 바로 밑에 생성한다.
  • 가장 중요한 3가지 설정을 보자면,
  • jdbc.sqlonly: 실행된 SQL 문만을 로그로 출력할지 여부
  • jdbc.sqltiming: 실행된 SQL문과 함께 해당 쿼리를 실행하는 데 걸린 시간을 로그로 출력할지 여부 
  • jdbc.resultsettable: SELECT 쿼리 결과인 ResultSet의 내용을 테이블 형식으로 로그에 출력할지 여부

 

※ 결과

728x90
728x90

application.properties에 다음과 같이 설정한다.

spring.thymeleaf.cache=false
  • Thymeleaf 템플릿 파일은 캐싱이 적용된 파일을 우선하여 브라우저에 적용이 되기 때문에 HTML 내 수정사항을 바로 브라우저 내에서 확인할 수 없다. 브라우저에서 캐시 비우기 및 강력 새로고침을 해도 어림도 없다.
  • 이를 위해서는 캐싱을 비활성화 시켜 수정된 HTML 파일을 바로 브라우저에 반영되도록 한다.

 

 

 

728x90
728x90

eGovFramework 4.2를 이용하여 eGovFrame Boot Web Project를 생성하여 DB 연결을 하려고 하던 중 문제가 발생했다.

 

@Configuration
public class EgovConfigDatasource {
	@Bean(name="dataSource")
	public DataSource dataSource() {
	    EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
	    return builder.setType(EmbeddedDatabaseType.HSQL).addScript("classpath:/db/sampledb.sql").build();
	}
}

기존 예제에서 DataSource를 설정하는 설정 클래스 파일인데

 

SQL 문 파일을 따로 만들어서 해당 SQL 문 집합체를 통해 DB 정보를 넣어주도록 되어있다.

 

이 DataSource 빈을 내가 원하는 DB와 연결하도록 바꿔야 하기 때문에 application.properties 안에 DB 정보들을 넣고 이 정보들을 가지고 DataSource 빈을 만들어주었다.

@ConfigurationProperties(prefix = "spring.datasource")
@Bean(name="dataSource")
	public DataSource dataSource() {
		return DataSourceBuilder.create().build();
	}

 

근데 "No Supported DataSource type found"  에러가 뜨는 것이다.

 

해결방법을 찾기 위해 구글링을 열심히 하고 eGov 홈페이지에서도 물어보고 했지만 최신버전이라 자료가 많이 없어 아무 방법이나 시도해보았다.

 

결국 해결방법을 찾았는데

프로젝트를 생성하면 eGovFrame에서 제공하는 MyBatis를 쓰게 된다.

 

근데 그걸 쓰지말고 내가 원하는 DB를 연결하기 위해 스프링부트에서 제공하는 MyBatis를 써야하는것 같다.

따라서 아래와 같이 dependency를 추가해주었다.

		<dependency>
		    <groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>

 

728x90
728x90

* 스프링부트에서 MyBatis를 사용하기 위해 다음 dependency를 추가한다.

<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
</dependency>
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>1.3.2</version>
</dependency>

 

 

1. 데이터베이스 연결 정보 등록

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url = jdbc:postgresql://localhost:5432/postgres
spring.datasource.username = postgres
spring.datasource.password = 1234
  • 다음과 같이 application.properties에 내가 연결하고 싶은 DB 정보 변수를 등록한다.

 

2. DataSource 빈 설정

@Configuration
public class DataSourceConfig {
	
	@ConfigurationProperties(prefix = "spring.datasource")
	@Bean(name="dataSource")
	public DataSource dataSource() {
		return DataSourceBuilder.create().build();   
	}

}
  • MyBatis의 SqlSession을 생성할 때 데이터베이스 정보 객체인 DataSource가 필요하다.
  • DataSource는 application.properties에 등록한 DB 정보를 가지고 생성한다.
  • @ConfigurationProperties(prefix = "spring.datasource") 어노테이션을 특정 객체에 붙이면 application.properties 에 있는 "spirng.datasource" 로 시작하는 모든 변수의 값을 해당 객체에 바인딩 시킨다는 뜻이다.

 

3. MyBatis Mapper 설정

@Configuration
@MapperScan(basePackages="*")
public class MyBatisConfig {

	@Bean
	public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws IOException {
		PathMatchingResourcePatternResolver pmrpr = new PathMatchingResourcePatternResolver();
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(dataSource);
		sqlSessionFactoryBean.setConfigLocation(pmrpr.getResource("classpath:/config/sqlmap/sql-mapper-config.xml"));
		sqlSessionFactoryBean.setMapperLocations(pmrpr.getResources("classpath:/config/sqlmap/mappers/*.xml"));
		return sqlSessionFactoryBean;
	}

	@Bean
	public SqlSessionTemplate sqlSession(SqlSessionFactory sqlSessionFactory) {
		return new SqlSessionTemplate(sqlSessionFactory);
	}

}
  • MyBatis의 xml 문서 내 쿼리를 Mapper 인터페이스 내 정해둔 메소드로 이용하기 위해서는 SqlSession 객체가 필요하다. 위에서 등록한 DataSource를 가지고 SqlSession 빈을 생성하고 등록하는 코드이다.
  • 등록된 SqlSession 빈이 쿼리를 모아놓은 xml 문서에서 원하는 쿼리를 호출하게 해주는 것이다.
  • @MapperScan(basePackages=""): Mapper 인터페이스로 인식될 경로를 지정한다.
  • sqlSessionFactoryBean.setConfigLocation(): MyBatis 부가 설정이 담긴 경로를 지정한다. 해당 파일에서 별칭 설정 등의 부가 설정이 가능하다.
  • sqlSessionFactoryBean.setMapperLocations(): 실행될 쿼리들을 모아놓은 xml 파일을 지정한다. 보통 최상위 폴더까지 가서 *.xml로 지정한다.
  • 이렇게 설정이 되어야 MyBatis를 사용할 수 있다.

 

4. MyBatis 부가 설정 파일 생성

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <typeAliases>
		<typeAlias alias="boardMapper" type="egovframework.example.board.service.BoardMapper"/>
    </typeAliases>
</configuration>
  • MyBatis 부가 설정을 해주는 파일이다. 파일 이름은 보통 sql-mapper-config.xml이다.
  • 쿼리를 모아놓은 xml 파일에서 사용할 별칭들을 설정할 수 있다. typeAliases를 통해 기다란 패키지에서 부터 클래스 까지의 경로를 변수 이름으로 설정한다.

 

5. Mapper XML 파일 생성

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="egovframework.example.board.service.BoardMapper">
	
	<insert id="registBoard" parameterType="Map">
		INSERT INTO BOARD(TITLE, WRITER, CONTENT, REG_DATE, COUNT)
		VALUES(#{title}, #{writer}, #{content}, NOW(), 0)
	</insert>


</mapper>
  • Mapper 인터페이스에서 호출할 쿼리들을 작성한 xml 문서다.
  • insert, select, update, delete 등 여러 쿼리를 등록하고 각 쿼리의 id 값은 Mapper 인터페이스 메소드 명과 일치시켜야 한다.
  • 각 쿼리의 parameterType과 resultType 속성 값이 있는데 파라미터를 받을 때의 데이터 타입과 쿼리의 결과가 출력될 때의 데이터 타입을 지정한다.

 

6. Mapper 인터페이스 생성

@Mapper("boardMapper")
public interface BoardMapper {
	
	void registBoard(Map<String, Object> board);
}
  • 컨트롤러 및 서비스 단에서 호출할 메소드를 정의한다. 호출하고 싶은 쿼리의 id 값과 일치하도록 메소드를 생성하면 된다.
728x90
728x90

회사에서 PPT 문서 작업을 하다보면 슬라이드에 이미지를 많이 넣게 되는데

 

고화질 이미지를 넣고 시간이 좀 지나면 화질이 완전히 깨져버린다....

 

PPT 문서에 들어가서 상단 '파일' 탭을 통해 옵션으로 들어가면 지금 보이는 창이 뜰 것이다.

 

여기서 좌측 메뉴의 '고급' 탭을 통해 '파일의 이미지 압축 안 함' 체크를 선택하면 된다.

 

저 체크가 해제되어 있으면 어떤 이미지든 간에 고화질이면 무조건 내부적으로 압축을 진행해서 화질이 깨지는 것이다.

728x90
728x90

사람을 인식하고 해당 신체의 모든 부위 얼굴, 팔, 다리, 몸통의 움직임을 추적하는

MediaPipe 라이브러리를 사용하여 팔 운동 트레이닝 로직(AI 이두컬 트레이너)을 만들어보자.

 

 

1. MediaPipe, OpenCV 설치

!pip install mediapipe opencv-python
  • 이번 작업에서 가장 중요한 두 가지 라이브러리를 설치한다.

 

2. 해당 라이브러리 임포트

import cv2 # openCV
import mediapipe as mp
import numpy as np
mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose
  • mp_drawings: 신체를 인식하여 여러 부위를 감지 및 추적하는 것을 시각화 할 때 사용하는 유틸리티
  • mp_pose: 실제 신체를 감지함과 동시에 감지 및 추적된 신체 부위의 위치 등을 추출해준다.

 

3. 감지 및 추적 될 신체 부위의 라벨링 값 확인

mp_pose.POSE_CONNECTIONS 
  • MediaPipe의 자체 알고리즘이 예측 결과로 신체에 그림을 그리기 위해 설정한 라벨링 값을 출력해보자.
frozenset({(0, 1),
           (0, 4),
           (1, 2),
           (2, 3),
           (3, 7),
           (4, 5),
           (5, 6),
           (6, 8),
           (9, 10),
           (11, 12),
           (11, 13),
           (11, 23),
           (12, 14),
           (12, 24),
           (13, 15),
           (14, 16),
           (15, 17),
           (15, 19),
           (15, 21),
           (16, 18),
           (16, 20),
           (16, 22),
           (17, 19),
           (18, 20),
           (23, 24),
           (23, 25),
           (24, 26),
           (25, 27),
           (26, 28),
           (27, 29),
           (27, 31),
           (28, 30),
           (28, 32),
           (29, 31),
           (30, 32)})
  • 출력해서 나온 결과를 아래 그림과 비교하면 각 숫자가 어떤 역할을 하는지 알 수 있다. 예를 들어, 코(0)는 눈(1, 4)와 연결된 것을 확인해 볼 수 있다.

 

4. 카메라 내 신체를 감지하여 그림을 그려보기

cap = cv2.VideoCapture(0) # 웹캠 불러오기
## 미디어파이프 인스턴스 설정 (신뢰도는 0.5, 연속 프레임 신뢰도 0.5)
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose: 
    # 미디어파이프를 효율적으로 사용하기 위해 with as 구문 사용

    while cap.isOpened():
        ret, frame = cap.read()
        
        # 이미지 다시 칠하기: 미디어 파이프에 전달하기 위해 BGR -> RGB로 변경
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        image.flags.writeable = False # 이미지 픽셀 값 수정 막기
				# pose.process()의 인자로 이미지를 넘겨주면 해당 이미지의 픽셀값이 수정 될 수도 있음
      
        # 신체를 감지함, 이미지 속의 신체 부위의 위치를 감지 및 움직임을 추적
        results = pose.process(image)
    
        # Recolor back to BGR
        # 이미지를 다시 RGB 에서 BGR 로 변경
        image.flags.writeable = True # 이미지 픽셀 값 수정 허용
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        
        # Render detections
        # 이미지나 상에 감지된 랜드마크를 그린다
        mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
                                mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=2), 
                                mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2) 
                                 )     
        # mp_drawing.draw_landmarks(이미지, 이미지의 감지된 신체부위 위치 및 관절, 연결할 랜드마크 선들, 선 스타일)
        
        cv2.imshow('Mediapipe Feed', image)

        if cv2.waitKey(10) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()
  • 요약 하자면 카메라를 불러와서 각 프레임 단위로 신체를 인식하고 여러 신체 부위를 감지 및 탐지하여 해당 이미지(프레임)에 관절라인, 즉 그림을 그린다.
  • min_detection_confidence:신체가 감지될 때 필요한 최소 신뢰도 설정.
  • min_tracking_confidence: 신체가 한번 감지 된 후 연속 프레임에서 해당 신체를 추적 할 때 필요한 최소 신뢰도 설정
  • MediaPipe의 신체 감지 알고리즘이 특정 부위를 50% 이상의 신뢰도(정확도)로 측정 되었을 때 해당 부위를 유효하게 간주함. 이 값을 너무 낮게 설정하면 잘못된 감지가 증가할 수 있으며, 너무 높게 설정하면 유효한 포즈를 놓칠 수 있다.
  • results = pose.process(image): 해당 프레임을 가져와 실제로 신체 감지 및 추적이 이루어진다. result 변수에는 감지 및 추적에 대해 다양한 정보가 들어간다.
  • mp_drawing.draw_landmarks(이미지, 이미지의 감지된 신체부위 위치 및 관절, 연결할 랜드마크 선들, 선 스타일): 프레임에 본격적으로 그림을 그린다.

 

5. 왼쪽 어깨, 팔꿈치, 팔목에 대한 좌표 추출

with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
    while cap.isOpened():
        ret, frame = cap.read()
        
        results = pose.process(image)

        try:
            landmarks = results.pose_landmarks.landmark
						# 각 신체부위에 대한 좌표 추출
            print(landmarks)
        except:
            pass
# 왼쪽 어깨 x,y 좌표
print(landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x)
print(landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y)
0.7106555700302124
0.8390206694602966
  • 팔을 굽혔다 폈다 하는 각도를 계산하기 위해서는 어깨, 팔꿈치, 팔목 이 3가지의 좌표가 필요하다.
shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
[0.7106555700302124, 0.8390206694602966],
 [0.781584620475769, 1.1789385080337524],
 [0.796806812286377, 1.4664262533187866]

 

6. 팔 각도 계산

def calculate_angle(a,b,c):
    # 각 값을 받아 넘파이 배열로 변환
    a = np.array(a) # First
    b = np.array(b) # Mid
    c = np.array(c) # End
    
    # 세 좌표를 가지고 각도로 변환하는 코드
    radians = np.arctan2(c[1]-b[1], c[0]-b[0]) - np.arctan2(a[1]-b[1], a[0]-b[0])
    angle = np.abs(radians*180.0/np.pi)
    
    # 180도가 넘으면 360에서 뺀 값을 계산한다.
    if angle > 180.0:
        angle = 360-angle
        
    return angle
  • 해당 함수에 어깨, 팔꿈치, 손목의 좌표를 넣어보자.
calculate_angle(shoulder, elbow, wrist)
156.64536266620797

 

7. 팔 운동 카운트 기능 삽입

shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
            elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
            wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
            # 왼쪽 어깨, 팔꿈치, 팔목 등 감지된 랜드마크의 좌표 값들을 계산
            
            # 해당 좌표들에 대해 각도를 계산
            angle = calculate_angle(shoulder, elbow, wrist)
            
            # 이미지에 각도를 팔꿈치 위치에 표시한다.
            # cv2.putText(): 이미지 위에 텍스트를 넣는 함수
            cv2.putText(image, str(angle), 
                           tuple(np.multiply(elbow, [640, 480]).astype(int)), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA
                                )
            
            # 만약 각도가 160도가 넘으면, down 상태
            # 각도가 30도 보다 작고 down이면 count 증가, up 상태
            if angle > 160:
                stage = "down"
            if angle < 30 and stage =='down':
                stage="up"
                counter +=1
                print(counter)
  • 카메라의 프레임마다 팔의 각도를 가져오는 angle 변수를 가지고 if문을 작성하여 카운트를 올려주는 코드를 작성한다.
  • 각도가 160도라면 팔을 구부리지 않고 쭉 펴져 있는 상태이기 때문에 운동 횟수를 늘리지 않고, 30도처럼 팔을 다 굽힌 상태라면 횟수를 늘린다.

 

※ 전체 코드

def calculate_angle(a,b,c):
    # 각 값을 받아 넘파이 배열로 변환
    a = np.array(a) # First
    b = np.array(b) # Mid
    c = np.array(c) # End
    
    # 세 좌표를 가지고 각도로 변환하는 코드
    radians = np.arctan2(c[1]-b[1], c[0]-b[0]) - np.arctan2(a[1]-b[1], a[0]-b[0])
    angle = np.abs(radians*180.0/np.pi)
    
    # 180도가 넘으면 360에서 뺀 값을 계산한다.
    if angle >180.0:
        angle = 360-angle
        
    return angle





cap = cv2.VideoCapture(0)

# Curl counter variables
counter = 0 
stage = None

## Setup mediapipe instance
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
    while cap.isOpened():
        ret, frame = cap.read()
        
        # Recolor image to RGB
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        image.flags.writeable = False
      
        # Make detection
        results = pose.process(image)
    
        # Recolor back to BGR
        image.flags.writeable = True
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        
        # Extract landmarks
        try:
            landmarks = results.pose_landmarks.landmark
            
            # Get coordinates
            shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
            elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
            wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
            # 왼쪽 어깨, 팔꿈치, 팔목 등 감지된 랜드마크의 좌표 값들을 계산
            # x,y,z까지 총 3개의 값이 있 는데 2차원의 값만 얻음
            
            # Calculate angle
            # 해당 좌표들에 대해 각도를 계산
            angle = calculate_angle(shoulder, elbow, wrist)
            
            # Visualize angle
            # 이미지에 각도를 팔꿈치 위치에 표시한다.
            # cv2.putText(): 이미지 위에 텍스트를 넣는 함수
            cv2.putText(image, str(angle), 
                           tuple(np.multiply(elbow, [640, 480]).astype(int)), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA
                                )
            
            # Curl counter logic
            # 만약 각도가 160도가 넘으면, down 상태
            # 각도가 30도 보다 작고 down이면 count 증가, up 상태
            if angle > 160:
                stage = "down"
            if angle < 30 and stage =='down':
                stage="up"
                counter +=1
                print(counter)
                       
        except:
            pass
        
        # Render curl counter
        # Setup status box
        cv2.rectangle(image, (0,0), (225,73), (245,117,16), -1)
        
        # Rep data
        cv2.putText(image, 'REPS', (15,12), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,0), 1, cv2.LINE_AA)
        cv2.putText(image, str(counter), 
                    (10,60), 
                    cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255), 2, cv2.LINE_AA)
        
        # Stage data
        cv2.putText(image, 'STAGE', (65,12), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,0), 1, cv2.LINE_AA)
        cv2.putText(image, stage, 
                    (60,60), 
                    cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255), 2, cv2.LINE_AA)
        
        
        # Render detections
        mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
                                mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=2), 
                                mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2) 
                                 )               
        
        cv2.imshow('Mediapipe Feed', image)

        if cv2.waitKey(10) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

 

출처1: https://giveme-happyending.tistory.com/202

 

mediapipe 사용하여 팔굽히기 모션 인식하기

공부에 참고한 링크 https://youtu.be/06TE_U21FK4 개발환경 운영체제: Window 10 64 bit 개발언어: Python 3.11 개발 툴: Jupyter Lab 추가 패키지: mediapipe, opencv-python 라이브러리 & 모듈 설치 MediaPipe, opencv-python 설치

giveme-happyending.tistory.com

출처2: https://www.youtube.com/watch?v=06TE_U21FK4&t=642s

 

728x90
728x90

스프링 부트에서는 공식적으로 JWT 인증 방식을 스프링 시큐리티와 함께 구현하는 것을 권장하고 있다.

 

스프링 시큐리티란?

스프링 시큐리티는 스프링 프레임워크 기반으로 제작한 프로젝트의 인증과 권한 기능을 담당하는 프레임워크다.

 

어플리케이션의 로그인, 회원가입 등의 기능을 자체적으로 처리하는 기능이 내장되어 있기 때문에

이런 식으로 스프링 시큐리티 없이 로그인 로직을 직접 작성하였다면,

if (param.get("USER_PW").toString().equals(member.get("USER_PW").toString())) {
		system.out.println("로그인 성공!");
		return "main";
	}

 

 

스프링 시큐리티 사용 시 이렇게 한줄 코드로 로그인 처리를 할 수 있다.

Member member = loginService.loadUserByUsername(param.get("USER_ID"));

템플릿 엔진을 사용한다면 로그인이 완료된 후에 어떤 뷰로 이동할지도 Config 파일에서 설정이 가능하다.

 

 

덧붙여 로그인한 사용자만 특정 API를 이용 할 수 있게

기존 스프링의 인터셉터를 직접 작성하였다면,

String requestUrl = request.getRequestURL().toString();

if(requestUrl.contains("/gis")) {

	if("ATH0003".equals(session.getAttribute("USER_AUTH"))){
		return true;
		
	} else {

		printwriter.print("<script>alert('접근 권한이 없습니다.');location.href='/replotting/main';</script>");
		printwriter.flush();
		printwriter.close();
    return false;
	}

 

 

이렇게 한 줄 코드로 인터셉터 기능을 구현할 수 있다.

request.requestMatchers("/gis").hasRole("ATH0003") // ATH0003 권한이 있는 사용자만 허용

 

 

이게 스프링 시큐리티의 매력이다.

일단 JWT를 중점으로 작업하기 위해 스프링 시큐리티를 최소화하여 구현해보자.

스프링에서 구현해야 하는 JWT의 동작 원리는 다음과 같다.

  1. 로그인 요청이 들어오면 로그인 성공 시 JWT 토큰을 클라이언트에게 발급
  2. 클라이언트가 토큰을 발급 받고 인증이 필요한 API에 접속 시 해당 토큰이 유효한지 검증

 

간단하게 위의 2가지 과정으로 인증이 이루어지는데 해당 과정을 구현하기 위해서 코드를 작성해야 할 파일이 3개가 있다.

  1. JWT 토큰을 생성해주는 토큰 생성 클래스 (JwtProvider.java)
  2. 인증이 필요한 API 접속 시 토큰을 검증하고 인증을 처리하는 필터 클래스 (JwtAuthenticationFilter.java)
  3. 해당 토큰 검증 필터를 적용 시킬 수 있게 설정하는 스프링 시큐리티 설정 클래스(WebSecurityConfig.java)

해당 정보를 바탕으로 구현해보자.

 

 

1. 라이브러리 추가

  implementation 'org.springframework.boot:spring-boot-starter-security'

	implementation 'io.jsonwebtoken:jjwt-api:0.11.5' 
	implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
	implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
  • 해당 라이브러리들을 설치한다.

 

 

2. application.properties 파일에 보안 키 설정

jwt.secret = 7b64445f014cec357c8b8e7e175320997c4f2ed468f5fe7cee1d9a5cf231f6bd
  • JWT 토큰을 생성하고 검증할 때 application.properties 안에 있는 시크릿 키를 인증하여 생성과 검증이 이루어진다.
  • 해당 키는 토큰 발행 및 검증을 하기 전에 서버 내의 1차적인 보안을 위한 키라고 생각하면 된다.
  • 해당 키는 base64 랜덤 값 32자리로 이루어져 있으며, 해당 키를 생성하기 위해서는 리눅스 터미널에서 openssl rand -hex 64 명령어를 입력한다. (Git Bash에서도 가능)

 

 

3. TokenInfoDTO 작성

@Builder
@Data
@Getter
@AllArgsConstructor
public class TokenInfo { // 클라이언트에 토큰을 보내기 위한 DTO
    private String grantType;  // 토큰 타입
    private String accessToken; // 접근 토큰
    private String refreshToken; // 접큰 토큰이 만료 될 시 재발급 받기 위한 토큰
}
  • 로그인에 성공 시 토큰을 발급한 후 클라이언트에게 보낼 토큰 정보 DTO다.
  • 변수 grantType 에는 보통 “Bearer”라는 단어가 들어가는데 이는 해당 토큰이 JWT 토큰이라는 것을 나타내는 식별자다.
  • JwtTokenProvider 클래스에서 토큰을 생성하고 해당 DTO를 만들 것이다.

 

 

4. JwtTokenProvider 작성

@Slf4j
@Component
public class JwtTokenProvider { // 토큰을 생성하고 검증한다.
    private final Key key;

    public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
        // application.properties에서 secret key를 가져온다.
        // 토큰 발행 및 검증을 하기 전에 서버 내의 1차적인 보안을 위한 키임.
        byte[] keyBytes = Base64.getDecoder().decode(secretKey);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    public TokenInfo generateToken(Member member) { // 토큰을 생성한다.
        Calendar cal1 = Calendar.getInstance();
        cal1.add(Calendar.HOUR_OF_DAY, 2);
        Date accessTokenExpiration = new Date(cal1.getTimeInMillis()); // 현재부터 2시간 뒤의 날짜 및 시간

        Calendar cal2 = Calendar.getInstance();
        cal2.add(Calendar.DATE, 14);
        Date refreshTokenExpiration = new Date(cal2.getTimeInMillis()); // 현재부터 2주 뒤의 날짜 및 시간

        // Access Token 생성
        String accessToken = Jwts.builder()
                .setHeader(createHeader())
                .setClaims(createClaims(member, "access"))
                .setSubject(member.getId()) // 페이로드의 sub 설정, 이 토큰의 주인이 누구인지?
                .signWith(key, SignatureAlgorithm.HS256) // 서명 부분에 진행할 암호화 알고리즘 설정
                .setExpiration(accessTokenExpiration) // 엑세스 토큰의 만료 기한 설정
                .compact();

        String refreshToken = Jwts.builder()
                .setHeader(createHeader())
                .setClaims(createClaims(member, "refresh"))
                .setSubject(member.getId()) // 페이로드의 sub 설정, 이 토큰의 주인이 누구인지?
                .signWith(key, SignatureAlgorithm.HS256) // 서명 부분에 진행할 암호화 알고리즘 설정
                .setExpiration(refreshTokenExpiration) // 리프레쉬 토큰의 만료 기한 설정
                .compact();

        return TokenInfo.builder() // 위에서 생성한 토큰을 포함하여 토큰 정보 반환
                .grantType("Bearer")
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .build();
    }

    public boolean validateToken(String token, HttpServletResponse response) throws IOException { // 토큰을 검증한다.
        try {
            Claims claims = parseClaims(token); // 토큰을 검증함과 동시에 복호화하여 클레임을 가져온다.

            return true;
        } catch (SecurityException | MalformedJwtException e) { // 위에서 검증함과 동시에 예외처리를 한다.
            log.info("Invalid JWT Token", e);
        } catch (ExpiredJwtException e) {
            log.error("토큰의 기한이 만료되었습니다.");
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT Token", e);
        } catch (IllegalArgumentException e) {
            log.info("JWT claims string is empty", e);
        }
        return false;

    }

    private Claims parseClaims(String accessToken) {  // 토큰을 복호화하여 클레임 부분을 반환한다.
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(accessToken) // 실제 토큰 검증이 일어나는 부분
                .getBody();
    }

    private Map<String, Object> createHeader() { // 토큰의 헤더 부분을 만든다.
        Map<String, Object> header = new HashMap<>();
        header.put("typ", "JWT");
        header.put("alg", "HS256");
        return header;
    }

    private Map<String, Object> createClaims(Member member, String classification) { // 토큰의 클레임을 만든다.
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", member.getId());
        claims.put("nm", member.getName());
        claims.put("seq", member.getSeq());
        claims.put("typ", classification);
        return claims;
    }

    public String getUserIdfromToken(String token) { // 토큰에서 유저의 아이디를 반환한다.
        Claims claim = parseClaims(token);
        return (String) claim.get("id");
    }
}
  • generateToken(): 엑세스 토큰과 리프레쉬 토큰을 만들어서 빌더패턴으로 정의된 TokenInfo DTO에 담는다. 토큰 내부의 헤더, 페이로드, 클레임, 서명 부분을 직접 setHeader() , setClaims() , signWith() 메소드들을 통해서 설정하고 setExpiration() 메소드를 통해 만료기한을 설정한다. signWith() 메소드는 시크릿키를 통해 인증이 필요해서 첫 번째 파라미터로 넘겨준다.
  • validateToken(): 토큰을 검증하는 메소드다. 클라이언트가 인증이 필요한 API 호출 시 바로 작동하는 필터 클래스에서 호출하기 위해 JwtTokenProvider 클래스에 정의했다. 해당 코드를 보면 클레임을 복호화 하는 parseClaims() 메소드를 호출하고 있는데 이 메소드 안에 실질적으로 토큰이 유효한지 검증하는 코드가 담겨있기 때문이다. 해당 메소드 호출 후 예외가 발생하지 않는다면 유효한 토큰이고, 문제가 있는 토큰이라면 각 문제에 따라 다른 예외들을 발생시킨다.
  • parseClaims(): 토큰의 클레임 부분을 복호화하는 코드이다. 해당 코드 내에서 .setSigningKey(key) 를 통해 시크릿 키 인증을 하고, .parseClaimsJws(accessToken) 를 통해 클레임 복호화 및 토큰 검증을 실행한다.
  • createHeader(): 토큰의 헤더 부분을 만든다. 헤더 값은 typ: JWT, alg: HS256 고정이다.
  • createClaims(): 토큰의 클레임 부분을 만든다. 클레임에는 해당 토큰의 사용자 정보가 들어간다. 토큰이 탈취 당할 시를 대비해 회원의 간략한 정보만 넣는다. 여기서 넣어준 사용자 정보는 클라이언트 쪽에서 토큰을 복호화 하여 사용할 수 있다.

 

 

5. 로그인 로직 작성

public TokenInfo login(String username, String password) {
    Member member = memberMapper.getMember(username);

    log.info("찾은 아이디: {}", member.getId());

    log.info("DB 암호화 비밀번호: {}", member.getPw());

    if (!passwordEncoder.matches(password, member.getPw())) {  // 요청 비밀번호와 DB 안의 비밀번호와 일치하는지 확인
        throw new PasswordNotMatchException(); // 일치하지 않으면 예외처리로 인해 중단
    }

    TokenInfo tokenInfo = jwtTokenProvider.generateToken(member); // 로그인에 성공한 회원에 대해 토큰 생성

    return tokenInfo;
}
  • 토큰을 발급 받기 전 아이디와 비밀번호 유효성 검증을 해야 하지 않겠는가 로그인 서비스 클래스에 해당 로직을 손수 짜준다.
  • 원래는 스프링 시큐리티의 기능을 이용하여 구현해야 한다.

 

 

6. JwtAuthenticationFilter 작성

@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtTokenProvider jwtTokenProvider;
    private AuthenticationManager authenticationManager;

    @Override
    public void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws IOException, ServletException {
        // 1. Request Header에서 JWT 토큰 추출
        String token = resolveToken(request);

        log.info("필터에 들어온 토큰: {}", token);

        // 2. vaidateToken으로 토큰 검증
        try {
            if(token != null && jwtTokenProvider.validateToken(token, response)) {
                // 토큰이 유효할 경우 토큰에서 클레임 부분에 유저 아이디를 가지고 와서 SecurityContext에 저장
                String userId = jwtTokenProvider.getUserIdfromToken(token);
                log.info("토큰의 주인: {}", userId);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userId, null, null);

                SecurityContextHolder.getContext().setAuthentication(authenticationToken);

                Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                log.info("해당 사용자 인증여부: {}", auth.isAuthenticated());
            }
        } catch (ExpiredJwtException e) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "토큰 만료");
        }
        filterChain.doFilter(request, response);

    }

    // Request Header에서 토큰 정보 추출
    private String resolveToken(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        if(StringUtils.hasText(token) && token.startsWith("Bearer")) {
            return token.substring(7);
        }
        return null;
    }
}
  • 스프링 시큐리티는 인증을 처리하기 위한 필터가 여러가지 있는데 그 중 OncePerRequestFilter를 상속 받아 Jwt 필터로 커스텀한다.
  • doFilterInternel() 안의 코드들이 필터로 인식되어 처리 된다.
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userId, null, null);

SecurityContextHolder.getContext().setAuthentication(authenticationToken); 
  • resolveToken(HttpServletRequest request) 메소드를 통해 요청 헤더에서 토큰 값을 뽑아온다.
  • jwtTokenProvider.validateToken() 을 통해 문제가 없는 토큰인지 검증이 완료되면 new UsernamePasswordAuthenticationToken(userId, null, null) 을 통해 jwtTokenProvider.getUserIdfromToken(token) 메소드로 뽑아온 유저 아이디를 인증이 완료된 인증 객체로 만들어준다.
  • 스프링 시큐리티에는 인증 객체라는 것이 존재한다. Authentication이라는 객체인데 이 객체에는 유저의 아이디 등을 포함한 유저의 정보가 들어있다. UsernamePasswordAuthenticationToken 객체는 이를 상속받은 객체이다.
  • 인증객체로 만들어준 UsernamePasswordAuthenticationToken을 SecurityContextHolder.getContext().setAuthentication(authenticationToken) 을 통해 시큐리티 컨텍스트에 넣어준다. SecurityContext는 Authentication 객체의 집이다. 여기에 들어간 인증객체는 스프링 시큐리티가 인증이 된 사용자라고 자동으로 인식을 한다.

 

 

7. 스프링 시큐리티 설정 파일 작성

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig{
    private final JwtTokenProvider jwtTokenProvider;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        // 스프링 시큐리티 접근 보안 설정
        return httpSecurity
                .csrf(AbstractHttpConfigurer::disable)
                .cors(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(request -> {
                    // 로그인 요청에 대해서는 모두 접근 허용
                    request.requestMatchers("/member/login").permitAll()
                            .requestMatchers("/api").permitAll()
                            // 이 밖에 모든 요청들은 인증을 필요로 한다.
                            .anyRequest().authenticated();
                }).addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
                        UsernamePasswordAuthenticationFilter.class).build();
                        // 요청 사용자에게 토큰이 있는지 먼저 검사하기 위해 JWT 필터를 먼저 거치도록 설정
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
  • 스프링 시큐리티 설정 파일에는 일반 스프링 설정 파일과 다르게 @EnableWebSecurity 라는 어노테이션을 붙여야 한다. 동시에 웹 인증 및 보안에 대해 스프링 시큐리티가 모든 것을 관여하게 된다.
  • SecurityFilterChain 이라는 객체를 빈으로 등록해줌으로서 웹의 접근 권한을 설정하고 해당 접근 권한이 있는지 판단하기 위한 필터를 등록해준다.
  • request.requestMatchers("/member/login").permitAll() 은 “/member/login” 매핑에 대해 인증 여부든 상관 없이 모두 접근을 허용한다는 것이다. 로그인은 어떤 사람이든 해야 하지 않겠는가
  • .anyRequest().authenticated() 은 앞서 설정해준 매핑들을 제외한 모든 요청들은 인증이 되어야 한다. 로그인을 진행하고 필터 클래스에서 인증이 완료된 Authentication 객체가 시큐리티 컨텍스트 안에 들어가야 접근할 수 있다.
  • .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class).build() 해당 코드는 우리가 커스텀 한 JWT 필터를 UsernamePasswordAuthenticationFilter 전에 배치한다는 뜻이다. 보통 이 필터가 가장 먼저 실행되기 때문에 JWT 필터를 먼저 실행하게 하는 것이다.
728x90

[Git] 자주 사용하는 명령어들

태이니즘
|2024. 2. 24. 20:16
728x90

1. git clone

git clone https://<본인계정 이름>:<토큰 값>@<github 저장소 주소>
  • 특정 깃허브 리포지토리를 연결함과 동시에 메인 브랜치 커밋 내역들을 로컬의 커밋내역으로 병합한다.
  • 1. git remote add origin <github 주소>: 해당 주소를 원격 저장소로 연결
  • 2. git pull origin main: 원격 저장소의 메인 브랜치만 다운로드 하여 커밋내역을 병합한다.
  • 위 2가지가 합쳐진 명령어임

 

2. git pull

git pull origin <브랜치 이름>
  • 연결된 원격 저장소의 특정 브랜치의 커밋 내역들을 로컬에 현재 접속되어 있는 브랜치에 병합한다.
  • 1. git fetch origin: 원격 저장소의 모든 브랜치들을 다운 받는다.
  • 2. git merge origin/<브랜치 이름>: 다운받은 원격 저장소의 메인 브랜치를 병합한다.                                                 
  • 위 2가지가 합쳐진 명령어임

 

3. git checkout 

git checkout -b <브랜치 이름>
  • 특정 브랜치를 생성하면서 해당 브랜치로 이동한다.

 

4. git push

git push origin <브랜치 이름>
  • 원격 저장소의 특정 브랜치에 현재 로컬에서 접속되어 있는 브랜치의 커밋 내역을 병합한다.
  • 원격 저장소에 해당 브랜치가 없으면 자동으로 생성된다.
git push -f <브랜치 이름>
  • 현재 접속되어 있는 로컬 브랜치를 강제로 push 한다.
  • git push를 일반적으로 입력 할 때 오류가 생기는 부분이 있는데, 로컬에서 어떤 작업을 새로 했다면 그 새로 작업을 하기전의 커밋 내역들이 원격 저장소의 브랜치의 커밋 내역들과 동일해야 한다.
  • 이 조건이 충족이 되어야 push를 하면 Fast-Forward Merge나 3-Way Merge를 할텐데 이게 안되면 아예 커밋 시작점이 달라져 버리기 때문에 허용되지 않는다.
  • 이럴 경우 커밋 내역을 그냥 덮어씌어 버려야 하는데 이 때 git push -f 명령어를 사용한다.

 

5. git rebase

git rabase -i HEAD~<현재 HEAD를 포함하여 수정하고 싶은 커밋 로그의 갯수>
  • 특정 커밋 로그의 범위를 압축하여 원하지 않는 커밋 로그를 삭제하는 명령어.
  • ex) 1. 로그인 기능 만들다 잠와서 집감 - 2. 다음날 로그인 기능 만들다 화장실감 - 3. 로그인 기능 완료(HEAD)
    이 3개의 로그인 작업에 대한 커밋 로그가 있을 때 1번, 2번 로그가 마음에 들지 않을 때
    git rebase -i HEAD~3을 입력한 후 에디터에서 1. pick - 2.squash - 3.squash 수정

 

6. git reset

git reset --hard <돌아가고 싶은 지점의 해시 인덱스 번호>
  • git reflog 를 입력하면 내가 했던 모든 작업들, 예를 들면 브랜치 이동, 커밋 등의 로그들이 남는데 해당 특정 부분으로 시간을 돌리고 싶을 때 사용한다.
  • git reflog 출력 결과에 나오는 해시 인덱스를 입력하면 그곳으로 돌아갈 수 있다.
  • reset도 soft, hard 등 다양하지만 보통 hard를 많이 사용한다. 위험하지만 내가 뭔가 잘못한게 있어 돌아가고 싶은 확신이 있을 때 사용
728x90

'Git' 카테고리의 다른 글

[Git] 강제 Pull로 내 로컬 커밋내역 덮어 씌우기  (0) 2024.07.19