centos7 time 설정

Linux 2024. 4. 15. 17:20

AWS ec2 에서 최근에는 ntp보다 chrony를 많이 사용한다.

 

1. sudo vi /etc/chrony.conf 에서
    server 169.254.169.123 prefer iburst
를 넣는다. 1줄만 있어도 된다.

 

2. sudo systemctl restart chronyd

 

 

3. chronyc sources -v   

  -> 소스서버가 제대로 반영되었는지 확인 (check)

 

4. date 로 시간확인.

 

 

혹시 시간이 다르면 수정 방법.

$sudo timedatectl set-ntp false

$sudo timedatectl set-time "2025-08-19 15:44:00"  (맞는시간으로 설정)

 

$date 로 확인해보고 맞으면

그 후에 다시 ntp살리기.
$sudo timedatectl set-ntp true

$sudo systemctl restart chronyd 

Posted by yongary
,

 

단, https에서만 동작한다.

 

async function copyImageToClipboard(imageUrl) {
    try {
        const response = await fetch(imageUrl);
        const blob = await response.blob();
        await navigator.clipboard.write([
            new ClipboardItem({
                [blob.type]: blob
            })
        ]);
        console.log('Image copied to clipboard');
    } catch (err) {
        console.error('Failed to copy image: ', err);
    }
}

// 이미지 URL과 버튼 클릭 이벤트 핸들러
document.getElementById('copyImageButton').addEventListener('click', function() {
    const imageUrl = 'path/to/your/image.png';
    copyImageToClipboard(imageUrl);
});
Posted by yongary
,

 

로드밸런서 설정시

HTTPS:443 설정에서 아래처럼 하면 웹서버로는 http가 전달되며, 이를 SSL Termination 이라고 부름.

  • 이 설정에서, 트래픽의 "Default actions"을 웹 서버로 트래픽을 HTTP로 전달하도록 구성합니다. 여기서 SSL 종료가 발생하며, ALB가 SSL/TLS 암호화를 해제하고, 결과적으로 내부 서버에는 암호화되지 않은 HTTP 트래픽으로 데이터를 전송합니다. 이는 내부 네트워크의 보안을 기반으로, 성능 최적화와 구성의 단순화를 도모할 수 있습니다.
  • 대상그룹으로 전달해도 동일한 효과가 발생할 것으로 예상됩니다.

 

참고 - NGINX로 전달한 블로그 : https://medium.com/@vdongbin/aws-elb%EC%99%80-nginx%EB%A1%9C-https-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-736b8c5ee76  

 

Posted by yongary
,

CentOs7 에 폰트 설치

Linux 2024. 4. 12. 17:42

1.  폰트 위치는 /usr/share/fonts/arial 

이며, msttcore 폴더 밑에 ms 기본 폰트들이 있는지 확인가능.

 

 

2. 없을시 설치 방법.
  a.  $wget https://www.itzgeek.com/msttcore-fonts-2.0-3.noarch.rpm 
  b.  sudo rpm -Uvh msttcore-fonts-2.0-3.noarch.rpm

  c. 

sudo fc-cache -f -v

 

Posted by yongary
,

 

1. Apps Script 컨솔 왼쪽 메뉴에서 "서비스 +" 를 눌러 Drive API 를 추가한다.

 

2. 코드를 아래와 같이 작성하고

3. 배포 -> 새 배포 를 한 후

4. 실행을 누른다.

function convertDocxToPdf() {
  var folderId = 'myFolderID-folder의 URL 뒤쪽 끝부분임.'; // 변환할 파일이 들어 있는 폴더 ID
  var folder = DriveApp.getFolderById(folderId);
  var files = folder.getFilesByType(MimeType.MICROSOFT_WORD);

  while (files.hasNext()) {
    var file = files.next();
    Logger.log('File name:' + file.getName());

    //1. Microsoft Word 파일을 Google Docs로 변환
    var resource = {
      title: file.getName(),
      mimeType: MimeType.GOOGLE_DOCS,
      parents: [{id: folderId}]
    };

    var convertedDocFile = Drive.Files.copy(resource, file.getId());

    //2. Google Docs 형식으로 문서를 연 후 PDF로 변환
    var doc = DocumentApp.openById(convertedDocFile.getId());
    var blob = doc.getBlob().getAs('application/pdf');

    // 특정 폴더에 PDF 파일 저장
    var folder = DriveApp.getFolderById(folderId);
    var pdfFile = folder.createFile(blob.setName(convertedDocFile.getName().replace(/\.[^/.]+$/, "") + ".pdf"));

    // 생성된 PDF 파일의 URL 반환
    return pdfFile.getUrl();
  }
}

 

 

원격호출이 가능한 appScript만들기 => doGet(e)과 filename 파라미터를 추가.

 

function convertDoc2PdfByFilename(filename) {
  var folderId = 'myFOlderID'; // 변환할 파일이 들어 있는 폴더 ID
  var folder = DriveApp.getFolderById(folderId);
  // var files = folder.getFilesByType(MimeType.MICROSOFT_WORD);
  var files = folder.getFilesByName(filename);

  while (files.hasNext()) {
    var file = files.next();
    Logger.log('File name:' + file.getName());

    //1. Microsoft Word 파일을 Google Docs로 변환
    var resource = {
      title: file.getName(),
      mimeType: MimeType.GOOGLE_DOCS,
      parents: [{id: folderId}]
    };

    var convertedDocFile = Drive.Files.copy(resource, file.getId());

    //2. Google Docs 형식으로 문서를 연 후 PDF로 변환
    var doc = DocumentApp.openById(convertedDocFile.getId());
    var blob = doc.getBlob().getAs('application/pdf');

    // 특정 폴더에 PDF 파일 저장
    var folder = DriveApp.getFolderById(folderId);
    var pdfFile = folder.createFile(blob.setName(convertedDocFile.getName().replace(/\.[^/.]+$/, "") + ".pdf"));

    // 생성된 PDF 파일의 URL 반환
    return pdfFile.getUrl();
  }
}

function doGet(e) {
  var filename = e.parameter.filename;
  if (!filename) {
    return ContentService.createTextOutput("Filename parameter missing")
      .setMimeType(ContentService.MimeType.TEXT);
  }

  // 파일 이름을 사용하여 DOCX를 PDF로 변환하고, 생성된 PDF의 URL을 반환
  var pdfUrl = convertDoc2PdfByFilename(filename);
  if (pdfUrl) {
    return ContentService.createTextOutput("PDF 생성됨: " + pdfUrl)
      .setMimeType(ContentService.MimeType.TEXT);
  } else {
    return ContentService.createTextOutput("해당 이름의 파일을 찾을 수 없습니다.")
      .setMimeType(ContentService.MimeType.TEXT);
  }
}
Posted by yongary
,

springboot에서 pdf로 변환하다가 어려움을 겪는다면, 시도해 볼만한 방법 중하나로
구글드라이브와 구글의 appScript를 이용하는 방법이다.

 

Spring Boot 애플리케이션에서 Google 계정에 로그인하여 Google Apps Script를 실행하고, 결과로 생성된 PDF를 가져오는 것은 가능합니다. 이 과정은 크게 세 부분으로 나눌 수 있습니다: Google 계정 인증, Google Apps Script 실행, 생성된 PDF 파일 가져오기. 이 과정은 Google Cloud Platform(GCP)의 여러 서비스와 API를 사용하며, OAuth 2.0을 통한 인증이 필요합니다.

1. Google 계정 인증 (OAuth 2.0 사용)

  1. Google Cloud Console에서 새 프로젝트를 생성합니다.
  2. API 및 서비스 대시보드로 이동하여 OAuth 동의 화면을 구성합니다.
  3. 사용자 인증 정보를 클릭하여 새 OAuth 클라이언트 ID를 생성합니다. 애플리케이션 유형으로 웹 애플리케이션을 선택합니다.
  4. 승인된 리디렉션 URI로 애플리케이션의 리디렉션 엔드포인트를 추가합니다. 예를 들어, http://localhost:8080/oauth2/callback과 같습니다.
  5. 클라이언트 ID와 클라이언트 비밀을 안전하게 저장합니다.

2. Google Apps Script 실행

  1. Google Apps Script를 사용하여 PDF를 생성하고 Google Drive에 저장하는 스크립트를 작성합니다.
  2. Apps Script 프로젝트에서 공유 및 배포 > 배포를 위한 앱 만들기 > API 실행 가능을 활성화하고, 생성된 스크립트 ID를 저장합니다.

3. Spring Boot에서 Google API 사용

  1. Spring Boot 프로젝트에 google-api-client, google-oauth-client, google-api-services-script 라이브러리를 의존성에 추가합니다.
implementation 'com.google.api-client:google-api-client:1.30.9'
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.30.6'
implementation 'com.google.apis:google-api-services-script:v1-rev20200827-1.30.10'
gradleCopy code
implementation 'com.google.api-client:google-api-client:1.30.9' implementation 'com.google.oauth-client:google-oauth-client-jetty:1.30.6' implementation 'com.google.apis:google-api-services-script:v1-rev20200827-1.30.10'
  1. 사용자를 Google로 리디렉션하여 인증하고, Google로부터 리디렉션된 요청을 처리하는 컨트롤러를 구현합니다. 이 과정에서 GoogleAuthorizationCodeFlow를 사용하여 액세스 토큰을 얻습니다.
  2. 얻은 액세스 토큰을 사용하여 Apps Script API를 호출하고, 스크립트를 실행하여 PDF를 생성합니다.

4. 생성된 PDF 가져오기

  1. Google Drive API를 사용하여 Apps Script가 생성한 PDF 파일을 검색하고 다운로드합니다.
  2. 파일의 ID나 이름을 알고 있다면, Files.get 메서드로 파일 메타데이터를 가져온 후 GoogleCredential을 사용하여 인증된 Drive 서비스 객체로 파일을 다운로드할 수 있습니다.
Posted by yongary
,

springFramework의 BeanUtils말고 apache의 BeanUtils를 상속해서 NullAwareBeanUtils를 만들면 된다.

import org.apache.commons.beanutils.BeanUtilsBean;

public class NullAwareBeanUtilsBean extends BeanUtilsBean {

    @Override
    public void copyProperty(Object dest, String name, Object value) throws IllegalAccessException, InvocationTargetException {
        if(value == null) return; // null 값이면 복사를 수행하지 않음
        super.copyProperty(dest, name, value);
    }
}

사용법:
NullAwareBeanUtilsBean nullAwareBeanUtils = new NullAwareBeanUtilsBean();
nullAwareBeanUtils.copyProperties(destObject, sourceObject);
Posted by yongary
,

selenium은 웹 내부만 자동화 해주는데 웹에서 뭘 진행하다 보면 exe파일로 앱이 떠야하는 경우가 있는 사이트의 경우 앱 클릭까지 자동화하는 방법:

 

웹 애플리케이션 테스트를 위해 Selenium을 사용하는 경우, Selenium은 브라우저 내에서 동작하는 웹 페이지의 자동화에 최적화되어 있습니다. 그러나 웹 페이지에서 특정 동작을 통해 외부 애플리케이션(예: .exe 파일)을 실행해야 하는 경우, Selenium만으로는 이를 직접 제어할 수 없습니다.

웹에서 실행되는 애플리케이션(예: 파일 다운로드 후 실행 등)을 자동화하고자 한다면, 다음과 같은 방법을 고려할 수 있습니다:

1. 키보드 및 마우스 제어를 위한 도구 사용

  • AutoIt 또는 Sikuli: 이러한 도구들은 시스템 수준에서의 GUI 자동화를 가능하게 해줍니다. 예를 들어, 파일 다운로드 대화 상자의 조작, .exe 파일 실행, 애플리케이션 내의 특정 버튼 클릭 등의 작업을 스크립트를 통해 자동화할 수 있습니다.
  • 이 도구들은 스크립트로 작성된 시나리오에 따라 키보드 입력, 마우스 클릭 및 이동을 시뮬레이션할 수 있으며, 화면 상의 이미지를 기반으로 특정 요소를 인식하고 조작할 수 있는 기능도 제공합니다.
  • Selenium과 함께 사용하여, Selenium이 웹 페이지 내의 자동화를 담당하고 AutoIt 또는 Sikuli가 외부 애플리케이션의 자동화를 담당하도록 할 수 있습니다.

2. Selenium과 외부 도구의 통합

  • 웹 자동화 과정에서 특정 단계에서 외부 애플리케이션을 실행해야 한다면, Selenium 스크립트 내에서 AutoIt 또는 Sikuli 스크립트를 호출하여 실행할 수 있습니다.
  • 예를 들어, Python을 사용하는 경우, subprocess 모듈을 사용하여 AutoIt 스크립트를 실행할 수 있습니다.

예제: Python에서 AutoIt 스크립트 실행

 
import subprocess

# AutoIt 스크립트 실행
subprocess.run(['path/to/autoit/script.exe'])

 

예제:  AutoIt 스크립트

; 메모장을 실행합니다.
Run("notepad.exe")

; 메모장의 창이 활성화될 때까지 기다립니다.
WinWaitActive("제목 없음 - 메모장")

; 텍스트를 입력합니다.
Send("Hello, World!{ENTER}")
Send("이것은 AutoIt 스크립트에 의해 자동으로 입력된 텍스트입니다.")



; 사용자에게 경고창을 표시합니다.
MsgBox(0, "경고", "이것은 AutoIt 스크립트입니다!")



; 특정 응용 프로그램 실행
Run("your_application.exe")

; 응용 프로그램 창이 나타날 때까지 기다립니다.
WinWaitActive("응용 프로그램 제목")

; 'OK' 버튼을 클릭합니다.
ControlClick("응용 프로그램 제목", "", "[CLASS:Button; TEXT:OK]")

기본방법 

  1. AutoIt을 설치합니다.
  2. 파일을 .au3 확장자로 저장합니다.
  3. 저장한 스크립트 파일을 더블 클릭하거나, AutoIt 스크립트 편집기에서 직접 실행하여 테스트합니다.

AutoIt 스크립트는 GUI 기반의 애플리케이션 자동화에 매우 유용하며, 복잡한 작업을 간단한 스크립트로 자동화할 수 있습니다. 스크립트는 상황에 맞게 수정하여 사용해야 합니다. 예를 들어, "응용 프로그램 제목"이나 "your_application.exe"는 실제 사용하는 응용 프로그램의 이름이나 실행 파일 경로로 변경해야 합니다.

 

오토잇 설치 가이드: https://mypcinfo.tistory.com/23  (2019년 글) 

Selenium+오토잇:   https://testmanager.tistory.com/145  (2018년 글)

 

 

 

Python에서 Selenium과 Sikuli를 함께 사용하여 GUI 자동화 작업을 수행하는 예제.

 

Sikuli의 기능을 Python 스크립트 내에서 직접 호출하기 위해서는 Sikuli의 명령줄(Command Line) 인터페이스를 사용하거나 SikuliX API (Java 기반)를 Python과 연동하는 방법이 있습니다. 여기서는 가장 간단한 방법 중 하나인 Sikuli Script를 외부 프로세스로 실행하는 방법을 사용합니다.

이 예제에서는 Selenium을 사용하여 웹 브라우저를 자동으로 제어하고, 특정 조건에서 Sikuli 스크립트를 호출하여 브라우저 외부의 GUI 요소를 제어하는 상황을 가정합니다. 예를 들어, Selenium으로 파일 다운로드 버튼을 클릭한 후, Sikuli를 사용하여 나타나는 시스템 파일 다이얼로그를 제어할 수 있습니다.

준비사항

  1. Python, Selenium, SikuliX가 설치되어 있어야 합니다.
  2. SikuliX IDE를 사용하여 Sikuli 스크립트를 작성하고 저장합니다. 예를 들어, saveFile.sikuli이라는 이름으로 저장합니다.

Sikuli 스크립트 예제

  • saveFile.sikuli 스크립트는 다운로드 창에서 "저장" 버튼을 클릭하는 작업을 수행할 수 있습니다.
from selenium import webdriver
import subprocess
import time

# Selenium으로 웹드라이버 초기화 및 웹페이지 열기
driver = webdriver.Chrome()
driver.get('http://example.com')

# 웹 페이지에서 파일 다운로드를 시작하는 동작 수행 (Selenium 사용)
download_button = driver.find_element_by_id('download')
download_button.click()

# 파일 다운로드 다이얼로그 처리를 위해 Sikuli 스크립트 호출 (외부 프로세스 실행)
time.sleep(5)  # 다운로드 다이얼로그가 나타나기를 기다림
sikuli_script_path = 'C:\\path\\to\\saveFile.sikuli'
subprocess.call(['C:\\path\\to\\runsikulix.cmd', '-r', sikuli_script_path])

# 작업 완료 후 브라우저 닫기
driver.quit()

 

이 예제에서는 subprocess.call을 사용하여 Sikuli 스크립트를 외부 프로세스로 실행합니다. SikuliX를 설치한 디렉토리 내의 runsikulix.cmd(Windows의 경우)를 사용하여 Sikuli 스크립트를 실행합니다. Sikuli 스크립트 경로와 SikuliX 실행 파일 경로는 실제 환경에 맞게 수정해야 합니다.

주의사항

  • Sikuli 스크립트를 실행하기 전에, 필요한 경우 시스템 다이얼로그가 화면에 완전히 나타날 때까지 충분한 대기 시간을 제공해야 합니다.
  • Sikuli 스크립트의 이미지 인식은 실행 환경의 해상도, 디스플레이 설정, UI 테마 등에 영향을 받을 수 있으므로, 환경에 따라 인식률이 달라질 수 있습니다.

 

Posted by yongary
,

 

  • 설치:  pip install requests
  • 예제: 
import requests

# GET 요청 보내기
def send_get_request(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            # 요청이 성공하면 JSON 형식으로 반환된 데이터를 파싱하여 반환합니다.
            return response.json()
        else:
            print(f"Failed to send GET request. Status code: {response.status_code}")
            return None
    except Exception as e:
        print(f"An error occurred: {e}")
        return None

# POST 요청 보내기
def send_post_request(url, data):
    try:
        response = requests.post(url, json=data)
        if response.status_code == 200:
            # 요청이 성공하면 JSON 형식으로 반환된 데이터를 파싱하여 반환합니다.
            return response.json()
        else:
            print(f"Failed to send POST request. Status code: {response.status_code}")
            return None
    except Exception as e:
        print(f"An error occurred: {e}")
        return None

# 예제 URL
url = 'https://jsonplaceholder.typicode.com/posts/1'

# GET 요청 보내기
response_data = send_get_request(url)
if response_data:
    print("GET 요청 결과:")
    print(response_data)

# POST 요청 보내기 (예제 데이터)
post_data = {'title': 'foo', 'body': 'bar', 'userId': 1}
response_data = send_post_request(url, post_data)
if response_data:
    print("POST 요청 결과:")
    print(response_data)

 

물론 flask안에서도 동작한다.

Posted by yongary
,

 

pymongo가 간단.

 

  • 설치 :  pip install pymongo
  • 예제: 
from pymongo import MongoClient

# MongoDB에 연결합니다. 호스트 및 포트는 MongoDB 서버에 맞게 수정해야 합니다.
client = MongoClient('mongodb://localhost:27017/')

# 데이터베이스와 컬렉션을 선택합니다.
db = client['mydatabase']
collection = db['mycollection']

# 예제 데이터를 삽입합니다.
data = {'name': 'John', 'age': 30, 'city': 'New York'}
collection.insert_one(data)

# 컬렉션에서 데이터를 검색합니다.
result = collection.find_one({'name': 'John'})
print(result)
Posted by yongary
,

 

flask를 이용하는 방법이 간단하다. (Django보다는 간단)

  • 아럐 예제는
    - /run_selenium 엔드포인트로 POST 요청이 들어오면 해당 요청에서 URL을 추출하여 Selenium을 사용하여 해당 URL의 웹 페이지를 가져오는 간단한 REST API 서버 구현. 

 

from flask import Flask, request, jsonify
from selenium import webdriver

app = Flask(__name__)

# 요청을 처리하는 엔드포인트를 정의합니다.
@app.route('/run_selenium', methods=['POST'])
def run_selenium():
    # 요청에서 필요한 데이터를 추출합니다.
    url = request.json.get('url')
    
    # Selenium을 실행하여 웹 페이지를 가져옵니다.
    driver = webdriver.Chrome()  # 적절한 드라이버를 선택하여 사용합니다.
    driver.get(url)
    
    # Selenium을 사용하여 추가적인 작업을 수행합니다.
    
    # 결과를 반환합니다.
    return jsonify({'message': 'Selenium 실행 완료'})

if __name__ == '__main__':
    app.run(debug=True)
Posted by yongary
,

아래 URL을 참고하세요.

 

https://velog.io/@kimsehwan96/S3-CORS-%ED%97%A4%EB%8D%94-%EA%B4%80%EB%A0%A8-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95-html2canvas-lottie

Posted by yongary
,

DatePicker 팝업 안에 '날짜 모름'을 추가하려면 DatePicker 컴포넌트를 커스텀하여 팝업 내부에 체크박스를 추가해야 합니다. 이를 위해 react-datepicker의 CustomInput prop을 사용하여 커스텀 인풋을 만들고, 해당 인풋이 클릭되었을 때 팝업이 열리도록 조정할 수 있습니다. 아래는 그 예시입니다.

import React, { useState } from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';

const CustomDatePicker = () => {
  const [selectedDate, setSelectedDate] = useState(null);
  const [dateUnknown, setDateUnknown] = useState(false);

  const handleDateChange = (date) => {
    setSelectedDate(date);
    setDateUnknown(false);
  };

  const toggleDateUnknown = () => {
    if (dateUnknown) {
      setSelectedDate(null);
    }
    setDateUnknown(!dateUnknown);
  };

  const CustomInput = React.forwardRef(({ value, onClick }, ref) => (
    <div>
      <input
        type="text"
        value={value}
        onClick={onClick}
        ref={ref}
        readOnly
      />
      <label>
        <input
          type="checkbox"
          checked={dateUnknown}
          onChange={toggleDateUnknown}
        />
        날짜 모름
      </label>
    </div>
  ));

  return (
    <DatePicker
      selected={selectedDate}
      onChange={handleDateChange}
      dateFormat="yyyy-MM-dd"
      placeholderText="날짜 선택"
      customInput={<CustomInput />}
    />
  );
};

export default CustomDatePicker;

 

j이 코드에서 CustomInput 컴포넌트를 만들어 Datepicker의 커스텀 인풋으로 사용하고 있습니다. 이 커스텀 인풋에는 입력 필드와 '날짜 모름'을 나타내는 체크박스가 함께 포함되어 있습니다. 사용자가 팝업을 열면 이 체크박스가 팝업 안에 표시됩니다.

 

 

 

기본사용법

https://jiyumi00.tistory.com/54 

 

[React -5] ReactDatePicker 라이브러리로 기간 설정 구현

회원관리, 판매내역, 거래내역 등 테이블이 있는 페이지에 기간 설정을 추가하려고 한다 ✅ React-DatePicker 라이브러리 설치 npm install react-datepicker https://reactdatepicker.com/ React Datepicker crafted by HackerOn

jiyumi00.tistory.com

 

 

커스텀 수정 예제:

https://velog.io/@oo009pbh/react-datepicker-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%95%98%EC%97%AC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

react-datepicker 커스텀 하여 사용하기

Date 형식을 다룰때 정말 많이 사용하는 라이브러리중에 하나이다. 주당 설치 횟수가 100만회가 너끈히 넘을 정도.무엇보다 데모사이트가 깔끔하게 되어 있어 기능을 테스트 해보기가 편하다.어

velog.io

 

커스텀 input 예제: 

https://velog.io/@bnb8419/React-Datepicker-%EC%82%AC%EC%9A%A9%EB%B2%95-1znalwg3

 

React Datepicker Custom

react date picker custom 방법

velog.io

 

css 스타일링으로 세로 text출력.

https://hanbbistory.tistory.com/59

Posted by yongary
,
  1. 고차 컴포넌트 (Higher-Order Component, HOC) 사용: GTM 스크립트를 포함한 컴포넌트를 만들고, 이 컴포넌트로 각 페이지나 레이아웃을 감싸서 GTM 스크립트를 한 번만 로드하도록 할 수 있습니다.
  2. 컨텍스트 (Context) 사용: React의 컨텍스트를 활용하여 GTM 스크립트를 한 번만 로드하고, 각 컴포넌트에서 이벤트를 트리거할 수 있습니다.
import React from 'react';

const addGTMScript = () => {
  const script = document.createElement("script");
  script.innerHTML = `
    (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script','dataLayer','GTM-XXXXXXX');
  `;
  document.head.appendChild(script);
};

const withGTM = (WrappedComponent) => {
  return class extends React.Component {
    componentDidMount() {
      addGTMScript();
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
};

const LoginButton = () => {
  const handleLoginClick = () => {
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      event: 'login'
    });
  };

  return (
    <div>
      <button onClick={handleLoginClick}>Login</button>
    </div>
  );
};

const LoginButtonWithGTM = withGTM(LoginButton);

const LoginPage = () => {
  return (
    <div>
      <h1>Login Page</h1>
      <LoginButtonWithGTM />
    </div>
  );
};

export default LoginPage;
Posted by yongary
,

springboot 버전정보를 api로 제공하는 2가지 방법.

 

1. 1 build.gradle에서 processResources 태스크를 사용하여 애플리케이션 속성 파일에 버전 정보를 주입

 

// build.gradle
processResources {
    filesMatching('application.properties') {
        expand(project: project)
    }
}

 

1.2 

# application.properties
app.version=${version}

# application.yml
app:
  version: ${version}

 

1.3

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class VersionController {

    @Value("${app.version}")
    private String appVersion;

    @GetMapping("/api/version")
    public String getVersion() {
        return appVersion;
    }
}

 

 

2. @Autowired BuildProperties buildProperties;를 사용하여 애플리케이션의 빌드 정보에 접근하는 것은 Spring Boot 2.0 이상에서 가능합니다. BuildProperties는 Spring Boot의 org.springframework.boot.info 패키지에 포함된 클래스로, 빌드 시 생성된 META-INF/build-info.properties 파일의 정보를 제공

2.1  build.gradle 1

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    // 기타 의존성들...
}

 

2.2 build.gradle 2

springBoot {
    buildInfo()
}

 

2.3 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class VersionController {

    @Autowired
    private BuildProperties buildProperties;

    @GetMapping("/api/version")
    public String getVersion() {
        return buildProperties.getVersion();
    }
}

 

 

클라이언트에서 사용 방법.

const AppVersionChecker = () => {
  const [clientVersion, setClientVersion] = useState('1.0.0'); // 클라이언트 버전-> localStorage에 기록해 놓으면 좋을듯.

  useEffect(() => {
    const checkVersion = async () => {
      try {
        // 서버에서 현재 버전을 가져옵니다.
        const response = await axios.get('https://yourserver.com/api/version');
        const serverVersion = response.data.version;

        // 서버 버전과 클라이언트 버전이 다르면 페이지를 새로고침합니다.
        if (serverVersion !== clientVersion) {
          window.location.reload();
        }
      } catch (error) {
        console.error('버전 확인 중 오류 발생:', error);
      }
    };
Posted by yongary
,

react에서 drag& drop

React.js 2024. 1. 4. 15:44

antd의 Dragger를 custom하게 바꾸는 예제. (목록을 우측으로 )

 

https://stackoverflow.com/questions/58469157/antd-how-to-display-fileuploadlist-to-the-right-of-dragger-upload 

 

 

 

 

 

 

 

 

 

<Input type={'file'} 과 함께 사용하는 drag&drop 예제.

 

import React, { useState } from 'react';

function FileUpload() {
  const [files, setFiles] = useState([]);

  const handleDrop = (e) => {
    e.preventDefault();
    const droppedFiles = [...e.dataTransfer.files];
    setFiles(droppedFiles);
  };

  const handleFileInputChange = (e) => {
    const selectedFiles = [...e.target.files];
    setFiles(selectedFiles);
  };

  const handleUpload = () => {
    // 업로드할 파일들을 처리하는 로직을 작성하세요.
    console.log(files);
  };

  return (
    <div
      onDrop={handleDrop}
      onDragOver={(e) => e.preventDefault()}
      style={{
        border: '2px dashed #ccc',
        padding: '20px',
        textAlign: 'center',
        cursor: 'pointer',
      }}
    >
      <input
        type="file"
        style={{ display: 'none' }}
        multiple
        onChange={handleFileInputChange}
        id="fileInput"
      />
      <label htmlFor="fileInput">
        드래그 앤 드롭 또는 클릭하여 파일을 업로드하세요.
      </label>
      <button onClick={handleUpload}>업로드</button>
      {files.map((file, index) => (
        <div key={index}>{file.name}</div>
      ))}
    </div>
  );
}

export default FileUpload;
Posted by yongary
,

 

Spring Security를 이용해 로그인한 관리자에게만 images를 노출할 수 있습니다.


### build.gradle
implementation 'org.springframework.boot:spring-boot-starter-security'


### 설정
import org.springframework.context.annotation.Bean;
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
            .authorizeRequests()
                .antMatchers("/images/**").hasRole("ADMIN") // 이미지 리소스에 대한 접근 권한 설정
                .anyRequest().permitAll() // 다른 요청은 모두 허용
                .and()
            .httpBasic(); // 기본 인증 사용
    }
}

### application.properties
spring.security.user.name=admin
spring.security.user.password=adminpassword
spring.security.user.roles=ADMIN

### WebConfig
registry.addResourceHandler("/images/**")
        .addResourceLocations("file://" + System.getProperty("user.dir") + "/my-images/");
 
 
 
### 참고 : 로그인 구현방식 중 1가지 예제:단,  {noop}는 암호화없는 평문이라는걸 알리는 키워드입니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/images/**").hasRole("ADMIN")
                .anyRequest().authenticated() // 모든 요청에 대해 인증을 필요로 함
                .and()
            .formLogin(); // 폼 기반 로그인 사용
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("admin").password("{noop}adminpassword").roles("ADMIN");
    }
}
Posted by yongary
,

 

front에서 직접 이미지를 올리더라도, S3의 bucket URL 및 키를 모두 front에서 관리하는 것은 보안상 위험하므로
Backend에서 pre-signed URL을 프론트로 주면, front에서 그 URL을 이용해서 올리는 방식이 권장된다.
pre-signed URL은 설정한 시간(예: 1시간) 동안 유효하다.

java 백엔드에서 pre-signed URL을 생성하고, front에서 react를 이용해서 파일을 올리는 예제는 다음과 같다.

 



백엔드:  AWS-SDK이용.

import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.URL;
import java.util.Date;

@Service
public class S3Service {
    private final AmazonS3 s3Client;
    private final String bucketName;

    public S3Service(
            @Value("${aws.accessKeyId}") String accessKeyId,
            @Value("${aws.secretKey}") String secretKey,
            @Value("${aws.region}") String region,
            @Value("${aws.s3.bucketName}") String bucketName) {
        this.bucketName = bucketName;
        this.s3Client = AmazonS3ClientBuilder
                .standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKeyId, secretKey)))
                .build();
    }

    public URL generatePresignedUrl(String objectKey, HttpMethod httpMethod, long expirationInSeconds) {
        Date expirationDate = new Date(System.currentTimeMillis() + (expirationInSeconds * 1000));
        GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, objectKey)
                .withMethod(httpMethod)
                .withExpiration(expirationDate);

        return s3Client.generatePresignedUrl(generatePresignedUrlRequest);
    }
}

 

 

FrontEnd:

import React, { useState } from 'react';
import axios from 'axios';

function FileUpload() {
  const [selectedFile, setSelectedFile] = useState(null);

  const handleFileChange = (event) => {
    setSelectedFile(event.target.files[0]);
  };

  const handleUpload = async () => {
    if (!selectedFile) {
      console.log('Please select a file.');
      return;
    }

    try {
      // Replace with your backend API endpoint that generates the Pre-signed URL
      const response = await axios.get('http://your-backend-api/generatePresignedUrl', {
        params: {
          objectKey: 'example.jpg', // 업로드할 객체의 키
          httpMethod: 'PUT', // 업로드할 때 사용할 HTTP 메서드 (PUT 또는 POST)
          expirationInSeconds: 3600, // URL의 유효 기간 (1시간)
        },
      });

      const { url } = response.data;

      // Use the obtained Pre-signed URL to upload the file directly to S3
      await axios.put(url, selectedFile, {
        headers: {
          'Content-Type': selectedFile.type,
        },
      });

      console.log('File uploaded successfully.');
    } catch (error) {
      console.error('Error uploading file:', error);
    }
  };

  return (
    <div>
      <input type="file" onChange={handleFileChange} />
      <button onClick={handleUpload}>Upload</button>
    </div>
  );
}

export default FileUpload;
Posted by yongary
,

selector 예제:

 - atom이 변하면 자동으로 따라서 변하는 동적인  atom 제공.

/// atom 예제
import { atom } from 'recoil';

export const userState = atom({
  key: 'userState', // 고유한 키
  default: {
    id: 1,
    name: 'John Doe',
    email: 'johndoe@example.com'
  },
});


/// selector 예제 => atom이 변하면 자동으로 따라서 변하며, 동적인 atom느낌을 제공.

import { selector } from 'recoil';
import { userState } from './userAtom'; // 위에서 정의한 atom을 가져옵니다.

export const userNameUpperState = selector({
  key: 'userNameUpperState', // 고유한 키
  get: ({get}) => {
    const user = get(userState);
    return user.name.toUpperCase();
  },
});

 

 

effects 예제

-  onSet : 바뀔때마다 호출.

- setSelf:  초기값 설정에 사용.  default:와 비슷하지만 비동기도 지원하고, 동적인 조건식 제공가능하므로 좀 더 권장. 

import { atom } from 'recoil';

export const userState = atom({
  key: 'userState',
  default: {
    id: 1,
    name: 'John Doe',
    email: 'johndoe@example.com'
  },
  effects: [
    ({ onSet }) => {
      onSet(newValue => {
        console.log('userState has changed:', newValue);
      });
    }
  ]
});

 

Posted by yongary
,

git 복사 (미러링)

기타 2023. 11. 18. 22:59

Git 저장소의 히스토리를 포함하여 복사해서 다른 저장소로 옮기는 방법

 

1. 기존 저장소를 --mirror로 받는다.
$git clone --mirror <기존_저장소_URL>

 

2. 새 저장소를 만들고 그 URL을 얻은 다음..

 

3. $cd 기존_저장소_URL
    $git push --mirror <새_저장소_URL> 

 

 

Posted by yongary
,

git submodule

기타 2023. 11. 18. 22:51

1 서브모듈 추가하고 싶은 폴더로 가서 

$git submodule add https://github.com/example/submodule.git path/to/submodule

 

2 변경 사항 커밋: 서브모듈을 추가한 후, .gitmodules 파일과 서브모듈 디렉토리에 대한 변경 사항을 커밋합니다.

git add .
git commit -m "Add submodule"

 

3. git push

 

========= 

전체 pull 하고 싶을때
최초한번

git submodule update --init --recursive

 

그 후 

git submodule update --remote --recursive

Posted by yongary
,

github

기타 2023. 11. 9. 22:23

 

ChatGPT

GitHub에서 특정 리포지토리를 clone하고 SSH 키를 설정하여 반복적으로 로그인하지 않도록 하는 방법은 다음과 같습니다:

  1. GitHub 계정 만들기 및 SSH 키 설정:
    • GitHub에 가입하려면 https://github.com/ 에서 가입 프로세스를 따릅니다.
    • 계정을 만들었다면, 로그인하세요.
    • SSH 키를 생성하려면 터미널 또는 명령 프롬프트에서 다음 명령을 실행합니다:이 명령에서 "your_email@example.com" 부분을 본인의 이메일 주소로 바꿉니다. 키 생성 중에 암호를 설정하라는 메시지가 나오면 암호를 설정하세요. 암호는 SSH 키를 사용할 때 필요할 수 있습니다.
    • mathematicaCopy code
      $ssh-keygen -t ed25519 -C "your_email@example.com"
    • 키 생성이 완료되면, 공개 키를 GitHub에 추가해야 합니다. 터미널에서 다음 명령으로 공개 키를 복사합니다:
    • bashCopy code
      $cat ~/.ssh/id_ed25519.pub
      ==> 공개키는  한 줄이며 ssh-ed25519 AAA로 시작하고 이메일로 끝납니다.
      • 공개 키를 클립보드에 복사한 후, GitHub 계정 설정 페이지로 이동하여 "SSH and GPG keys" 섹션에 공개 키를 추가합니다.
  2. 리포지토리 Clone:
    • SSH 키가 GitHub에 등록되었으면, 해당 리포지토리를 clone할 수 있습니다. 터미널에서 다음 명령을 사용하여 리포지토리를 clone합니다:여기서 사용자명과 리포지토리명을 실제 사용자명과 리포지토리 이름으로 바꿉니다.
    • scssCopy code
      $git clone git@github.com:사용자명/리포지토리명.git

 

 

 

만약 A PC에서 생성한 SSH 키를 B PC에서도 사용하려면, A PC에서 생성한 키를 B PC로 복사해야 합니다.

  1. 공개 키와 개인 키 복사 A PC의 SSH 키 파일을 B PC로 이동합니다. 예를 들어, 기본 경로(~/.ssh/id_ed25519와 ~/.ssh/id_ed25519.pub)를 복사합니다.
  2. bash
    코드 복사
    # A PC에서 B PC로 복사 (B PC의 IP가 192.168.1.100이고 사용자명이 user라고 가정) scp ~/.ssh/id_ed25519* user@192.168.1.100:~/.ssh/
  3. 권한 설정 B PC에서 복사한 파일의 권한을 올바르게 설정합니다:
  4. bash
    코드 복사
    chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub
  5. SSH 에이전트에 추가 (B PC에서) 복사한 키를 SSH 에이전트에 추가합니다:
  6. bash
    코드 복사
    eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519 

    혹은 Window에서는 관리자용-PowerShell에서 아래방식이 잘됨.

Start-Service ssh-agent

ssh-add C:\Users\<YourUsername>\.ssh\id_ed25519

 

혹시 git 이 잘 안된다면 $which ssh 를 해보면 /usr/bin/ssh 가 나오는 경우가 있는데,
이걸 "/c/Windows/System32/OpenSSH/ssh" 를 사용하도록 바꿔야 한다.

 

간단하게 mv /usr/bin/ssh /usr/bin/ssh-backup 으로 없애버리면 된다.

Posted by yongary
,

postgreSQL array관련

BACK-END 2023. 9. 24. 18:29

postgreSQL에서 array필드를 query할 땐 아래처럼 하면 된다.
(tags가  TEXT[] 필드일 때 가정..==>  INSERT시에는 ARRAY['tag1', 'tag2']) 로 삽입.

 

== MyBatis. xml ===

<select id="getTags" resultType="string">
    SELECT array_agg(tag) AS tags
    FROM (SELECT unnest(tags) AS tag FROM posts) AS subquery
</select>

 

== java 코드 ==

public interface TagMapper {
    List getTags();
}

 

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    TagMapper tagMapper = sqlSession.getMapper(TagMapper.class);
    List<String> tags = tagMapper.getTags();
    
    // 결과(tags)를 처리
} finally {
    sqlSession.close();
}

 

Posted by yongary
,

 

JAVA python
for (int i = 0; i < arr.length; i++)     //0~99
     System.out.println( 'A')
for i in range(0,  len(arr)) :            #0~99
     print('A')

Arrays.sort 
         Collections.reverseOrder()


indexOf
a = [2,3,4, 1]
a.sort()
a.reverse()

[기본기능]
a.append( 7) 뒤에추가, 
a.index(7)  ==> 4 
a.insert( 위치=0,  99 )   특정위치에 추가, 위치기본값=0
a.remove는 value를 제거 => 이거보단 아래 filter 추천.

List 
   stream().map 
   .filter
   
list : 키워드 
arr = list(map(lambda x:x*2, arr))
arr = list(filter(lambda x:x>=2, arr))


HashMap  혹은 javascript의 json과 유사점도 많음
  hm.getKeys().stream()
  hm.getValues().stream()

딕셔너리. dic = [1:'kim', 2:'lee']
dic[1] ==> 'kim'. 혹은  dic.get(1) 
dic.keys
dic.values
class A {
   생성자 public A( ) 
}
class A:
    def __init__ (self):  
        self.value = 0
함수 및 static 느낌 x = 1
def add(num):
    global x
     return num + x
stream * boolean 느낌 -> 여러개 모두 체크 (java엔 없음) all([true, true, true])  --> true
any([treu, false, false])  ===> true

Posted by yongary
,

$ brew install python@3.11  을 하면  python3.11 은 실행되지만 python 명령어는 실행되지 않는다.

 

이 때 pyenv를 사용해서 해결하면 향후 프로젝트 별로 다른 python을 실행할 때도 도움이 된다.

애시당초 pyenv를 먼저 설치하고 python을 설치하는 것이 더 좋은 방법같다.

 

1.  $brew install pyenv

2. $pyenv install python3.11.5 

3. $pyenv global 3.11.5

4. $echo 'eval "$(pyenv init -)"' >> ~/.bashrc     하고 터미널 재시작 혹은 source .bashrc 실행 

 

 

 

Posted by yongary
,

프런트로 Stream을 리턴하는 방식은 프런트 처리시 난이도가 높아서, 특수한 경우에 한해 사용하면 좋다.

1. Flux나 Observable 생성: WebFlux나 RxJava에서 Flux 또는 Observable을 사용하여 데이터 스트림을 생성합니다. 이 스트림은 비동기적으로 데이터를 생성하거나 가져올 수 있습니다.
    dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' }

2. 예제코드  : 보통 Flux를 리턴하고 데이터가 1/0 개일경우는 Mono 리턴. 

import reactor.core.publisher.Flux;

    @RestController
    public class StreamController {

        @GetMapping(value = "/stream-data", produces = "text/event-stream")
        public Flux<String> streamData() {
            // 스트림 생성 예제 (여기서는 간단한 문자열 스트림)
            return Flux.just("Data 1", "Data 2", "Data 3")
                    .delayElements(java.time.Duration.ofSeconds(1)); // 1초마다 데이터 전송
        }
    }

 

 

Posted by yongary
,

mtools

mongoDB, redis 2023. 9. 5. 17:59

mtools를 통해 EXPLAIN과 같이 slow query를 분석할 수 있다.

/var/logs/mongod 

밑에 있는 로그 파일을 50mb 정도의 사이즈로 나눠서

mloginfo --queries --no-progressbar 쪼개진파일.log > slow_queries.txt
(mtools 파이썬 패키지 설치하면 mloginfo라는 executable 생깁니다)

하면 늦은 query를 분석할 수 있다. 

Posted by yongary
,

Object, Vector

java core 2023. 8. 31. 23:44

Object class에 기본 포함되는 함수는

  • public boolean equals(Object o) 
  • public String toString()
  • finalize() : 가비지 콜렉션이 정리할 때 사용
  • public native int hashCode()  - 대략적인 hashCode 이며
  • clone()
    (그 외 wait, notify 등)

 

Vector 와  ArrayList의 차이
- Vector는 synchronized라서 Thred Safe하고 좀 늦다는 게 가장 큰 차이이고
- 둘 다 고정 사이즈이고 내부적으로는 array를 사용하며,  size를 증가시킬 때 Vector는 100%씩 증가, ArrayList는 50%씩 증가. 

 

 

 

Posted by yongary
,

bean 종류

springBoot gradle 2023. 8. 31. 23:12

spring에서 bean은 아무 생각없이 만들면

spring container에서 singleton 으로 만들어 준다.

(즉, 전체 sping container에서 항상 동일한 하나의 global instance로 bean을 만들어 준다)

 

따라서 immutable이고 status도 없이 만드는 것이 좋음.

 

만약, 다른 용도로 만드려면

 

Prototype Bean - 매번 생성

Session Bean  - WEB 세션별로 생성

Request  Bean - WEB Request별로 생성

이 존재하며..

 

 

@Component

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)     과 같이 annotate 한다.

    - @Scope("prototype") 만 해도 되지만.. 위에 방식이 더 안전.

 

 

xml에서 할 경우에는
<baen id="note" class="my.Note" scope="prototype"> 으로 한다.

 

Posted by yongary
,

React 팁스

React.js 2023. 8. 26. 00:39

1. json obj 이용 세팅

   setCount(prev => prev + 1) 처럼 json이용해서 사용하려면

   setUser (prev => { ...prev, name:'kim'} ) 하면 안된다.  이유는 { 를 함수시작으로 인식하기 때문이며, 해결 법은 2가지인데
       setUser(prev => (
{...prev, name:'kim'}) ) 괄호이용 ,   usetUser (prev => { return {prev..., name:'kim'} }) return 이용.

 

2. handleChange 에서 e.target.name 이용

    const handleChange = e => {

          setUser( {...user, 
                         [e.target.name]: e.target.value} ).  이렇게 중괄호(curly brace)를 이용하면  변수를 이용해 필드설정 가능

   }                                                                                   => 여러개 의 input text 를 하나의 handleChange로 이용하게 된다.
    

 

3. 페이지 reload 하는 경우

const TestApp = () => {

        console.log("reloaded");  // setQuantity(0) 호출시 안찍힘. setQuantity(1) 호출시 찍힘. 즉 state가 바뀔때만 reload

                                                      // json obj는 같은 {age:1} 대신  {age:1} 형태로 같은 내용이라도 pointer가 항상 바뀌므로 찍힘.

        const [quantity, setQuantity] = useState(0)

        const totalPrice = quantity * 5;   // useState 필요없이 이 방식대로 해도 page가 reload 될 때마다 재계산된다.                   

}

 

4. api호출 취소

  axios는 cancelToken 이용
  fetch는 AbortController 의   controller.signal 이용해서  취소

 

Posted by yongary
,