본문 바로가기
국비학원/수업기록

국비 지원 개발자 과정_Day81

by 루팽 2023. 3. 25.

회원제로 운영되는 서비스 - 여러 게시판 유형

  (ex. QnA인데 양도, 매매 등으로 검색조건이 꼭 필요한 게시판, 리뷰게시판, 예매게시판 등)

 

게시판의 유형에 따라 첨부파일이 필요한 경우도 있고 필요 없는 경우도 있음

게시판 유형에따라 댓글 처리 테이블을 별도로 설계함

  리뷰게시판 - 리뷰댓글테이블(ex. review_comment)

  QnA게시판 - QnA댓글테이블(ex. qna_comment)

  예매게시판 - 예매댓글테이블(ex. reserve_comment)

  첨부파일은 통합 테이블로 관리하기로 함 - mblog_file

 

 MEM_NO        NUMBER(5)         NOT NULL, 시퀀스--seq_member_no.nextval 자동채번-사용자가 입력하는 값이 아님
1)MEM_UID       VARCHAR2(20 BYTE), --사용자가 입력한 값
2)MEM_PW        VARCHAR2(10 BYTE) NOT NULL, --사용자가 입력한 값
 MEM_PW  
3)MEM_NAME      VARCHAR2(30 BYTE) NOT NULL, --사용자가 입력한 값
4)MEM_NICKNAME  VARCHAR2(30 BYTE), --사용자가 입력한 값
5)MEM_EMAIL     VARCHAR2(30 BYTE), --사용자가 입력한 값
6)MEM_TEL       VARCHAR2(20 BYTE), --사용자가 입력한 값
7)MEM_GENDER    VARCHAR2(1 BYTE), --사용자가 입력한 값
8)MEM_BIRTHDAY  VARCHAR2(20 BYTE), --사용자가 입력한 값
9)MEM_ZIPCODE   VARCHAR2(6 BYTE), --사용자가 입력한 값
10)MEM_ADDR      VARCHAR2(100 BYTE), --사용자가 입력한 값
11_MEM_ADDR_DTL  VARCHAR2(50 BYTE), --사용자가 입력한 값
 MEM_STATUS    VARCHAR2(20 BYTE), --디폴트0, 일반1, 블랙리스트-입력받는게 아님
 MEM_AUTH      VARCHAR2(20 BYTE) --인증(관리자, 일반회원, 강사)-입력받는게 아님

 

<리액트 스프링 회원가입 - App.jsx>

import { Route, Routes } from 'react-router-dom';
import LoginPage from './components/auth/LoginPage';
import KakaoRedirectHandler from './components/kakao/KakaoRedirectHandler';
import Profile from './components/kakao/Profile';
import MemberPage from './components/page/MemberPage';
import HomePage from './components/page/HomePage';
import DeptPage from './components/page/DeptPage';
import DeptDetail from './components/dept/DeptDetail';
import RepleBoardPage from './components/page/RepleBoardPage';
import Signup from './components/member/Signup';

function App({imageUploader}) {
  return (
    <>
      <Routes>
        <Route path='/' exact={true} element={<LoginPage />} />
        <Route path='/home' exact={true} element={<HomePage />} />
        <Route path='/member/signup' exact={true} element={<Signup />} />
        <Route path='/repleboard' element={<RepleBoardPage />} />
        <Route path='/dept/:gubun' element={<DeptPage imageUploader={imageUploader} />} />
        {/* 컴포넌트 함수를 호출하는 것이다 - 마운트(화면이 보여지는 것) - return이 호출되었다 */}
        <Route path='/deptdetail/:deptno' element={<DeptDetail imageUploader={imageUploader} />} />
        <Route path='/auth/kakao/callback' exact={true} element={<KakaoRedirectHandler />} />
        <Route path="/member" exact={true} element={<MemberPage imageUploader={imageUploader} />} />
        <Route path="/profile" exact={true} element={<Profile />} />
      </Routes>
    </>
  );
}

export default App;

 

<리액트 스프링 회원가입 - LoginPage.jsx>

import React from 'react'
import Image from 'react-bootstrap/Image'
import { Link } from 'react-router-dom'

const LoginPage = () => {
  const REDIRECT_URI = "http://localhost:3000/auth/kakao/callback"
  const KAKAO_AUTH_URL = `https://kauth.kakao.com/oauth/authorize?client_id=${process.env.REACT_APP_KAKAO_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code`
  return (
    <>
      <div>
        <a href={KAKAO_AUTH_URL}>
        <Image src='/images/kakao/kakao_login_medium_wide.png' />
        </a>
      </div>
      <Link to={"/member/signup/"} className="btn btn-primary">회원가입</Link>
    </>
  )
}

export default LoginPage

 

 

<리액트 스프링 회원가입 - Signup.jsx>

import React, { useCallback, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { memberInsertDB } from '../../service/dbLogic'
import { BButton, ContainerDiv, FormDiv, HeaderDiv } from '../styles/FormStyle'

// 회원가입 페이지
const Signup = () => { // 컴포넌트 함수
  // useXXX -> 리액트 훅(Hook), 16.8버전 등장
  // -> 그 전까지는 클래스 지원 되던것을 함수형 프로그래밍에대한 이점으로 훅 지원하게됨
  const navigate = useNavigate()
  const[mem_uid, setMemuid] = useState("")
  const[mem_pw, setMempw] = useState("")
  const[mem_name, setMemname] = useState("")
  const[mem_nickname, setMemnickname] = useState("")
  const[mem_email, setMememail] = useState("")
  const[mem_tel, setMemtel] = useState("")
  const[mem_gender, setMemgender] = useState("")
  const[mem_birthday, setMembirthday] = useState("")

  const handleID= useCallback((e) => {
    setMemuid(e)
    console.log(e)
  }, [])
  const handlePW= useCallback((e) => {
    setMempw(e)
  }, [])
  const handleName= useCallback((e) => {
    setMemname(e)
  }, [])
  const handleNickname= useCallback((e) => {
    setMemnickname(e)
  }, [])
  const handleEmail= useCallback((e) => {
    setMememail(e)
  }, [])
  const handleTel= useCallback((e) => {
    setMemtel(e)
  }, [])
  const handleGender= useCallback((e) => {
    setMemgender(e)
  }, [])
  const handleBirthday= useCallback((e) => {
    setMembirthday(e)
  }, [])

  // Post, @RequestBody, {} -> Map or VO -> 비동기처리 -> Promise(resolve, reject)
  // async - await
  const memberInsert = async() => {
    const member = {
      mem_uid: mem_uid,
      mem_pw: mem_pw,
      mem_name: mem_name,
      mem_nickname: mem_nickname,
      mem_email: mem_email,
      mem_tel: mem_tel,
      mem_gender: mem_gender,
      mem_birthday: mem_birthday
    }
    const res = await memberInsertDB(member)
    console.log(res + ", " + res.data)
    if(!res.data) {
      console.log('회원가입에 실패하였습니다.')
    } else {
      console.log('회원가입 성공!')
      // 회원가입 성공시 로그인 화면을 이동
      navigate("/")
    }
  }

  return (
    <>
      <ContainerDiv>
        <HeaderDiv>
          <h3 style={{marginLeft:"10px"}}>회원가입</h3>
        </HeaderDiv>
        <FormDiv>
          <div style={{width:"100%", maxWidth:"2000px"}}>
            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h4>아이디</h4> 
            </div>
            <input id="mem_uid" type="text" maxLength="50" placeholder="아이디를 입력하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handleID(e.target.value)}} />

            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h4>비밀번호</h4> 
            </div>              
            <input id="mem_pw" type="text" maxLength="50" placeholder="비밀번호를 입력하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handlePW(e.target.value)}} />

            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h4>비밀번호 확인</h4> 
            </div>              
            <input id="mem_pw2" type="text" maxLength="50" placeholder="비밀번호를 확인하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} />

            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h4>이름</h4> 
            </div>              
            <input id="mem_name" type="text" maxLength="50" placeholder="이름을 입력하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handleName(e.target.value)}} />
              
            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h4>닉네임</h4> 
            </div>              
            <input id="mem_nickname" type="text" maxLength="50" placeholder="닉네임을 입력하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handleNickname(e.target.value)}} />

            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h4>이메일</h4> 
            </div>              
            <input id="mem_email" type="text" maxLength="50" placeholder="이메일을 입력하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handleEmail(e.target.value)}} />

            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h4>전화번호</h4> 
            </div>              
            <input id="mem_tel" type="text" maxLength="50" placeholder="전화번호를 입력하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handleTel(e.target.value)}} />

            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h4>성별</h4> 
            </div>              
            <input id="mem_gender" type="text" maxLength="50" placeholder="성별을 선택하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handleGender(e.target.value)}} />

            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h4>생일</h4> 
            </div>              
            <input id="mem_birthday" type="text" maxLength="50" placeholder="생일을 입력하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}}  onChange={(e)=>{handleBirthday(e.target.value)}} />

              <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <BButton onClick={memberInsert}>가입</BButton>
              <hr style={{margin:'10px 0px 10px 0px'}}/>
            </div>
          </div>
        </FormDiv>
      </ContainerDiv>
    </>
  )
}

export default Signup

 

<리액트 스프링 회원가입 - dbLogic.js>

import axios from "axios";

export const memberListDB = (member) => {
  return new Promise((resolve, reject) => {
    try {
      const response = axios({
        method: "get",
        url: process.env.REACT_APP_SPRING_IP + "member/memberList",
        params: member,
      });
      resolve(response);
    } catch (error) {
      reject(error);
    }
  });
};

export const memberInsertDB = (member) => {
  return new Promise((resolve, reject) => {
    console.log(member)
    try {
      const response = axios({
        method: "post", // @RequestBody
        url: process.env.REACT_APP_SPRING_IP + "member/memberInsert",
        data: member, // post방식 data
      });
      resolve(response);
    } catch (error) {
      reject(error);
    }
  });
};

export const memberUpdateDB = (member) => {
  return new Promise((resolve, reject) => {
    console.log(member)
    try {
      const response = axios({
        method: "post", // @RequestBody
        url: process.env.REACT_APP_SPRING_IP + "member/memberUpdate",
        data: member, // post방식 data
      });
      resolve(response); // 요청 처리 성공했을 때
    } catch (error) {
      reject(error); // 요청 처리 실패했을 때
    }
  });
};

export const memberDeleteDB = (member) => {
  return new Promise((resolve, reject) => {
    console.log(member)
    try {
      const response = axios({
        method: "get",
        url: process.env.REACT_APP_SPRING_IP + "member/memberDelete",
        params: member,
      });
      resolve(response); // 요청 처리 성공했을 때
    } catch (error) {
      reject(error); // 요청 처리 실패했을 때
    }
  });
};

export const deptInsertDB = (dept) => {
  return new Promise((resolve, reject) => {
    try {
      const response = axios({
        method: "post", // @RequestBody
        url: process.env.REACT_APP_SPRING_IP + "dept/deptInsert",
        data: dept, // post방식 data
      });
      resolve(response);
    } catch (error) {
      reject(error);
    }
  });
};

export const deptUpdateDB = (dept) => {
  return new Promise((resolve, reject) => {
    console.log(dept)
    try {
      const response = axios({
        method: "post", // @RequestBody
        url: process.env.REACT_APP_SPRING_IP + "dept/deptUpdate",
        data: dept, // post방식 data
      });
      resolve(response); // 요청 처리 성공했을 때
    } catch (error) {
      reject(error); // 요청 처리 실패했을 때
    }
  });
};

export const deptDeleteDB = (dept) => {
  return new Promise((resolve, reject) => {
    try {
      const response = axios({
        method: "get",
        url: process.env.REACT_APP_SPRING_IP + "dept/deptDelete",
        params: dept,
      });
      resolve(response); // 요청 처리 성공했을 때
    } catch (error) {
      reject(error); // 요청 처리 실패했을 때
    }
  });
};

export const deptListDB = (dept) => {
  return new Promise((resolve, reject) => {
    try {
      const response = axios({
        method: "get",
        url: process.env.REACT_APP_SPRING_IP + "dept/deptList",
        params: dept, // 쿼리스트링은 header에 담김 - get방식 params
      });
      resolve(response);
    } catch (error) {
      reject(error);
    }
  });
};

 

<리액트 스프링 회원가입 - RestMemberController.java>

package com.example.demo.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.logic.BoardLogic;
import com.example.demo.logic.MemberLogic;
import com.google.gson.Gson;

@RestController
@RequestMapping("/member/*")
public class RestMemberController {
	// Logger logger = LoggerFactory.getLogger(RestMemberController.class);
	Logger logger = LogManager.getLogger(RestMemberController.class);
	
	@Autowired
	private MemberLogic memberLogic = null;
	
	/**
	 * 회원 목록
	 * @param pMap
	 * @return
	 */
	@GetMapping("memberList")
	public String memberList(@RequestParam Map<String, Object> pMap) {
		logger.info("memberList 호출");
		String temp = null;
		List<Map<String, Object>> mList = new ArrayList<>();
		mList = memberLogic.memberList(pMap);
		Gson g = new Gson();
		temp = g.toJson(mList);
		return temp;
	}
	
	/**
	 * 회원 등록
	 * @param pMap
	 * @return
	 */
	@PostMapping("memberInsert")
	public String memberInsert(@RequestBody Map<String, Object> pMap) {
		// 리액트에서 body에 {}객체리터럴로 넘겨준 정보를 Map이나 VO에 담을 수 있다
		logger.info("memberInsert 호출");
		logger.info(pMap);
		int result = 0;
		result = memberLogic.memberInsert(pMap);
		return String.valueOf(result);
	}
	
	/**
	 * 회원 정보 수정
	 * @param pMap
	 * @return
	 */
	@PostMapping("memberUpdate")
	public String memberUpdate(@RequestBody Map<String, Object> pMap) {
		logger.info("memberUpdate 호출");
		logger.info(pMap);
		int result = 0;
		result = memberLogic.memberUpdate(pMap);
		return String.valueOf(result);
	}
	
	/**
	 * 회원 정보 삭제
	 * @param pMap
	 * @return
	 */
	@GetMapping("memberDelete")
	public String memberDelete(@RequestParam Map<String, Object> pMap) {
		logger.info("memberDelete 호출");
		logger.info(pMap);
		int result = 0;
		result = memberLogic.memberDelete(pMap);
		return String.valueOf(result);
	}
}

 

 

<리액트 스프링 회원가입 - MemberLogic.java>

package com.example.demo.logic;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.dao.MemberDao;

/*
 * 모델계층(MemberLogic[직접 오라클과 연동하지 않음] + MemberDao[데이터셋])
 * 공통된 관심사 분리(AspectJ 프레임워크 - 오픈소스 참고)
 * 트랜잭션 처리 지원받음
 */
@Service
public class MemberLogic {
	Logger logger = LogManager.getLogger(MemberLogic.class);

	@Autowired
	private MemberDao memberDao = null;
	
	public List<Map<String, Object>> memberList(Map<String, Object> pMap) {
		logger.info("memberList 호출");
		List<Map<String,Object>> mList = new ArrayList<>();
		mList= memberDao.memberList(pMap);
		return mList;
	}
	
	public int memberInsert(Map<String, Object> pMap) {
		logger.info("memberInsert 호출");
		logger.info(pMap);
		int result = 0;
		result = memberDao.memberInsert(pMap);
		return result;
	}

	public int memberUpdate(Map<String, Object> pMap) {
		logger.info("memberUpdate 호출");
		logger.info(pMap);
		int result = 0;
		result = memberDao.memberUpdate(pMap);
		return result;
	}

	public int memberDelete(Map<String, Object> pMap) {
		logger.info("memberDelete 호출");
		logger.info(pMap);
		int result = 0;
		result = memberDao.memberDelete(pMap);
		return result;
	}
}

 

<리액트 스프링 회원가입 - MemberDao.java>

package com.example.demo.dao;

import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
// import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

// @Repository -> MVC패턴 공부를위해 Service를 사용함
@Service
public class MemberDao {
	Logger logger = LogManager.getLogger(MemberDao.class);
	
	@Autowired
	private SqlSessionTemplate sqlSessionTemplate = null;

	public List<Map<String, Object>> memberList(Map<String, Object> pMap) {
		logger.info("memberList 호출");
		List<Map<String,Object>> mList = sqlSessionTemplate.selectList("memberList", pMap);
		return mList;
	}
	
	public int memberInsert(Map<String, Object> pMap) {
		logger.info("memberInsert 호출");
		logger.info(pMap);
		int result = 0;
		result = sqlSessionTemplate.update("memberInsert", pMap);
		return result;
	}

	public int memberUpdate(Map<String, Object> pMap) {
		logger.info("memberUpdate 호출");
		logger.info(pMap);
		int result = 0;
		result = sqlSessionTemplate.update("memberUpdate", pMap);
		return result;
	}

	public int memberDelete(Map<String, Object> pMap) {
		logger.info("memberDelete 호출");
		logger.info(pMap);
		int result = 0;
		result = sqlSessionTemplate.delete("memberDelete", pMap);
		return result;
	}
}

 

<리액트 스프링 회원가입 - member.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="com.example.demo">
	<select id="getToday" resultType="string" parameterType="string">
		SELECT
		to_char(sysdate, 'YYYY-MM-DD') FROM dual
	</select>

	<select id="login" parameterType="map" resultType="string">
		select mem_name from member230324
		<where>
			<if test='mem_uid!=null and mem_uid.length()>0'>
				AND mem_uid = #{mem_uid}
			</if>
			<if test='mem_pw!=null and mem_pw.length()>0'>
				AND mem_pw = #{mem_pw}
			</if>
		</where>
	</select>

	<!-- 회원 목록 -->
	<select id="memberList" parameterType="map" resultType="map">
		select mem_uid, mem_name from member230324
		<where>
			<if test='mem_uid!=null and mem_uid.length()>0'>
				AND mem_uid = #{mem_uid}
			</if>
		</where>
	</select>

	<!-- 회원 정보 입력 -->
	<insert id="memberInsert" parameterType="map">
		INSERT INTO member230324(mem_no
            <if test="mem_uid != null">
                ,mem_uid
            </if>
            <if test="mem_pw != null">
                ,mem_pw
            </if>
            <if test="mem_name != null">
                ,mem_name
            </if>
            <if test="mem_nickname != null">
                ,mem_nickname
            </if>
            <if test="mem_email != null">
                ,mem_email
            </if>
            <if test="mem_tel != null">
                ,mem_tel
            </if>
            <if test="mem_gender != null">
                ,mem_gender
            </if>
            <if test="mem_birthday != null">
                ,mem_birthday
            </if>
            )
		VALUES (seq_member_no.nextval
            <if test="mem_uid != null">
                ,#{mem_uid}
            </if>
            <if test="mem_pw != null">
                ,#{mem_pw}
            </if>
            <if test="mem_name != null">
                ,#{mem_name}
            </if>
            <if test="mem_nickname != null">
                ,#{mem_nickname}
            </if>
            <if test="mem_email != null">
                ,#{mem_email}
            </if>
            <if test="mem_tel != null">
                ,#{mem_tel}
            </if>
            <if test="mem_gender != null">
                ,#{mem_gender}
            </if>
            <if test="mem_birthday != null">
                ,#{mem_birthday}
            </if>
            )
	</insert>
</mapper>

댓글