728x90

쿼리문을 작성하다보면 특이한 경우의 수가 많이 생긴다. 보통은 제품명을 하나로 입력하지만 고객사에서 자동으로 입력되길 원해서 필터값을 모두 조합해서 등록해야 하는 경우도 생긴다. 그런데 이때 필터값은 보통 코드 테이블에 공통으로 관리 하는데 그러면 테이블 join 을 해서 subquery 로 가져 와야 하는데 이런 단어들은 어떻게 합쳐야 할찌 난감할때가 있다. 

 

등록이 안된 필터는 null 이나 공백을 내 놓을테니 그 부분도 대응을 해야 한다. 아래 예시는 필터 정보를 | 기호로 하나로 합쳐서 select 해주는 쿼리문이다. CONCAT 는 문자열을 합치는 용도로 쓰는데 의외로 많이 쓰이기 때문에 알아 두는것이 유용하다. 

$sql = "SELECT 
      CONCAT(
        COALESCE((SELECT name FROM material_category WHERE id = G.goods_cate), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_1), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_2), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_3), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_4), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_5), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_6), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_7), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_8), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_9), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_10), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_11), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_12), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_13), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_14), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_15), ''), '|', 
        COALESCE((SELECT name FROM material_category WHERE id = G.filter_16), '')
      ) AS full_name
    FROM goods G
    WHERE goods_idx = :goods_idx";

$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':goods_idx', $goods_idx, \PDO::PARAM_INT);
$stmt->execute();

// fetch()로 결과를 가져오고, full_name만 반환
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
return $result['full_name'] ?? null; // full_name이 없을 경우 null 반환

 

이렇게 합쳐진 full_name 만 return 해주는 함수 예시로 pdo 로 DB 연동을 하고 있다면 참조 하면 좋을거 같다.  

728x90
728x90

테이블을 만들다 보면 종종 autoICrement 로 자동증가로 키값을 잡는 경우가 있다. 주로 키값을 관리하는 테이블로 관리를하지만 경우에 따라서는 자동증가 기능도 사용하곤 하는데 작업을하다 보면 insert 후에 그 키값을 받아 와서 다른테이블에  insert 해야 하는 경우가 있다. 이럴때 유용하게 사용할 수 있는 함수는 lastInsertId 다. 

 

 

트랙잭션 내에서는 커밋하기 전에 넣어 줘야 값을 담아 온다. 커밋 뒤에 넣어서 안되는줄 알았는데 순서를 바꿨더니 값이 잘 나온다. 자동 증가값 키가 필요 하다면 참고 하면 좋겠다.  

$this->pdo->beginTransaction();

$stmt->execute(); // 쿼리 실행

$lastId = $this->pdo->lastInsertId(); // 자동 증가값 가져오기

$this->pdo->commit(); // 트랜잭션 커밋
728x90
728x90

1. 테이블 생성

CREATE TABLE SubjectScores (
    id INT PRIMARY KEY AUTO_INCREMENT,
    student VARCHAR(100) NOT NULL,
    subject VARCHAR(50) NOT NULL,
    score INT NOT NULL,
    semester VARCHAR(20),
    year INT
);

 

2. 샘플 데이터 입력구문

INSERT INTO SubjectScores (student, subject, score, semester, year) VALUES
('Kim Min-soo', 'English', 85, '1st Semester', 2024),
('Kim Min-soo', 'Math', 90, '1st Semester', 2024),
('Kim Min-soo', 'Science', 80, '1st Semester', 2024),
('Lee Young-hee', 'English', 78, '1st Semester', 2024),
('Lee Young-hee', 'Math', 88, '1st Semester', 2024),
('Lee Young-hee', 'Science', 92, '1st Semester', 2024),
('Park Cheol-soo', 'English', 95, '1st Semester', 2024),
('Park Cheol-soo', 'Math', 85, '1st Semester', 2024),
('Park Cheol-soo', 'Science', 88, '1st Semester', 2024),
('Kim Ji-young', 'English', 82, '1st Semester', 2024),
('Kim Ji-young', 'Math', 79, '1st Semester', 2024),
('Kim Ji-young', 'Science', 91, '1st Semester', 2024);

 

3. 학생별 과목별 평균과 랭킹 구하는 쿼리문

SELECT student, subject, AVG(score) AS average_score,
       RANK() OVER (PARTITION BY subject ORDER BY AVG(score) DESC) AS rank
FROM SubjectScores
GROUP BY student, subject;

 

728x90
728x90

vue3 에서 컴포넌트간에 메서드 호출 예시를 예제로 알아 본다. 작업을 하다 보면 자식 컴포넌트에서 부모 컴포넌트의 메소드를 호출하기도 하고 반대로 부모 컴포넌트에서 자식 컴포넌트 함수를 호출 하기도 한다. 

 

아래 예시는 <script setup> 코드로 vue3 이다. 자식 컴포넌트에서 부모를 호출하는 부분은 defineExpose 에 정의해 주어야 하고 부모컴포넌트에서 받아 오는 값은 props 로 정의해주어야 한다. 

<template>
  <div>
    <p>받은 값: {{ receivedValue }}</p>
    <button @click="callParentFunction">부모 함수 호출</button>
  </div>
</template>

<script setup>
import { defineProps, defineExpose } from 'vue';

// 부모로부터 값을 받기
const props = defineProps({
  receivedValue: String,
});

// 자식에서 부모가 호출할 수 있는 함수를 정의
const childFunction = () => {
  console.log('자식 함수가 호출되었습니다!');
};

// 부모가 호출할 수 있도록 expose
defineExpose({
  childFunction,
});
</script>

 

부모컴포넌트에서 자식함수를 호출 하려면 ref 를 정의하고 호출 할수 있다. 

<template>
  <div>
    <h1>부모 컴포넌트</h1>
    <ChildComponent ref="childRef" :receivedValue="parentValue" @childEvent="handleChildEvent" />
    <button @click="callChildFunction">자식 함수 호출</button>
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';

// 부모 컴포넌트의 상태
const parentValue = ref('부모에서 전달한 값');

// 자식의 이벤트를 처리하는 메서드
const handleChildEvent = (message) => {
  console.log(message); // "자식에서 호출된 이벤트" 출력
};

// 자식 컴포넌트에 대한 참조 생성
const childRef = ref(null);

// 자식의 함수를 호출하는 메서드
const callChildFunction = () => {
  if (childRef.value) {
    childRef.value.childFunction(); // 자식의 함수를 호출
  }
};
</script>
728x90
728x90

vue3 <script setup> 코드로 새창으로 링크 거는 예시이다. a 태그에서 바로 쓸수도 있지만 버튼에서 링크가 걸려야 하는 경우 예시도 같이 메모해 본다. 

 

sns 로그인 같은 버튼을 연동할때 유용하게 쓸수 있는거 같다. 

<template>
  <div>
    <a :href="link" target="_blank" rel="noopener noreferrer">새 창으로 열기 (링크)</a>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const link = ref('https://www.example.com');
</script>

 

버튼 예시

<template>
  <div>
    <button @click="openLink">새 창으로 열기 (버튼)</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const link = ref('https://www.example.com');

const openLink = () => {
  window.open(link.value, '_blank', 'noopener,noreferrer');
};
</script>
728x90
728x90

AppleLogin 라이브러리에서 email 정보도 가져와서 사이트에서 활용해야 한다면 responseMode 를 form_post 로 설정하고 값을 보내고 다시 post 로 데이터를 받아야 한다.

 

 

post 데이터를 받는 부분이기 때문에 백엔드로 받아서 front 에서 토큰을 저장하는 로직을 구현 해주어야 한다. 

import React from 'react';
import AppleLogin from 'react-apple-login';

const AppleLoginButton = () => {
  const handleSuccess = (response) => {
    console.log("로그인 성공:", response);
    // 서버에 response.id_token을 보내서 사용자 정보를 처리합니다.
    fetch('https://yourapp.com/api/auth/apple', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        id_token: response.id_token,
      }),
    })
    .then(res => res.json())
    .then(data => {
      console.log("서버 응답:", data);
      // 사용자 정보를 처리합니다.
    })
    .catch(error => {
      console.error("서버 통신 실패:", error);
    });
  };

  const handleError = (error) => {
    console.error("로그인 실패:", error);
  };

  return (
    <AppleLogin
      clientId="com.yourapp.identifier" // 애플 개발자 계정에서 등록한 클라이언트 ID
      redirectURI="https://yourapp.com/auth/apple/callback" // 리다이렉트 URI
      responseType="id_token" // id_token으로 변경
      responseMode="form_post" // POST 방식으로 변경
      usePopup={true} // 팝업으로 로그인할지 여부
      onSuccess={handleSuccess}
      onError={handleError}
    />
  );
};

export default AppleLoginButton;

 

이때 redirectURI 는 백엔드 api url 을 주어야 하고 아래와 같이 백엔드에서는 post 로 받아서 다시 front 로 /front/login 으로 id 와 email 정보를 넘겨 주고 front 에서는 세션 처리를 하면 된다.

email 값이 안넘어 온다면 form_post 로 변경해 보길 권한다. 

const appleToken = async (request, response) => {
    const { code } = request.body;

    if (!code) {
        return response.status(400).json({ message: 'Code is required.' });
    }

    const result = (data, message) => {
        if (message === messageMap.OK) {
            const { id, email } = data;
            response.redirect(`https://프런트도메인/front/login?snsid=${id}&email=${email}`);
        } else {
            response.status(message.status).json({ message: message.string });
        }
    };
};
728x90
728x90

vue 에서 api 호출시 axios 로 호출 되는 모든 api 의 오류를 체크 할수 있는 파일은 interceptor.js 에서 한번에 처리 할수 있다. 그래서 headers 부분도 매번 api 호출 할때마다 적어주는것이 아니라 congif.headers 로 한번에 지정이 가능하다. 

 

아래 예시는 localStorage 에 토큰값을 저장해 두고 api 호출시 토큰값을 헤더에 실어 보내는 예시다. 보통 권한 오류는 401 로 떨어 지기에 401 로 코드를 받았을때 와 403, 404 정도로 분기를 하고 필유시 코드별로 메세지를 지정해 둘수도 있다. 

 

원래는 이렇게 router 가 사용이 가능해야 하는데 안되는 경우도 있으니 참고하는게 좋겠다. 

import axios from 'axios';
import store from '@/store'; // Vuex store
import router from '@/router'; // Vue Router

// Axios 인스턴스 생성
const axiosInstance = axios.create();

// 요청 인터셉터
axiosInstance.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token'); // 토큰 가져오기
    if (token) {
      config.headers.Authorization = `Bearer ${token}`; // Authorization 헤더 추가
    }
    return config;
  },
  (error) => {
    return Promise.reject(error); // 요청 오류 처리
  }
);

// 응답 인터셉터
axiosInstance.interceptors.response.use(
  (response) => {
    return response; // 응답이 성공적일 경우 응답 반환
  },
  async (error) => {
    if (error.response) {
      const status = error.response.status;

      // 401 Unauthorized 처리
      if (status === 401) {
        alert('로그인 세션이 종료되었습니다. 다시 로그인해주세요.');
        store.dispatch('auth/logout'); // 로그아웃 액션
        router.push('/auth/login'); // 로그인 페이지로 리다이렉트
        return Promise.reject(error);
      }

      // 403 Forbidden 처리
      if (status === 403) {
        alert('이 작업을 수행할 권한이 없습니다.');
        return Promise.reject(error);
      }

      // 404 Not Found 처리
      if (status === 404) {
        router.push('/error/404'); // 404 페이지로 리다이렉트
        return Promise.reject(error);
      }

      // 기타 오류 처리
      alert(`오류 발생: ${error.response.data.message || '알 수 없는 오류'}`);
      router.push('/error/general'); // 일반 오류 페이지로 리다이렉트
    } else {
      // 네트워크 오류 처리
      alert('네트워크 오류가 발생했습니다. 인터넷 연결을 확인해주세요.');
    }

    return Promise.reject(error); // 오류를 다음으로 전파
  }
);

export default axiosInstance;

 

코드상에서 api 호출 예시는 아래와 같다. intercepter.js 를 먼저 타기 때문에 일괄로 관리하기에 효율적이다. 

import axios from '@/path/to/interceptor'; // interceptor.js 경로

// API 요청 예시
axios.get('/api/example')
  .then(response => {
    // 성공적으로 데이터를 받아온 경우 처리
  })
  .catch(error => {
    // 에러 처리 (여기서는 interceptor가 처리하므로 추가 필요 없음)
  });
728x90
728x90

1.애플 개발자 계정 설정

먼저, 애플개발자계정에서 앱을 등록하고,"SigninwithApple"기능을 활성화 해야합니다.이 과정에서 BundleID와 관련된 설정을 하게 됩니다.

 

2.필요한 라이브러리 설치

리액트에서 애플로그인을 쉽게구현하기 위해react-apple-login같은 라이브러리를 사용할수 있습니다.다음과 같이 설치합니다.

npm install react-apple-login

 

3. 로그인 버튼 구현

애플로그인에서 AppleLogin 라이브러리를 사용한다면 한가지 유의할 점이 있다. 아래와 같이 responseMode 를 query 보내게 되면 이메일 계정 정보를 선택하는 화면이 노출 되지 않는다. 단순히 애플 로그인만 사용 할수 있으니 이메일정보를 받아와야 하는 프로젝트라면 post 방식을 이용해야 한다. 

import React from 'react';
import AppleLogin from 'react-apple-login';

const AppleLoginButton = () => {
  const handleSuccess = (response) => {
    console.log("로그인 성공:", response);
    // 서버에 response를 보내서 사용자 정보를 처리합니다.
  };

  const handleError = (error) => {
    console.error("로그인 실패:", error);
  };

  return (
    <AppleLogin
      clientId="com.yourapp.identifier" // 애플 개발자 계정에서 등록한 클라이언트 ID
      redirectURI="https://yourapp.com/auth/apple/callback" // 리다이렉트 URI
      responseType="code"
      responseMode="query"
      usePopup={true} // 팝업으로 로그인할지 여부
      onSuccess={handleSuccess}
      onError={handleError}
    />
  );
};

export default AppleLoginButton;

 

4. 서버 백엔드 처리

애플 로그인 후, 받은 인증 코드를 서버로 전송하여 사용자 정보를 가져오는 과정이 필요합니다. 서버에서는 애플의 API를 호출하여 사용자 정보를 검증하고, 필요한 경우 데이터베이스에 저장합니다.

 

5. 애플 로그인 api 예시 (Node.js)

const axios = require('axios');

const verifyAppleToken = async (token) => {
  const response = await axios.post('https://appleid.apple.com/auth/token', {
    // 필요한 파라미터 추가
  });
  return response.data;
};

 

728x90
728x90

리액트에서 페이지 로드 후 바로 실행하기 위해서는 useEffect 에 정의해 준다. vue나 일반적인 javascript 라면 mouted, ready 에 선언해야 할 부분을리액트에서는 useEffect 로 함수마다 정의해도 되고 함수를모아서 호출하는 방식도 가능하다. 

 

아래 예시는 api 호출 후 바로 버튼을 숨김처리 하는 예시다. 사용자 입장에서는 화면에 진입했을때 api 호출 후 나오는 부분이라 페이지에서 바로 확인이 가능하다.   

import React, { useEffect, useState, useCallback } from 'react';

const YourComponent = () => {
  const [isButtonDisabled, setIsButtonDisabled] = useState(true); 

  const user_seq = 'your_user_seq'; // 사용자 시퀀스
  const cont_seq = 'your_cont_seq'; // 콘텐츠 시퀀스

  const checkUserPlay = useCallback(() => {
    get(`/content/userAgeChk?user_seq=${user_seq}`)
      .then((response) => {
        if (response.messageCode === 200) {
          const { cnt } = response.data;
          if (cnt > 0) {
          	setModalReview(true);
          } else {
          	setModalCheck(true);
          }
        }
      })
      .catch((error) => console.error(error));
  }, [user_seq, cont_seq]); // 의존성 배열에 필요한 값 추가

  useEffect(() => {
    checkUserPlay();
  }, [checkUserPlay]); // 의존성 배열에 checkUserPlay 추가

  return (
    <div>
      <button disabled={isButtonDisabled}>버튼 텍스트</button> 
    </div>
  );
};

export default YourComponent;

 

728x90
728x90

나이 계산은 은근히 스크립트로 계산 하려면 코드를 많이 써야 한다. sql 쿼리문에서 나이 계산이 되어야 하는 경우도 종종 있어서 쿼리문에서 바로 뽑아서 쓸수 있는 코드 예시를 공유해본다. 

회원코드값으로 user 테이블에서 생년월일 필드로 만나이를 계산하는 공식이다. 

 

mysql 쿼리문에서 나이 계산하기

SELECT
    DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(CONCAT(birth_year, '-', birth_month, '-', birth_day), '%Y') - 
    (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(CONCAT(birth_year, '-', birth_month, '-', birth_day), '00-%m-%d')) AS age
FROM
    user
WHERE
    user_seq = #{user_seq};

 

리액트에서 나이 계산 하기 

import React, { useState } from 'react';

const AgeCalculator = () => {
  const [birthDate, setBirthDate] = useState('');
  const [age, setAge] = useState(null);

  const calculateAge = (dateString) => {
    const birthDate = new Date(dateString); // YYYY-MM-DD 형태의 문자열을 Date 객체로 변환
    const today = new Date();

    // 만 나이 계산
    let calculatedAge = today.getFullYear() - birthDate.getFullYear();
    if (today < new Date(today.getFullYear(), birthDate.getMonth(), birthDate.getDate())) {
      calculatedAge--;
    }
    
    return calculatedAge;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    setAge(calculateAge(birthDate));
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="date" // HTML5의 date 입력 형식 사용
          value={birthDate}
          onChange={(e) => setBirthDate(e.target.value)}
        />
        <button type="submit">나이 계산하기</button>
      </form>
      {age !== null && <p>만 나이: {age}세</p>}
    </div>
  );
};

export default AgeCalculator;
728x90

+ Recent posts