본문 바로가기
Development/C#

멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 66일차

by Mobics 2025. 3. 5.

 

목차


    Tic Tac Toe 서버 만들기

    25.03.04

    프로그램 설치

    >> Node.js

    : JavaScript를 사용하기 위해 --> 설치 완료 후, 명령 프롬프트에 'node'라고 치면 설치가 됐는지 확인할 수 있다.

    https://nodejs.org/ko

     

    Node.js — 어디서든 JavaScript를 실행하세요

    Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

    nodejs.org

     

    >> Postman

    : 설치 및 로그인 --> 회원가입

    https://www.postman.com/downloads/

     

    Download Postman | Get Started for Free

    Try Postman for free! Join 35 million developers who rely on Postman, the collaboration platform for API development. Create better APIs—faster.

    www.postman.com

     

    >> MongoDB

    https://www.mongodb.com/try/download/community

     

    Try MongoDB Community Edition

    Try MongoDB Community Edition on premise non-relational database including the Community Server and Community Kubernetes Operator for your next big project!

    www.mongodb.com

    --> 설치할 때, 아래 사진과 같이 Complete 선택, 이후로는 Next만 눌러 설치

     

    --> 이후 Name만 작성하여 Save&Connect

     

    >> SocketIOUnity

    : Unity를 이용하여 설치할 예정

     

    >> ParrelSync

    : Unity를 이용하여 설치할 예정

     

    Express 서버 만들기

    >> cmd에서 설치하고자 하는 경로로 이동 후, 명령어 입력

    npx express-generator --view=pug projectName

    --> 이후 프로젝트가 생성된 경로로 이동 (cd tictactoe-server)

     

    >> 라이브러리 설치

    : VisualStudio Code or Cursor에서 생성된 프로젝트 폴더 열기

    --> Open Folder로 프로젝트 폴더 열어서 Package.json 파일 열기

     

    >> cmd에서 프로젝트 경로로 이동하거나 Ctrl + ` 으로 Terminal을 열어서 명령어 입력

    npm install

     

    ※ 에러

    : 나온 경로로 따라 들어가서 'npm.ps1' 파일을 삭제한 후, 다시 명령어 실행

     

    >> 이후 아래 명령어로 서버 시작

    --> 연결 테스트 후, Ctrl + C 로 작업 중단

    npm start

     

    └ 서버 연결 테스트

    >> 브라우저

    : 인터넷의 주소창에 아래와 같이 입력하여 접속하면 Express 서버가 정상적으로 작동하는지 확인 가능

    localhost:3000

     

    heroku 프로그램..?

     

    >> Postman으로 테스트

    : 서버를 시작한 뒤, 테스트

    --> 아래에 respond with a resource가 나오면 성공 (routes/users.js 파일에 있는대로 나온 것)

     

    └ MongoDB에 연결

    : 아래 명령어를 통해 필요한 라이브러리 추가 설치 --> 서버를 가동 중이었다면 작업 중단 후 설치하기

    npm install bcrypt, express-session, session-file-store, socket.io, uuid, mongodb

     

    ※ 혹시 설치에 에러가 걸린다면 아래와 같이 ',' 를 쓰지말고 띄워쓰기로만 구분

    npm install bcrypt express-session session-file-store socket.io uuid mongodb

     

    >> app.js 파일에 코드 추가

    var mongodb = require('mongodb');
    var MongoClient = mongodb.MongoClient;
    
    
    // MongoDB 추가
    async function connectDB() {
      var databaseURL = "mongodb://localhost:27017/tictactoe";
    
      try {
        const database = await MongoClient.connect(databaseURL, {
          useNewUrlParser: true,
          useUnifiedTopology: true
        });
        console.log("DB 연결 완료 : " + databaseURL);
        app.set('database', database.db('tictactoe'));
    
        // 연결 종료 처리
        process.on("SIGINT", async () => {
          await database.close();
          console.log("DB 연결 종료");
          process.exit(0);
        });
      } catch (err) {
        console.error("DB 연결 실패 : " + err);
        process.exit(1);
      }
    }
    
    connectDB().catch(err => {
      console.error("초기 DB 연결 실패 : " + err);
      process.exit(1);
    });

     

    ※ MongoDB Compass 프로그램에서 URl 확인

     

    ※ 추가한 전체 코드 (app.js)

    var createError = require('http-errors');
    var express = require('express');
    var path = require('path');
    var cookieParser = require('cookie-parser');
    var logger = require('morgan');
    var mongodb = require('mongodb');       // 추가
    var MongoClient = mongodb.MongoClient;  // 추가
    
    var indexRouter = require('./routes/index');
    var usersRouter = require('./routes/users');
    const req = require('express/lib/request');
    
    var app = express();
    
    // MongoDB 추가
    async function connectDB() {
      var databaseURL = "mongodb://localhost:27017/tictactoe";
    
      try {
        const database = await MongoClient.connect(databaseURL, {
          useNewUrlParser: true,
          useUnifiedTopology: true
        });
        console.log("DB 연결 완료 : " + databaseURL);
        app.set('database', database.db('tictactoe'));
    
        // 연결 종료 처리
        process.on("SIGINT", async () => {
          await database.close();
          console.log("DB 연결 종료");
          process.exit(0);
        });
      } catch (err) {
        console.error("DB 연결 실패 : " + err);
        process.exit(1);
      }
    }
    
    connectDB().catch(err => {
      console.error("초기 DB 연결 실패 : " + err);
      process.exit(1);
    });
    
    // view engine setup
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'pug');
    
    app.use(logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', indexRouter);
    app.use('/users', usersRouter);
    
    // catch 404 and forward to error handler
    app.use(function(req, res, next) {
      next(createError(404));
    });
    
    // error handler
    app.use(function(err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};
    
      // render the error page
      res.status(err.status || 500);
      res.render('error');
    });
    
    module.exports = app;

     

    서버 기능 만들기

    └ 회원가입 만들기

    : users.js에 코드 작성 --> get 방식은 post 방식보다 정보의 노출이 더 발생하기 때문에 사용하지 않고, post 방식을 사용

    var bcrypt = require('bcrypt');
    var saltrounds = 10;
    
    
    // 회원가입
    router.post('/signup', async function(req, res, next) {
      try {
        var username = req.body.username;
        var password = req.body.password;
        var nickname = req.body.nickname;
    
        // 입력값 검증
        if (!username || !password || !nickname) {
          return res.status(400).send("모든 필드를 입력해주세요");
        }
    
        // 사용자 중복 체크
        var database = req.app.get('database');
        var users = database.collection('users');
       
        const existingUser = await users.findOne({ username: username });
        if (existingUser) {
          return res.status(409).send("이미 존재하는 사용자입니다");
        }
    
        // 비밀번호 암호화
        var salt = bcrypt.genSaltSync(saltrounds);
        var hash = bcrypt.hashSync(password, salt);
        
        // DB에 저장
        await users.insertOne({
          username: username,
          password: hash, // 해시된 비밀번호 저장
          nickname: nickname
        });
        res.status(201).send("사용자가 성공적으로 생성되었습니다");
      } catch (err) {
        console.error("사용자 추가 중 오류 발생 :", err);
        res.status(500).send("서버 오류가 발생했습니다");
      }
    });

     

    >> Postman에서 테스트

    : Collection 생성 후 테스트

    --> POST로 바꿔줘야 정상 작동

    --> VisualStudio Code에서 F5를 눌러 서버를 작동한 다음 Send를 눌러 테스트 (또는 Run and Debug)

    {
        "username": "test01",
        "password": "test1234",
        "nickname": "Test-01"
    }

     

    ※ 오타 확인하는 법

    google에 'json parser' 을 검색하여 json parser Online에 접속한 다음 코드를 복사 붙여넣기하여 확인 가능

     

    └ 로그인 만들기

    - users.js에 코드 작성

    var ResponseType = {
      INVALID_USERNAME: 0,
      INVALID_PASSWORD: 1,
      SUCCESS: 2
    }
    
    
    // 로그인
    router.post("/signin", async function(req, res, next) {
      try {
        var username = req.body.username;
        var password = req.body.password;
        
        var database = req.app.get('database');
        var users = database.collection('users');
    
        // 입력값 검증
        if (!username || !password) {
          return res.status(400).send("모든 필드를 입력해주세요.");
        }
    
        const existingUser = await users.findOne({ username: username });
        if (existingUser) {
          var compareResult = bcrypt.compareSync(password, existingUser.password);
          if (compareResult) {
            // 세션에 사용자 정보(로그인 정보) 저장
            req.session.isAuthenticated = true;
            req.session.userId = existingUser._id.toString();
            req.session.username = existingUser.username;
            req.session.nickname = existingUser.nickname;
    
            res.json({ result: ResponseType.SUCCESS });
          } else {
            res.json({ result: ResponseType.INVALID_PASSWORD });
          }
        } else {
          res.json({ result: ResponseType.INVALID_USERNAME });
        }
      } catch (err) {
        console.error("로그인 중 오류 발생.", err);
        res.status(500).send("서버 오류가 발생했습니다");
      }
    });

     

    - app.js에 코드 작성

    const session = require('express-session');             // 추가
    var fileStore = require('session-file-store')(session); // 추가
    
    
    // 로그인 만들기
    app.use(session({
      secret: process.env.SESSION_SECRET || 'session-login',
      resave: false,
      saveUninitialized: false,
      store: new fileStore({
        path: './sessions',
        ttl: 24 * 60 * 60, // 24시간
        reapInterval: 60 * 60 // 1시간
      }),
      cookie: {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        maxAge: 24 * 60 * 60 * 1000 // 24시간
      }
    }));

     

    >> Postman에서 테스트

    : VisualStudio Code에서 F5를 눌러 서버를 작동한 다음 Send를 눌러 테스트 (또는 Run and Debug)

    {
        "username": "test01",
        "password": "test1234"
    }

     

    └ 로그아웃 만들기

    : user.js에 코드 작성

    // 로그아웃
    router.post('/signout', function(req, res, next) {
      req.session.destroy((err) => {
        if (err) {
          console.log("로그아웃 중 오류 발생");
          return res.status(500).send("서버 오류가 발생했습니다.");
        }
        res.status(200).send("로그아웃 되었습니다.");
      });
    });

     

    >> Postman에서 테스트

    : 로그인을 먼저 한 다음 테스트

     

    └ 점수 추가 및 불러오기

    : user.js에 코드 작성 --> User의 정보와 엮여있어야 한다.

    // 점수 추가
    router.post('/addscore', async function(req, res, next) {
      try {
        // 로그인 여부 체크
        if (!req.session.isAuthenticated) {
          return res.status(400).send("로그인이 필요합니다.");
        }
    
        var userId = req.session.userId;
        var score = req.body.score;
    
        // 점수 유효성 검사
        if (!score || isNaN(score)) {
          return res.status(400).send("유효한 점수를 입력해주세요.");
        }
    
        var database = req.app.get('database');
        var users = database.collection('users');
    
        const result = await users.updateOne(
          { _id: new ObjectId(userId) },
          { 
              $set: {
              score: Number(score),
              updatedAt: new Date()
            }
          }
        );
    
        if (result.matchedCount === 0) {
          return res.status(400).send("사용자를 찾을 수 없습니다.");
        }
    
        res.status(200).json({ message: "점수가 성공적으로 업데이트 되었습니다."});
      } catch (err) {
        console.error("점수 추가 중 오류 발생 : ", err);
        res.status(500).send("서버 오류가 발생했습니다.");
      }
    });
    
    // 점수 조회
    router.get('/score', async function(req, res, next) {
      try {
        if (!req.session.isAuthenticated) {
          return res.status(403).send("로그인이 필요합니다.");
        }
    
        var userId = req.session.userId;
        var database = req.app.get('database');
        var users = database.collection('users');
    
        const user = await users.findOne({ _id: new ObjectId(userId) });
    
        if (!user) {
          return res.status(404).send("사용자를 찾을 수 없습니다.");
        }
    
        res.json({
          id: user._id.toString(),
          username: user.username,
          nickname: user.nickname,
          score: user.score || 0
        });
      } catch (err) {
        console.error("점수 조회 중 오류 발생 : ", err);
        res.status(500).send("서버 오류가 발생했습니다.");
      }
    });

     

     

    >> Postman에서 테스트

    - 점수 추가

    : 로그인을 먼저 한 다음 테스트

    {
        "score": 10
    }

     

    - 점수 불러오기

    : 로그인 및 점수 추가를 먼저 한 다음 테스트

     

    최종 코드

    >> app.js

    var createError = require('http-errors');
    var express = require('express');
    var path = require('path');
    var cookieParser = require('cookie-parser');
    var logger = require('morgan');
    var mongodb = require('mongodb');       // 추가
    var MongoClient = mongodb.MongoClient;  // 추가
    
    var indexRouter = require('./routes/index');
    var usersRouter = require('./routes/users');
    const req = require('express/lib/request');
    const session = require('express-session');             // 추가
    var fileStore = require('session-file-store')(session); // 추가
    
    var app = express();
    
    // 로그인 만들기
    app.use(session({
      secret: process.env.SESSION_SECRET || 'session-login',
      resave: false,
      saveUninitialized: false,
      store: new fileStore({
        path: './sessions',
        ttl: 24 * 60 * 60, // 24시간
        reapInterval: 60 * 60 // 1시간
      }),
      cookie: {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        maxAge: 24 * 60 * 60 * 1000 // 24시간
      }
    }));
    
    // MongoDB 추가
    async function connectDB() {
      var databaseURL = "mongodb://localhost:27017/tictactoe";
    
      try {
        const database = await MongoClient.connect(databaseURL, {
          useNewUrlParser: true,
          useUnifiedTopology: true
        });
        console.log("DB 연결 완료 : " + databaseURL);
        app.set('database', database.db('tictactoe'));
    
        // 연결 종료 처리
        process.on("SIGINT", async () => {
          await database.close();
          console.log("DB 연결 종료");
          process.exit(0);
        });
      } catch (err) {
        console.error("DB 연결 실패 : " + err);
        process.exit(1);
      }
    }
    
    connectDB().catch(err => {
      console.error("초기 DB 연결 실패 : " + err);
      process.exit(1);
    });
    
    // view engine setup
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'pug');
    
    app.use(logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', indexRouter);
    app.use('/users', usersRouter);
    
    // catch 404 and forward to error handler
    app.use(function(req, res, next) {
      next(createError(404));
    });
    
    // error handler
    app.use(function(err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};
    
      // render the error page
      res.status(err.status || 500);
      res.render('error');
    });
    
    module.exports = app;

     

    >> users.js

    var express = require('express');
    var router = express.Router();
    var bcrypt = require('bcrypt');
    const { ObjectId } = require('mongodb');
    var saltrounds = 10;
    
    var ResponseType = {
      INVALID_USERNAME: 0,
      INVALID_PASSWORD: 1,
      SUCCESS: 2
    }
    
    /* GET users listing. */
    router.get('/', function(req, res, next) {
      res.send('respond with a resource');
    });
    
    // 회원가입
    router.post('/signup', async function(req, res, next) {
      try {
        var username = req.body.username;
        var password = req.body.password;
        var nickname = req.body.nickname;
    
        // 입력값 검증
        if (!username || !password || !nickname) {
          return res.status(400).send("모든 필드를 입력해주세요");
        }
    
        // 사용자 중복 체크
        var database = req.app.get('database');
        var users = database.collection('users');
       
        const existingUser = await users.findOne({ username: username });
        if (existingUser) {
          return res.status(409).send("이미 존재하는 사용자입니다");
        }
    
        // 비밀번호 암호화
        var salt = bcrypt.genSaltSync(saltrounds);
        var hash = bcrypt.hashSync(password, salt);
        
        // DB에 저장
        await users.insertOne({
          username: username,
          password: hash, // 해시된 비밀번호 저장
          nickname: nickname
        });
        res.status(201).send("사용자가 성공적으로 생성되었습니다");
      } catch (err) {
        console.error("사용자 추가 중 오류 발생 :", err);
        res.status(500).send("서버 오류가 발생했습니다");
      }
    });
    
    // 로그인
    router.post("/signin", async function(req, res, next) {
      try {
        var username = req.body.username;
        var password = req.body.password;
        
        var database = req.app.get('database');
        var users = database.collection('users');
    
        // 입력값 검증
        if (!username || !password) {
          return res.status(400).send("모든 필드를 입력해주세요.");
        }
    
        const existingUser = await users.findOne({ username: username });
        if (existingUser) {
          var compareResult = bcrypt.compareSync(password, existingUser.password);
          if (compareResult) {
            // 세션에 사용자 정보(로그인 정보) 저장
            req.session.isAuthenticated = true;
            req.session.userId = existingUser._id.toString();
            req.session.username = existingUser.username;
            req.session.nickname = existingUser.nickname;
    
            res.json({ result: ResponseType.SUCCESS });
          } else {
            res.json({ result: ResponseType.INVALID_PASSWORD });
          }
        } else {
          res.json({ result: ResponseType.INVALID_USERNAME });
        }
      } catch (err) {
        console.error("로그인 중 오류 발생.", err);
        res.status(500).send("서버 오류가 발생했습니다");
      }
    });
    
    // 로그아웃
    router.post('/signout', function(req, res, next) {
      req.session.destroy((err) => {
        if (err) {
          console.log("로그아웃 중 오류 발생");
          return res.status(500).send("서버 오류가 발생했습니다.");
        }
        res.status(200).send("로그아웃 되었습니다.");
      });
    });
    
    // 점수 추가
    router.post('/addscore', async function(req, res, next) {
      try {
        // 로그인 여부 체크
        if (!req.session.isAuthenticated) {
          return res.status(400).send("로그인이 필요합니다.");
        }
    
        var userId = req.session.userId;
        var score = req.body.score;
    
        // 점수 유효성 검사
        if (!score || isNaN(score)) {
          return res.status(400).send("유효한 점수를 입력해주세요.");
        }
    
        var database = req.app.get('database');
        var users = database.collection('users');
    
        const result = await users.updateOne(
          { _id: new ObjectId(userId) },
          { 
              $set: {
              score: Number(score),
              updatedAt: new Date()
            }
          }
        );
    
        if (result.matchedCount === 0) {
          return res.status(400).send("사용자를 찾을 수 없습니다.");
        }
    
        res.status(200).json({ message: "점수가 성공적으로 업데이트 되었습니다."});
      } catch (err) {
        console.error("점수 추가 중 오류 발생 : ", err);
        res.status(500).send("서버 오류가 발생했습니다.");
      }
    });
    
    // 점수 조회
    router.get('/score', async function(req, res, next) {
      try {
        if (!req.session.isAuthenticated) {
          return res.status(403).send("로그인이 필요합니다.");
        }
    
        var userId = req.session.userId;
        var database = req.app.get('database');
        var users = database.collection('users');
    
        const user = await users.findOne({ _id: new ObjectId(userId) });
    
        if (!user) {
          return res.status(404).send("사용자를 찾을 수 없습니다.");
        }
    
        res.json({
          id: user._id.toString(),
          username: user.username,
          nickname: user.nickname,
          score: user.score || 0
        });
      } catch (err) {
        console.error("점수 조회 중 오류 발생 : ", err);
        res.status(500).send("서버 오류가 발생했습니다.");
      }
    });
    
    module.exports = router;