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
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
,