고래씌
[SpringBoot] 2-1. React에 스프링부트 연동(게시판, 채팅방 목록) 본문
1. React에 스프링부트 연동(게시판)
=> 메뉴앱 프로젝트 생성

=> 사용할 패키지 다운받기
cd .\menuapp\
① axios를 사용하기 위해 패키지 다운
npm install --save axios
② bootstrap 사용하기 위해 다운
npm install --save bootstrap
③ npm start
=> public폴더안에 css폴더와 images 폴더 그대로 복사하여 붙여놓기 해줌
=> css폴더안에 있는 style.css는 모두 App.css로 옮김
▶ src > components폴더 > Hedader.js 파일 생성
export default function Header(){
return (
<header>
<div id="header-container">
<h2>Menu</h2>
</div>
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<a className="navbar-brand" href="#">
<img src="/resources/images/logo-spring.png" alt="스프링로고" width="50px" />
</a>
<button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span classNameName="navbar-toggler-icon"></span>
</button>
<div classNameName="collapse navbar-collapse" id="navbarNav">
</div>
</nav>
</header>
)
}
▶ GetMenu.js
import {useState} from "react"
import axios from 'axios';
export default function GetMenu(){
const [menus, setMenus] = useState([]);
function getMenus(){
/*
CORS 정책 위반
모든 브라우저는 보안상의 이유로 동일한 출처(Origin)가 아닌 경로에서 들어오는
리소스 요청에 대해서는 전부 차단을 함
* Origin ? 프로토콜 + ip주소 + 포트번호 => http://localhost:3000
cors 정책은 값을 내려주는 서버측에서 허용을 해줘야지 요청/응답이 가능하다.
*/
// 1차) 비동기요청 보내기
axios
.get("http://localhost:8083/springboot/menus") // 전달할 데이터가 있다면 , {} 이런 형식으로 붙혀서 보내야하는데 전달할 데이터없기때문에 생략가능
// 2차) 요청받은 데이터로 setMenus함수 호출
.then(response => setMenus(response.data))
}
return(
<>
<div className='menu-test'>
<h4>전체메뉴조회기능(GET)</h4>
<input type='button' className='btn btn-block btn-outline-success btn-send'
id='btn-menus' value="전송" onClick={getMenus} />
</div>
<div id="menus-result" className='result'>
<table className="table">
<thead>
<tr>
<th>번호</th>
<th>음식점</th>
<th>메뉴</th>
<th>가격</th>
<th>타입</th>
<th>맛</th>
</tr>
</thead>
<tbody>
{
menus.map((menu) => {
return (
<tr key={menu.id}>
<td>{menu.id}</td>
<td>{menu.restaurant}</td>
<td>{menu.name}</td>
<td>{menu.price}</td>
<td>{menu.type}</td>
<td>{menu.taste}</td>
</tr>
)
})
}
</tbody>
</table>
</div>
</>
)
}
=> springboot에서 MenuController.java에서 다음과 같이 수정해야 전체보기를 클릭하였을때 정상적으로 표시된다.
▶ MenuController.java
// 메뉴 목록 조회
@GetMapping("/menus")
public List<Menu> menus(HttpServletResponse res){
// 응답헤더에 Accsess-Controll-Allow-Origin 추가
System.out.println(menuService.selectMenus());
List<Menu> list = menuService.selectMenus();
// return menuService.selectMenus();
res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://localhost:3000"); // origin이 접근하는 걸 허용하는 속성
// 허용하고자하는 출처(ORIGIN)
return list;
}

=> 하지만 이렇게 하나하나 "http://localhost:8083" 지정하기 어렵다. 그러니 proxy 설정을 추가한다!
package.json 파일에 들어가서 맨 아래에 추가

▶ GetMenu.js 아래와 같이 수정

=> 프록시 설정 완료 후에는 localhost:3000으로 들어오는 모든 요청을 proxy가 localhost:8083 서버로 전달하여 대신 응답결과를 얻어와서, 현재서버에 전달을 해준다.
proxy : 중계자역할을 하는 객체
▶ PostMenu.js
import axios from "axios";
import { useState } from "react";
export default function PostMenu(){
const [newMenu, setNewMenu] = useState({
restaurant : '', name : '', price : '', type : '',
taste : ''
});
const handleInputChange = (e) => {
const {name, value} = e.target;
setNewMenu({...newMenu, [name] : value});
}
console.log(newMenu);
const insertMenu = (e) => {
e.preventDefault();
// 프록시 사용하지 않고 등록되는 기능 추가하기
axios
.post("http://localhost:3000/springboot/menu", newMenu) // 자동 매핑이 돼서 JSON.stringify 쓸필요 X
.then(function(response){
const {msg} = response.data;
alert(msg);
})
.catch(console.log)
.finally(
function(){
setNewMenu({
restaurant : '',
name : '',
price : '',
type : '',
taste : ''
});
e.target.reset();
}
)
}
return (
<>
<div className="menu-test">
<h4>메뉴 등록하기(POST)</h4>
<form id="menuEnrollFrm" onSubmit={insertMenu} >
<input type="text" name="restaurant" placeholder="음식점" className="form-control" onChange={handleInputChange} />
<br />
<input type="text" name="name" placeholder="메뉴" className="form-control" onChange={handleInputChange}/>
<br />
<input type="number" name="price" placeholder="가격" className="form-control" onChange={handleInputChange}/>
<br />
<div className="form-check form-check-inline">
<input type="radio" className="form-check-input" name="type" id="post-kr" value="kr" onChange={handleInputChange}/>
<label htmlFor="post-kr" className="form-check-label">한식</label>
<input type="radio" className="form-check-input" name="type" id="post-ch" value="ch" onChange={handleInputChange}/>
<label htmlFor="post-ch" className="form-check-label">중식</label>
<input type="radio" className="form-check-input" name="type" id="post-jp" value="jp" onChange={handleInputChange}/>
<label htmlFor="post-jp" className="form-check-label">일식</label>
</div>
<br />
<div className="form-check form-check-inline">
<input type="radio" className="form-check-input" name="taste" id="post-hot" value="hot" onChange={handleInputChange}/>
<label htmlFor="post-hot" className="form-check-label">매운맛</label>
<input type="radio" className="form-check-input" name="taste" id="post-mild" value="mild" onChange={handleInputChange}/>
<label htmlFor="post-mild" className="form-check-label">순한맛</label>
</div>
<br />
<input type="submit" className="btn btn-block btn-outline-success btn-send" value="등록"/>
</form>
</div>
</>
)
}
▶ MenuController.java
=> @CrossOrigin 어노테이션 추가

▶ App.js
import './App.css';
import 'bootstrap/dist/css/bootstrap.css' // 부트스트랩 css 가져오기
import Header from './components/Header';
import GetMenu from './components/GetMenu';
import PostMenu from './components/PostMenu';
function App() {
return (
<div id='container'>
<Header />
<section id="content">
<div id='menu-container' className='text-center'>
<GetMenu />
<PostMenu />
</div>
</section>
</div>
);
}
export default App;
2. React에 스프링부트 연동(채팅)
1) 시스템 관리자 계정에서 다음과 같이 설정

2) CHAT 에 SQL문을 열어서 아래와 같이 추가
DROP TABLE MEMBER;
DROP TABLE CHAT_MESSAGE;
DROP TABLE CHAT_ROOM;
DROP TABLE CHAT_ROOM_JOIN;
DROP SEQUENCE SEQ_UNO;
DROP SEQUENCE SEQ_RNO;
CREATE TABLE MEMBER (
USER_NO NUMBER PRIMARY KEY,
EMAIL VARCHAR2(30) NOT NULL UNIQUE,
USER_PWD VARCHAR2(100) NOT NULL,
NICK_NAME VARCHAR2(15) NOT NULL,
ENROLL_DATE DATE DEFAULT SYSDATE,
MODIFY_DATE DATE DEFAULT SYSDATE,
STATUS VARCHAR2(1) DEFAULT 'Y' CHECK (STATUS IN('Y', 'N')),
PROFILE VARCHAR2(200)
);
INSERT INTO MEMBER
VALUES(
SEQ_UNO.NEXTVAL,
'sample1@naver.com',
1234,
'루피1',
DEFAULT,
DEFAULT,
DEFAULT,
'/images/user0.jpg'
);
INSERT INTO MEMBER
VALUES(
SEQ_UNO.NEXTVAL,
'sample2@naver.com',
1234,
'루피2',
DEFAULT,
DEFAULT,
DEFAULT,
'/images/user1.jpg'
);
INSERT INTO MEMBER
VALUES(
SEQ_UNO.NEXTVAL,
'sample3@naver.com',
1234,
'루피3',
DEFAULT,
DEFAULT,
DEFAULT,
'/images/user2.jpg'
);
INSERT INTO MEMBER
VALUES(
SEQ_UNO.NEXTVAL,
'sample4@naver.com',
1234,
'루피4',
DEFAULT,
DEFAULT,
DEFAULT,
'/images/user3.jpg'
);
INSERT INTO MEMBER
VALUES(
SEQ_UNO.NEXTVAL,
'sample5@naver.com',
1234,
'루피5',
DEFAULT,
DEFAULT,
DEFAULT,
'/images/user4.jpg'
);
INSERT INTO MEMBER
VALUES(
SEQ_UNO.NEXTVAL,
'sample6@naver.com',
1234,
'루피6',
DEFAULT,
DEFAULT,
DEFAULT,
'/images/user5.jpg'
);
CREATE SEQUENCE SEQ_UNO NOCACHE;
CREATE TABLE CHAT_MESSAGE(
CM_NO NUMBER PRIMARY KEY,
MESSAGE VARCHAR2(4000),
CREATE_DATE DATE,
CHAT_ROOM_NO NUMBER,
USER_NO NUMBER
);
CREATE SEQUENCE SEQ_CM_NO NOCACHE;
CREATE TABLE CHAT_ROOM(
CHAT_ROOM_NO NUMBER PRIMARY KEY,
TITLE VARCHAR2(400),
STATUS VARCHAR2(1) DEFAULT 'Y',
USER_NO NUMBER
);
CREATE SEQUENCE SEQ_CRM_NO NOCACHE;
CREATE TABLE CHAT_ROOM_JOIN(
USER_NO NUMBER,
CHAT_ROOM_NO NUMBER,
USER_STATUS NUMBER,
PRIMARY KEY(USER_NO, CHAT_ROOM_NO)
);
COMMIT;
3) pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kh</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-1</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.3</version>
<scope>test</scope>
</dependency>
<!-- 웹소켓 의존성 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4) application.properties
server.servlet.context-path=/api
server.port=8084 #datasource spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe spring.datasource.username=CHAT spring.datasource.password=CHAT spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver #mybatis mybatis.mapper-locations=classpath*:/mapper/**/*.xml mybatis.configuration.jdbc-type-for-null=NULL mybatis.configuration.map-underscore-to-camel-case=true mybatis.type-aliases-package=com.kh.api #mybatis.type-handlers-package=com.kh.springboot.menu.model.typeHandler |
5) 다시 myapp으로 들어간다!
cd .\myapp\
npm start
▶ App.js

=> 추가
=> pages > ChattingRoom.js, pages > ChattingRoomList.js 파일 생성
▶ ChattingRoomList.js (채팅방 목록)
import { useEffect } from "react";
import { useState } from "react";
import axios from 'axios';
export default function ChattingRoomList(){
let [채팅방목록, 채팅방목록변경] = useState([]);
let [모달, 모달창오픈] = useState(false);
useEffect(() => {
axios
.get("http://localhost:3000/api/chatRoomList")
.then(
// 응답데이터 함수
(response) => {
console.log(response);
채팅방목록변경(response.data); // 채팅방 목록페이지 조회
}
)
.catch( (err) => console.log(err))
}, [])
return (
<>
<section className="board-list">
<h1 className="board-name">채팅방 목록</h1>
<div className="list-wrapper">
<table className="list-table">
<thead>
<tr>
<th>방번호</th>
<th>채팅방 주제(제목)</th>
<th>개설자</th>
<th>참여인원수</th>
</tr>
</thead>
<tbody>
{
채팅방목록.length == 0 ?
(
<tr>
<td colSpan={4}>존재하는 채팅방이 없습니다.</td>
</tr>
) : (
채팅방목록.map((채팅방) => {
return(
<tr key={채팅방.chatRoomNo}>
<td>{채팅방.chatRoomNo}</td>
<td>{채팅방.title}</td>
<td>{채팅방.nickName}</td>
<td>{채팅방.cnt}</td>
</tr>
)
})
)
}
</tbody>
</table>
<div className="btn-area">
<button onClick={ () => 모달창오픈(true)}>채팅방 만들기</button>
</div>
</div>
</section>
{모달 && <채팅창 모달창오픈={모달창오픈} />}
</>
)
}
function 채팅창({모달창오픈}){
return(
<div className="modal">
<div className="modal-content">
<span className="close" onClick={() => 모달창오픈(false)}>×</span>
<div className="login-form">
<h3>채팅방 만들기</h3>
<input type="text" name="title" className="form-control" placeholder="채팅방 제목"
/><button>만들기</button>
</div>
</div>
</div>
)
}
▶ package.json
=> 프록시 추가

=> spingboot로 가서 아래에 폴더및 class 파일 생성


▶ Member.java
package com.kh.api.model.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Member {
private int userNo;
private String eamil;
private String nickName;
private String userPwd;
private String profile;
private String status;
private String enrollDate;
private String modifyDate;
private UserStatus userStatus;
}
▶ ChatRoom.js
package com.kh.api.model.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatRoom {
private int chatRoomNo;
private String title;
private String status;
private int userNo;
private String nickName;
private int cnt;
}
▶ ChatMessage.java
package com.kh.api.model.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessage {
private int cmNo;
private String message;
private String createDate;
private int chatRoomNo;
private int userNo;
private String nickName;
private String profile;
}
▶ ChatRoomJoin.java
package com.kh.api.model.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatRoomJoin {
private int userNo;
private int chatRoomNo;
private int userStatus; // 1 → 접속중, 2 → 나감
}
▶ UserStatus.java (Enum 객체) (com.kh.api.model.vo)
package com.kh.api.model.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
public enum UserStatus {
@JsonProperty("1")
online(1),
@JsonProperty("2")
offline(2);
private int userStatus;
UserStatus(int userStatus){
this.userStatus = userStatus;
}
// Json 형태로 변환시 문자열형태로 변환해야해서 String형으로 getter함수 사용
@JsonValue
public String getUserStatus() {
return String.valueOf(userStatus);
}
public static UserStatus getValueOfUserStatus(int userStatus) {
UserStatus [] list = UserStatus.values();
for(UserStatus us : list) {
if(us.userStatus == userStatus) {
return us;
}
}
return null; // 못찾았다면 null값 반환
}
}
▶ ChatController.java
package com.kh.api.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kh.api.model.service.ChatService;
import com.kh.api.model.vo.ChatRoom;
// 모두 JSON으로 받아줄 예정
@RestController
public class ChatController {
@Autowired
private ChatService service;
// 채팅방 목록
@GetMapping("/chatRoomList")
public List<ChatRoom> selectChatRooms(){
return service.selectChatRooms();
}
}
▶ ChatService.java
package com.kh.api.model.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.kh.api.model.dao.ChatDao;
import com.kh.api.model.vo.ChatRoom;
@Service
public class ChatService {
@Autowired
private ChatDao dao;
// 채팅방 목록
public List<ChatRoom> selectChatRooms() {
return dao.selectChatRooms();
}
}
▶ ChatDao.java
package com.kh.api.model.dao;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.kh.api.model.vo.ChatRoom;
@Repository
public class ChatDao {
@Autowired
private SqlSessionTemplate session;
// 채팅방 목록
public List<ChatRoom> selectChatRooms() {
return session.selectList("chatMapper.selectChatRooms");
}
}
▶ chatting-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="chatMapper">
<select id="selectChatRooms" resultType="chatRoom">
SELECT
CHAT_ROOM_NO,
TITLE,
NICK_NAME,
(SELECT COUNT(*) FROM CHAT_ROOM_JOIN CRJ WHERE CRJ.CHAT_ROOM_NO = CR.CHAT_ROOM_NO) CNT,
USER_NO
FROM CHAT_ROOM CR
LEFT JOIN MEMBER USING (USER_NO)
</select>
</mapper>
▶ 결과

'Server > SpringBoot' 카테고리의 다른 글
[SpringBoot] 2-3. React 스프링부트에 연동(채팅방 상세보기, 메시지) (0) | 2024.02.06 |
---|---|
[SpringBoot] 2-2. React에 스프링부트 연동(채팅방 생성) (0) | 2024.02.05 |
[SpringBoot] 1-5. 메뉴 삭제 - DELETE (0) | 2024.02.02 |
[SpringBoot] 1-4. 메뉴 검색, 메뉴 수정 - PUT 방식 (0) | 2024.02.02 |
[SpringBoot] 1-3. 메뉴 추가(insert) - POST 방식 (0) | 2024.02.02 |