참신한것을 만들어보려고 하다가, 문득 캐치마인드를 만들어 보고 싶어졌다.
서버와 클라이언트를 구축하고, socket.io를 이용하여 채팅과 그리기를 구현한다.
로그인도 구현하는데, 토큰은 나중에 사용하도록하고 당장은 자동로그인 없이 진행한다.
로그인의 경우 mysql을, 그리기 관련해서는 mongodb를 사용해보자.
로그인이 있으니 회원가입도 해야지, 회원가입에 필요한 정보들은 일부 mysql에서 관리(팀,직책 등)하고, 그 외의 이미지 등은 입력받는 것으로 하자.
CRA로 typescript 템플릿을 적용시키자. 그리고 react-router로 라우팅을 하고 로그인, 회원가입, 게임 페이지를 구분해주자.
<BrowserRouter>
<Switch>
<Route path="/" exact component={LoginPage}/>
<Route path="/SignUp" exact component={SignUp}/>
<Route path="/game" exact component={CatchMind}/>
</Switch>
</BrowserRouter>
간단하게 입장 페이지인 로그인 페이지를 만들어주고
const LoginPage:React.FC = ()=>{
const history = useHistory();
const dispatch = useDispatch();
const [id,setId]=useState("");
const changeIdInput = (e:ChangeEvent<HTMLInputElement>):void=>{
setId(()=>e.target.value);
};
const loginClick = ()=>{
if(!id) return;
dispatch(loginAsyc.request({name:id,history}));
}
return (
<div style={{position:"relative",overflow:"hidden",height:"100vh"}}>
<h2><span>캐치</span>마인드</h2>
<LoginBox>
<h3>아이엠폼 캐치마인드(ver0.1)</h3>
<label>
로그인
<input type="text" value={id} onChange={changeIdInput} placeholder="아이디는 이름입니다~"/>
</label>
<div>
<button onClick={loginClick}>로그인버튼</button>
<button>
<Link to="/signUp">회원가입</Link>
</button>
</div>
</LoginBox>
</div>
)
};
export default LoginPage
이렇게 만들고 난뒤 회원가입 페이지도 만들자, 회원가입 페이지에서는 mysqldb에 저장되어 있는 팀명과 직책명을 가져와야하니까 useEffect를 사용해서 가져오자. 또한 서버로 이미지를 업로드 할 때, 업로드할 이미지의 프리뷰를 보여주기 위해서 이미지파일을 blob에 저장도 하도 base64로도 변환해주자.
useEffect(()=>{
(async ()=>{
signUpAxios.get().then(res=>{
setTeamsTItles(() => {
return{
teams:res.data.teams,
titles:res.data.titles
}
})
}).catch(e=>{
console.log(e);
})
})();
},[]);
const makeBase64 = (image:File):Promise<typeof image>=>new Promise(()=>{
const reader = new FileReader();
reader.readAsDataURL(image);
reader.onload = function(event){
const target = event.target as FileReader;
setPreview(()=>target.result as string)
return target.result
}; // data url!
})
<SignUpConatiner>
<h2>회원가입</h2>
<div>
<select value={selecteds.team} id="team" onChange={changeSelectValue}>
{teamsTitles.teams.map((item)=>(<option value={item} key={item}>{item}</option>))}
</select>
<select value={selecteds.title} id="title" onChange={changeSelectValue}>
{teamsTitles.titles.map((item)=>(<option value={item} key={item}>{item}</option>))}
</select>
</div>
<div>
<input type="text" value={inputs.name} onChange={changeValue} placeholder="이름을 입력하세용" id="name"/>
<input type="number" value={inputs.age} onChange={changeValue} placeholder="(원하는)나이를입력하세요" id="age"/>
</div>
{preview&&<img src={preview}/>}
<input type="file" accept="image/*" onChange={uploadImg}/>
<button onClick={singUpClick} >회원가입!</button>
{err&&{err}}
</SignUpConatiner>
이렇게 휘뚜루 마뚜루 프론트를 정리하구 서버로 넘어가면,
서버에서는 express를 이용하여 node server를 구현해주자.
또 코드관리를 편하게 하기 위해서 각 파트 별로(login 등)으로 라우터를 구분해주자.
const express = require('express')
const cors = require('cors');
const app = express();
app.use(login);
app.use(test);
require("dotenv").config();
app.use(cors());
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded())
app.get('/', function (req, res) {
res.send(true);
});
server.listen(8000)
login은 mysql을 이용한다고 했으니 mysql2라이브러리를 이용하여 로컬의 mysql에 접속하자
const mysql = require('mysql2/promise');
require("dotenv").config();
const connection = mysql.createPool({
host: process.env.mysql_dbhost,
port:process.env.mysql_port,
user:"root",
password:process.env.mysql_dbpassword,
database:process.env.mysql_database,
connectionLimit:10
});
module.exports = connection
접속을 했으면 먼저 로그인 페이지에서 요청하는 직책과 팀명을 보내주자.
router.get('/signUp',async(req,res)=>{
try{
const teams = await mysql.query("SELECT * FROM users.teams");
const titles = await mysql.query("SELECT * FROM users.titles");
res.send({teams:teams[0].map(item=>item.teamName),titles:titles[0].map(item=>item.title)});
}catch(e){
}
});
그리고 웹에서 보낼 회원가입 정보를 받아줄 API를 만든다. 이때 들어오는 이미지는 multer를 이용해서 서버의 imgs 폴더에 저장해주고, db에는 해당 경로만 저장한다.
const upload = multer({
storage:multer.diskStorage({
destination:function(req,file,cb){
cb(null,'uploads/')
},
filename:function(req,file,cb){
console.log(file);
cb(null,req.body.name + (file.originalname));
}
})
});
router.post('/signUp',upload.single('image'),async(req,res)=>{
const {name,age,title,team} = req.body;
const image = req.body.name + (req.file.originalname)
const isExist = await mysql.query(`SELECT * FROM users.users where age=${+age} and name="${name}"`)
if(isExist[0].length===0){
try{
await mysql.query(`insert into users.users (name,age,team,title) value("${name}",${+age},"${team}","${title}")`)
await mysql.query(`insert into users.images (image,name) value("${image}","${name}")`);
res.send("ok")
}catch(e){
console.log(e)
res.send("err")
}
}
});
회원 가입이 끝났으니 로그인을 마저 구현한다.
router.post('/login', async(req,res)=>{
const {name} = req.body;
const isExist = await mysql.query(`SELECT * FROM users.users where name="${name}"`);
if(isExist[0].length!==0){
const result = await mysql.query(`SELECT users.*,images.* from users inner join images on users.name = images.name where users.name="${name}"`);
const image = result[0][0].image;
const img = fs.readFileSync("uploads/"+image,'base64')
res.send({...result[0][0],image:img});
}else{
console.log(isExist[0].length === 0);
res.status(400).send({
name:"",
pid:0,
age:0,
title:"",
team:"",
err:"yes"
})
}
});
db상에서는 Pid를 이용하여 유저의 정보가 겹치지 않게 구현했으나, 로그인 아이디를 이름으로 하게 됨으로 동명이인의 경우가 발생할 수도 있게 되었다. 이는 나중에 수정해야겠다.