ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • NodeJS/채팅 만들기 (BackEnd)
    Language/NodeJS 2022. 9. 2. 10:41

    이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

    frontend는 별도 backend는 node js로 구성된 서비스

    실질적인 작업은 일주일도 하지 못하고 완성된 작품이라서 부족한게 많지만 다음에는 좀더 좋게 만들기 위해서 간단히 기록해 둔다.

    다음에는 타입스크립트 및 세션베이스로 고려해보자.

     

    package.json

    {
      "name": "test-nodejs",
      "version": "1.0.0",
      "description": "test nodejs",
      "main": "index.js",
      "author": "kand.deokjoon",
      "license": "ISC",
      "type": "module",
      "scripts": {
        "build": "babel . --ignore node_modules,build --out-dir build",
        "start:local": "MODE=local nodemon --watch src/ src/server.js"
      },
      "dependencies": {
        "chalk-template": "^0.2.0",
        "cors": "^2.8.5",
        "express": "^4.17.2",
        "http": "^0.0.1-security",
        "module": "^1.2.5",
        "moment": "^2.29.1",
        "sockjs": "^0.3.24",
        "url": "^0.11.0"
      },
      "devDependencies": {
        "@babel/cli": "^7.16.0",
        "@babel/core": "^7.16.5",
        "@babel/node": "^7.16.5",
        "@babel/preset-env": "^7.16.5",
        "nodemon": "^2.0.15"
      }
    }

     

    server.js

    import Server from './lib/server.js'
    
    global.g_channels = {}
    global.g_clients = {}
    
    const server = new Server()

     

    socket-events.js

    import chalk from 'chalk'
    
    import constants from './lib/constants.js'
    import log from './lib/log.js'
    import socketService from './services/socket-service.js'
    import chatService from './services/chat-service.js'
    
    const socketEvents = {
      /**
       * sockjs - connection event
       * 
       * @param {*} socket 
       */
      connection: (socket) => {
        log.socket(`${chalk.underline(socket.id)} : connected (${socket.headers['x-forwarded-for']})`)
        // const params = url.parse(socket.url, true).query
        // log.info(`seller_no : ${params.seller_no}`)
    
        /**
         * sockjs - connection data event
         */
        socket.on('data', (message) => {
          try {
            const params = JSON.parse(message)
    
            console.log('\r\n\r\n\r\n')
            log.info('######################################## DATA STATED ########################################')
            log.info(`${chalk.bold.magenta.dim('[DATA-TYPE]')} [${params.type}] - (${params.action})`)
    
            if (params.type === constants.SOCKET.TYPE) {
              switch (params.action) {
                case constants.SOCKET.ACTION.CONNECT:
                  socketService.connect(socket, params)
                  break
              }
            } else if (params.type === constants.EASYTALK.TYPE) {
              switch (params.action) {
                case constants.EASYTALK.ACTION.JOIN_CHANNEL:
                  if (chatService.isValidUserParams(socket.id, params) === false) {
                    return log.error('권한없음')
                  }
                  chatService.join(socket, params)
                  break
                case constants.EASYTALK.ACTION.SEND_MESSAGE:
                  if (chatService.isValidChannelParams(socket.id, params) === false) {
                    return log.error('권한없음')
                  }
                  chatService.send(socket.id, params)
                  break
                case constants.EASYTALK.ACTION.EXIT_CHANNEL:
                  chatService.exit(socket.id, params.is_forever)
                  break
                case constants.EASYTALK.ACTION.EMIT_CHANNEL_MESSAGE:
                  chatService.emitChannelMessage(params)
                  break
              }
            }
          } catch (err) {
            return log.error(err)
          }
        })
    
        /**
         * sockjs - connection close event
         */
        socket.on('close', () => {
          try {
            if (g_clients.hasOwnProperty(socket.id) === true) {
              log.socket(`${chalk.underline(socket.id)} : disconnected (${g_clients[socket.id].ip})`)
              socketService.close(socket.id)
            } else {
              log.socket(`${chalk.underline(socket.id)} : disconnected (${socket.headers['x-forwarded-for']})`)
            }
          } catch (err) {
            return log.error(err)
          }
        })
      },
    
    }
    
    export default socketEvents

     

    Config

    config/config.js

    import baseConfig from './base-config.js'
    
    import prdConfig from './prd-config.js'
    import qaConfig from './qa-config.js'
    import devConfig from './dev-config.js'
    import demoConfig from './demo-config.js'
    import localConfig from './local-config.js'
    
    let config = baseConfig;
    
    const SERVER_HOSTS = Object.freeze({
      dev: {
        'test-dev-001': {
          EZP_API_HOST: 'dev-001.test.co.kr'
        },
        ...
      },
      qa: {
        'test-qa-001': {
          EZP_API_HOST: 'qa-001.test.co.kr'
        },
        ...
      },
      ...
    })
    
    console.log('TEST process.env.MODE : ', process.env.MODE)
    console.log('TEST process.env.HOST_NAME : ', process.env.HOST_NAME)
    
    if (process.env.MODE == 'local') {
      config = Object.assign({}, config, localConfig);
    } else {
      if (process.env.MODE == 'prd') {
        config = Object.assign({}, config, prdConfig);
      } else if (process.env.MODE == 'qa') {
        config = Object.assign({}, config, qaConfig);
      } else if (process.env.MODE == 'dev') {
        config = Object.assign({}, config, devConfig);
      } else if (process.env.MODE == 'demo') {
        config = Object.assign({}, config, demoConfig);
      }
      
      // 서버별 URL 틀려서 API 주소 변경
      config.api.host = SERVER_HOSTS[process.env.MODE][process.env.HOST_NAME]['EZP_API_HOST']
      console.log('TEST config.api.host : ', config.api.host)
    }
    
    
    export default config

     

    config/base-config.js

    export default {
      mode: "common",
      log: true,
      port: 10002,
      url: "/",
      ssl: {
        use: false,
        key: "/path/to/your/ssl.key",
        cert: "/path/to/your/ssl.crt"
      }
    }

     

    config/local-config.js

    export default {
      mode: "local",
      api: {
        host: 'test.apitest.com',
        port: 80,
        serviceId: "test-node",
        signature: "sdas"
      }
    }

     

    Library

    lib/server.js

    import express from 'express'
    import sockjs from 'sockjs'
    import fs from 'fs'
    import chalk from 'chalk'
    import * as http from 'http'
    import https from 'https'
    import cors from 'cors'
    
    import config from '../config/config.js'
    import log from './log.js'
    import socketEvents from '../socket-events.js'
    import routers from '../routes/index.js'
    // import session from "express-session"
    
    export default class Server {
      constructor() {
        log.start(`mode ${chalk.bold.red.dim(config.mode.toUpperCase())}`)
    
        this.port = process.env.PORT || config.port
    
        this.app = express()
        this.server = null
        this.sockjs_server = null
    
        this.configuration()
      }
    
      /**
       * configuration
       */
      configuration() {
        this.setRouter()
        this.createServer()
        this.setServerEvent()
        this.createSocketServer()
    
        // this.sessionTest()
      }
    
      /**
       * routers setting
       */
      setRouter() {
        this.app.use('/', routers.index)
      }
    
      /**
       * create server
       */
      createServer() {
        // http or https setting
        if (config.ssl.use === false) {
          log.start('no ssl')
          this.server = http.createServer(this.app)
        } else {
          log.start('ssl')
          const opt = {
            key: fs.readFileSync(config.ssl.key),
            cert: fs.readFileSync(config.ssl.cert)
          }
          this.server = https.createServer(opt, this.app)
        }
      }
    
      /**
       * set server event
       */
      setServerEvent() {
        // this.server.listen(this.port)
        this.server.listen(this.port, '0.0.0.0', () => {
          // log.start(`server on port ${this.port}!`)
        })
    
        // server - listening event
        this.server.on('listening', (args) => {
          // log.start(`server listening ${this.port}`)
          const addr = this.server.address()
          const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
          log.start('Listening at ' + bind)
        })
    
        // server - error event
        this.server.on('error', (error) => {
          log.error('server error', error)
        })
      }
    
      /**
       * sockjs - create server
       */
      createSocketServer() {
        // sockjs - create server
        this.sockjs_server = sockjs.createServer()
        this.sockjs_server.installHandlers(this.server,
          {
            prefix: '/socket.io',
            log: function () { }
          }
        )
    
        // sockjs - connection event
        this.sockjs_server.on('connection', socketEvents.connection)
      }
    }

     

    lib/request.js

    import * as http from 'http'
    import https from 'https'
    import log from './log.js'
    
    class Request {
      constructor() {
    
      }
    
      setOptions(options = {}) {
        this.options = options
      }
     
      send(options) {
        try {
          // log.test('api url', this.options.path)
          log.test('this.options', this.options)
          log.test('this.options.host', this.options.host)
    
          // TODO. test 및 status code에 따른 분기처리
          let req = null
          if (this.options.port === 443) {
            req = https.request(this.options, res => {
              res.setEncoding("utf8")
              res.on('data', options.ondata)
            })
          } else {
            req = http.request(this.options, res => {
              res.setEncoding("utf8")
              res.on('data', options.ondata)
            })
          }
        
          req.on('error', e => {
            log.error(e)
          })
        
          req.write(JSON.stringify(options.data))
          req.end()
        } catch(err) {
          log.error(err)
        }
      }
    
      
    }
    
    export default Request

     

    lib/log.js

    import chalk from 'chalk'
    import moment from 'moment'
    
    moment.locale('ko')
    
    const log = {
        test: (title, message = '') => {
            if (message === '') {
                message = title
                log.print(chalk.rgb(255, 255, 0).bold.dim('[Test] '), message)
            } else {
                const type = chalk.rgb(255, 255, 0).bold.dim('[Test] ') + chalk.rgb(255, 255, 0).bold.dim(title)
                log.print(type, message)
            }
        },
    
        socket: message => log.print(chalk.bold.magenta('[Socket] '), message),
    
        start: message => log.print(chalk.bold.green.dim('[Node Start] '), message),
        stop: message => log.print(chalk.bold.red.dim('[Node Stop] '), message),
    
        error: message => log.print(chalk.bold.red.dim('[Error] '), message),
        info: message => log.print(chalk.bold.blue('[Info] '), message),
        message: message => log.print(chalk.bold.cyan.dim('[Message] '), message),
    
        print: (type, message) => console.log(`[${moment(new Date()).format('HH:mm:ss')}] ${type} : `, message)
    }
    
    export default log

     

    lib/constants.js

    const constants = {
      SOCKET: {
        TYPE: 'socket',
        ACTION: {
          CONNECT: 'socket_connect',
          REFRESH_EASY_TALK_TOP: 'refresh_easy_talk_top',
          RELOAD_PAGE: 'reload_page'
        },
        TARGET: {
          SELF: 'self',
          OTHER: 'other'
        }
      },
      EASYTALK: {
        TYPE: 'talk',
        ACTION: {
          JOIN_CHANNEL: 'join_channel',
          SEND_MESSAGE: 'send_message',
          EXIT_CHANNEL: 'exit_channel',
          EMIT_CHANNEL_MESSAGE: 'emit_channel_message',
    
          RECEIVE_MESSAGE: 'receive_message',
          REFRESH_CHANNEL: 'refresh_channel',
          RESET_CHANNEL: 'reset_channel'
        }    
      },
      COMMON: {
        USER_TYPE_BUYER: 'B', // 쇼핑몰
        USER_TYPE_SELLER: 'S',  // 도매처
        SEND_TYPE_MESSAGE: '10' // 전송 유형 (10: 문자,  20: 주문문의, 21: 주문 문의 (일반주문 상품), 22: 주문 문의 (바로주문 상품) , 30:  상품 문의 (일반주문)
      }
    }
    
    const deepFreeze = obj => {
      const propNames = Object.getOwnPropertyNames(obj)
    
      for (const name of propNames) {
        const value = obj[name];
    
        if (value && typeof value === "object") {
          deepFreeze(value);
        }
      }
    
      return Object.freeze(obj);
    }
    
    
    export default deepFreeze(constants)

     

    lib/util/socket-utils.js

    import moment from 'moment'
    
    import constants from '../constants.js'
    import log from '../log.js'
    
    moment.locale('ko')
    
    const socketUtils = {
      /**
       * send to clients
       * 
       * @param {*} to_clients 
       * @param {*} customParams 
       */
      sendToClients: (to_clients, customParams) => {
        for (const [key, to_client] of Object.entries(to_clients)) {
          to_client.socket.write(JSON.stringify({ ...customParams }));
        }
      },
    
      /**
       * send to join clients by socket id
       * 
       * @param {*} from_socket_id 
       * @param {*} customParams 
       */
      sendToJoinClientsBySocketId: (from_socket_id, customParams) => {
        const from_client = g_clients[from_socket_id]
        const to_sockets = g_channels[from_client.channel_id].sockets
    
        for (const [key, to_socket] of Object.entries(to_sockets)) {
          const to_client = g_clients[key]
          const target = from_client.user_type === to_client.user_type ? constants.SOCKET.TARGET.SELF : constants.SOCKET.TARGET.OTHER
    
          to_socket.write(JSON.stringify({ type: constants.EASYTALK.TYPE, target: target, ...customParams }));
        }
      },
    
      /**
       * get join clients by user type
       * 
       * @param {*} user_type 
       * @param {*} buyer_no 
       * @param {*} seller_no 
       * @returns 
       */
      getJoinClientsByUserType: (user_type, buyer_no, seller_no) => {
        return Object.values(g_clients).filter(client => {
          if (
            client.user_type === user_type && client.buyer_no === buyer_no && client.seller_no === seller_no
          ) {
            return true
          }
        })
      },
    
      /**
       * get all clients by socket id
       * 
       * @param {*} from_socket_id 
       * @returns 
       */
      getAllClientsBySocketId: from_socket_id => {
        const { buyer_no, seller_no } = g_clients[from_socket_id]
    
        return Object.values(g_clients).filter(client => {
          if (
            client.user_type === constants.COMMON.USER_TYPE_BUYER && client.buyer_no === buyer_no ||
            client.user_type === constants.COMMON.USER_TYPE_SELLER && client.seller_no === seller_no
          ) {
            return true
          }
        })
      },
    
      /**
       * get all clients by user no
       * 
       * @param {*} buyer_no 
       * @param {*} seller_no 
       * @returns 
       */
      getAllClientsByUserNo: (buyer_no, seller_no) => {
        return Object.values(g_clients).filter(client => {
          if (
            client.user_type === constants.COMMON.USER_TYPE_BUYER && client.buyer_no === buyer_no ||
            client.user_type === constants.COMMON.USER_TYPE_SELLER && client.seller_no === seller_no
          ) {
            return true
          }
        })
      },
    
      /**
       * get self clients by socket id
       * 
       * @param {*} from_socket_id 
       * @returns 
       */
      getSelfClientsBySocketId: from_socket_id => {
        const { user_type, buyer_no, seller_no } = g_clients[from_socket_id]
    
        return Object.values(g_clients).filter(client => {
          if (
            (user_type === constants.COMMON.USER_TYPE_BUYER && client.buyer_no === buyer_no) ||
            (user_type === constants.COMMON.USER_TYPE_SELLER && client.seller_no === seller_no)) {
            return true
          }
        })
      },
    
      /**
       * get other clients by socket id
       * 
       * @param {*} from_socket_id 
       * @returns 
       */
      getOtherClientsBySocketId: from_socket_id => {
        const { user_type, buyer_no, seller_no } = g_clients[from_socket_id]
    
        return Object.values(g_clients).filter(client => {
          if (
            (user_type === constants.COMMON.USER_TYPE_BUYER && client.user_type === constants.COMMON.USER_TYPE_SELLER && client.seller_no === seller_no) ||
            (user_type === constants.COMMON.USER_TYPE_SELLER && client.user_type === constants.COMMON.USER_TYPE_BUYER && client.buyer_no === buyer_no)) {
            return true
          }
        })
      },
    
      /**
       * is target join by socket id
       * 
       * @param {*} from_socket_id 
       * @returns 
       */
      isJoinTargetBySocketId: from_socket_id => {
        const { user_type, buyer_no, seller_no, channel_id } = g_clients[from_socket_id]
    
        for (const socket_id of Object.keys(g_channels[channel_id].sockets)) {
          const client = g_clients[socket_id]
    
          if (user_type === constants.COMMON.USER_TYPE_BUYER && client.user_type === constants.COMMON.USER_TYPE_SELLER && client.buyer_no === buyer_no && client.seller_no === seller_no) {
            return 'T'
          } else if (user_type === constants.COMMON.USER_TYPE_SELLER && client.user_type === constants.COMMON.USER_TYPE_BUYER && client.buyer_no === buyer_no && client.seller_no === seller_no) {
            return 'T'
          }
        }
    
        return 'F'
      },
    
      /**
       * is join channel
       * 
       * @param {*} user_type 
       * @param {*} buyer_no 
       * @param {*} seller_no 
       * @returns 
       */
      isJoinChannel: (user_type, buyer_no, seller_no) => {
        const channel_id = `${buyer_no}-${seller_no}`
    
        if (Object.keys(g_channels).includes(channel_id) === true) {
          for (const socket_id of Object.keys(g_channels[channel_id].sockets)) {
            const client = g_clients[socket_id]
    
            if (client.user_type === user_type && client.buyer_no === buyer_no && client.seller_no === seller_no) {
              return 'T'
            }
          }
        }
    
        return 'F'
      },
    
      /**
       * add channel and socket by channel id
       * 
       * @param {*} channel_id 
       * @param {*} socket 
       */
      addChannelAndSocketByChannelId: (channel_id, socket) => {
        if (Object.keys(g_channels).includes(channel_id) === true) {
          // 기존 채널
          let channelSockets = g_channels[channel_id].sockets
          if (Object.keys(channelSockets).includes(socket.id) === false) {
            channelSockets[socket.id] = socket
          }
        } else {
          // 신규 채널
          g_channels[channel_id] = { sockets: [] }
          g_channels[channel_id].sockets[socket.id] = socket
        }
      },
    
      /**
       * add client
       * 
       * @param {*} channel_id 
       * @param {*} user_type 
       * @param {*} buyer_no 
       * @param {*} seller_no 
       * @param {*} socket 
       */
      addClient: (channel_id, user_type, buyer_no, seller_no, socket) => {
        g_clients[socket.id] = {
          user_type: user_type,
          buyer_no: buyer_no,
          seller_no: seller_no,
          socket: socket,
          ip: socket.headers['x-forwarded-for']
        }
    
        if (channel_id !== null) {
          g_clients[socket.id].channel_id = channel_id
        }
      },
    
      /**
       * delete channel by socket id
       * 
       * @param {*} delete_socket_id 
       */
      deleteChannelBySocketId: delete_socket_id => {
        if (Object.keys(g_clients).includes(delete_socket_id) === true) {
          if (g_clients[delete_socket_id].hasOwnProperty('channel_id') === true) {
    
            const before_channel_id = g_clients[delete_socket_id].channel_id
            log.test('before_channel_id', before_channel_id)
    
            delete g_channels[before_channel_id].sockets[delete_socket_id]
            if (Object.keys(g_channels[before_channel_id].sockets).length === 0) {
              delete g_channels[before_channel_id]
            }
          }
        }
      },
    
      /**
       * delete client by socket id
       * 
       * @param {*} delete_socket_id 
       */
      deleteClientBySocketId: delete_socket_id => {
        delete g_clients[delete_socket_id]
      },
    
      /**
       * delete join client by socket id
       * 
       * @param {*} delete_socket_id 
       */
      deleteJoinClientBySocketId: delete_socket_id => {
        delete g_clients[delete_socket_id].channel_id
        if (g_clients[delete_socket_id].user_type === constants.COMMON.USER_TYPE_BUYER) {
          delete g_clients[delete_socket_id].seller_no
        } else if (g_clients[delete_socket_id].user_type === constants.COMMON.USER_TYPE_SELLER) {
          delete g_clients[delete_socket_id].buyer_no
        }
      }
    }
    
    export default socketUtils

     

    Services

    services/socket-service.js

    import moment from 'moment'
    
    import log from '../lib/log.js'
    import socketUtils from '../lib/util/socket-utils.js'
    
    moment.locale('ko')
    
    const socketService = {
      /**
       * connect
       * 
       * @param {*} socket 
       * @param {*} params 
       */
      connect: (socket, params) => {
        const { buyer_no, seller_no, user_type } = params
    
        socketUtils.addClient(null, user_type, buyer_no, seller_no, socket)
    
        log.test('channels', g_channels)
        log.test('clients', g_clients)
      },
      /**
       * close
       * 
       * @param {*} socket 
       */
      close: (from_socket_id) => {
        socketUtils.deleteChannelBySocketId(from_socket_id)
        socketUtils.deleteClientBySocketId(from_socket_id)    
    
        log.test('close from_socket_id', from_socket_id)
        log.test('close channels', g_channels)
        log.test('close clients', g_clients)
      }
    }
    export default socketService

     

    services/chat-service.js

    import moment from 'moment'
    
    import constants from '../lib/constants.js'
    import log from '../lib/log.js'
    import ezpApi from '../api/ezp-api.js'
    import socketUtils from '../lib/util/socket-utils.js'
    
    moment.locale('ko')
    
    const chatService = {
      /**
       * is valid user params
       * 
       * @param {*} from_socket_id 
       * @param {*} params 
       * @returns 
       */
      isValidUserParams: (from_socket_id, params) => {
        if (Object.keys(g_clients).includes(from_socket_id) === true) {
          const from_client = g_clients[from_socket_id]
    
          if (from_client.user_type === constants.COMMON.USER_TYPE_BUYER && from_client.user_type === params.user_type && from_client.buyer_no === params.buyer_no) {
            return true
          } else if (from_client.user_type === constants.COMMON.USER_TYPE_SELLER && from_client.user_type === params.user_type && from_client.seller_no === params.seller_no) {
            return true
          } else {
            return false
          }
        }
    
        return true
      },
      /**
       * is valid channel  params
       * @param {*} from_socket_id 
       * @param {*} params 
       * @returns 
       */
      isValidChannelParams: (from_socket_id, params) => {
        if (Object.keys(g_clients).includes(from_socket_id) === true) {
          const from_client = g_clients[from_socket_id]
    
          if (from_client.user_type === params.user_type && from_client.buyer_no === params.buyer_no && from_client.seller_no === params.seller_no) {
            return true
          } else {
            return false
          }
        }
    
        return false
      },
      /**
       * join
       * 
       * @param {*} socket
       * @param {*} params
       */
      join: (socket, params) => {
        const { user_type, buyer_no, seller_no } = params
        const channel_id = `${buyer_no}-${seller_no}`
    
        socketUtils.deleteChannelBySocketId(socket.id)
        socketUtils.addChannelAndSocketByChannelId(channel_id, socket)
        socketUtils.addClient(channel_id, user_type, buyer_no, seller_no, socket)
    
        ezpApi.join({
          user_type: user_type,
          buyer_no: buyer_no,
          seller_no: seller_no,
          success: res => {
            try {
              if (res.meta.code === 200) {
                log.test('SUCCESS res.code', res.meta.code)
    
                // TOP 새로고침
                const self_clients = socketUtils.getSelfClientsBySocketId(socket.id)
                /*
                socketUtils.sendToClients(self_clients, {
                  type: constants.SOCKET.TYPE,
                  action: constants.SOCKET.ACTION.REFRESH_EASY_TALK_TOP,
                  unread_count: Number(res.response.target_unread_count)
                })
                */
    
                const self_other_clients = Object.values(self_clients).filter(client => {
                  // 자신 제외
                  if (client.socket.id !== socket.id) {
                    return true
                  }
                })
    
                // 채널 새로고침
                socketUtils.sendToClients(self_other_clients, {
                  type: constants.EASYTALK.TYPE,
                  action: constants.EASYTALK.ACTION.REFRESH_CHANNEL
                })
              } else {
                log.test('ERROR res.code', res.meta.code)
              }
            } catch (err) {
              return log.error(err);
            }
          }
        })
    
        log.test('join channels', g_channels)
        log.test('join clients', g_clients)
      },
    
      /**
       * send
       * 
       * @param {*} socket 
       * @param {*} params 
       */
      send: (from_socket_id, params) => {
        const { buyer_no, seller_no, message } = params
        const send_ts = moment(new Date()).format('YYYY.MM.DD HH:mm:ss')
        const is_join_target = socketUtils.isJoinTargetBySocketId(from_socket_id)
    
        log.test('is_join_target', is_join_target)
    
        ezpApi.send({
          ...params, send_ts, is_join_target,
          success: res => {
            try {
              if (res.meta.code === 200) {
                log.test('SUCCESS res.code', res.meta.code)
                /*
                if (is_join_target === 'F') {
                  // TOP 새로고침
                  const other_clients = socketUtils.getOtherClientsBySocketId(from_socket_id)
                  socketUtils.sendToClients(other_clients, {
                    type: constants.SOCKET.TYPE,
                    action: constants.SOCKET.ACTION.REFRESH_EASY_TALK_TOP,
                    unread_count: Number(res.response.target_unread_count)
                  })
                }
                */
    
                // 채널 새로고침
                const all_clients = socketUtils.getAllClientsBySocketId(from_socket_id)
                log.test('all_clients', all_clients);
                socketUtils.sendToClients(all_clients, {
                  type: constants.EASYTALK.TYPE,
                  action: constants.EASYTALK.ACTION.REFRESH_CHANNEL,
                  buyer_no: buyer_no,
                  seller_no: seller_no
                })
              } else {
                log.test('ERROR res.code', res.meta.code)
              }
            } catch (err) {
              return log.error(err);
            }
          }
        })
    
        // 채널 접속자들 대상으로 메세지 전송
        socketUtils.sendToJoinClientsBySocketId(from_socket_id, {
          action: constants.EASYTALK.ACTION.RECEIVE_MESSAGE,
          send_type: constants.COMMON.SEND_TYPE_MESSAGE,
          message: message,
          send_ts: send_ts
        })
      },
    
      /**
       * exit
       * 
       * @param {*} socket 
       */
      exit: (from_socket_id, is_forever) => {
        const { user_type, buyer_no, seller_no } = g_clients[from_socket_id]
    
        if (is_forever === true) {
          // 나가기
          ezpApi.exit({
            user_type, buyer_no, seller_no,
            success: res => {
              try {
                log.test('API exit', res)
    
                // 채널 나가기 - 대상
                const self_clients = socketUtils.getSelfClientsBySocketId(from_socket_id)
    
                socketUtils.deleteChannelBySocketId(from_socket_id)
                socketUtils.deleteJoinClientBySocketId(from_socket_id)
    
                // 채널 나가기 - 리로드
                socketUtils.sendToClients(self_clients, {
                  type: constants.EASYTALK.TYPE,
                  action: constants.EASYTALK.ACTION.RESET_CHANNEL,
                  buyer_no: buyer_no,
                  seller_no: seller_no
                })
    
              } catch (err) {
                return log.error(err);
              }
            }
          })
        } else {
          // 뒤로가기
          socketUtils.deleteChannelBySocketId(from_socket_id)
          socketUtils.deleteJoinClientBySocketId(from_socket_id)
        }
      },
    
      /**
       * 이지톡 - 메세지 조회 및 전송
       * 
       * @param {*} params 
       */
      emitChannelMessage: (params) => {
        const { buyer_no, seller_no, easytalk_message_nos } = params
        const is_join_seller = socketUtils.isJoinChannel(constants.COMMON.USER_TYPE_SELLER, buyer_no, seller_no)
    
        ezpApi.getEasypickMessages({
          buyer_no, seller_no, easytalk_message_nos, is_join_seller,
          success: res => {
            try {
              if (res.meta.code === 200) {
                log.test('SUCCESS res.code', res.meta.code)
    
                const join_buyer_clients = socketUtils.getJoinClientsByUserType(constants.COMMON.USER_TYPE_BUYER, buyer_no, seller_no)
                const join_seller_clients = socketUtils.getJoinClientsByUserType(constants.COMMON.USER_TYPE_SELLER, buyer_no, seller_no)
    
                for (const row of res.response.message_list) {
                  let send_params = {
                    action: constants.EASYTALK.ACTION.RECEIVE_MESSAGE,
                    type: constants.EASYTALK.TYPE,
                    send_type: row.send_type,
                    message: row.send_message,
                    message_json: row.message_json,
                    send_ts: row.send_ts
                  }
    
                  // 쇼핑몰 전송
                  socketUtils.sendToClients(join_buyer_clients, {
                    target: constants.SOCKET.TARGET.SELF,
                    ...send_params
                  })
    
                  // 도매처 전송
                  socketUtils.sendToClients(join_seller_clients, {
                    target: constants.SOCKET.TARGET.OTHER,
                    ...send_params
                  })
                }
    
                // 채널 새로고침
                const all_clients = socketUtils.getAllClientsByUserNo(buyer_no, seller_no)
                socketUtils.sendToClients(all_clients, {
                  type: constants.EASYTALK.TYPE,
                  action: constants.EASYTALK.ACTION.REFRESH_CHANNEL
                })
              } else {
                log.test('ERROR res.code', res.meta.code)
              }
            } catch (err) {
              return log.error(err);
            }
          }
        })
      },
    
    }
    export default chatService

     

    Api

    api/test-api.js

    import crypto from 'crypto'
    
    import log from '../lib/log.js'
    import config from '../config/config.js'
    import Request from '../lib/request.js'
    import constants from '../lib/constants.js'
    
    const ezpApi = {
      /**
       * eazy pick api - headers
       * 
       * @returns 
       */
      getHeaders: () => {
        const timestamp = new Date().getTime()
        const signature = crypto.createHmac('sha256', config.api.signature).update(`0=${config.api.serviceId}&1=${timestamp}`).digest().toString('base64')
    
        return {
          "Content-Type": "application/json; charset=utf-8",
          "ezp-service-id": config.api.serviceId,
          "ezp-current-timestamp": timestamp,
          "ezp-signature": signature
        }
      },
    
      /**
       * send message
       * 
       * @param {*} params 
       * @param {*} send_dt 
       * @returns 
       */
      send: ({ user_type, buyer_no, seller_no, message, send_ts, is_join_target, success }) => {
    
        try {
          const request = new Request();
          request.setOptions({
            host: config.api.host,
            port: config.api.port,
            path: '/api/v2/easytalk/send',
            method: 'POST',
            headers: ezpApi.getHeaders()
          });     
    
          request.send({
            data: {
              send_type: constants.COMMON.SEND_TYPE_MESSAGE,
              send_user_type: user_type,
              buyer_no: buyer_no,
              seller_no: seller_no,
              send_message: message,
              send_ts: send_ts,
              is_join_target: is_join_target
            },
            ondata: (res) => {
              success(JSON.parse(res))
            }
          })
        } catch (err) {
          return log.error(err);
        }
      },
    
      /**
       * exit
       * 
       * @param {*} user_type 
       * @param {*} buyer_no 
       * @param {*} seller_no 
       * @returns 
       */
      exit: ({ user_type, buyer_no, seller_no, success }) => {
    
        try {
          const request = new Request();
          request.setOptions({
            host: config.api.host,
            port: config.api.port,
            path: '/api/v2/easytalk/exit',
            method: 'POST',
            headers: ezpApi.getHeaders()
          });
    
          request.send({
            data: {
              user_type: user_type,
              buyer_no: buyer_no,
              seller_no: seller_no,
            },
            ondata: (res) => {
              success(JSON.parse(res))
            }
          })
        } catch (err) {
          return log.error(err);
        }
      },
    
      /**
       * join
       * 
       * @param {*} param0 
       * @returns 
       */
      join: ({ user_type, buyer_no, seller_no, success }) => {
    
        try {
          const request = new Request();
          request.setOptions({
            host: config.api.host,
            port: config.api.port,
            path: '/api/v2/easytalk/join',
            method: 'POST',
            headers: ezpApi.getHeaders()
          });
    
          request.send({
            data: {
              user_type: user_type,
              buyer_no: buyer_no,
              seller_no: seller_no
            },
            ondata: (res) => {
              success(JSON.parse(res))
            }
          })
        } catch (err) {
          return log.error(err);
        }
      },
    
      /**
       * 이지톡 - 메세지 조회
       * 
       * @param {*} param0 
       * @returns 
       */
      getEasypickMessages: ({ buyer_no, seller_no, easytalk_message_nos, is_join_seller, success }) => {
    
        try {
          const request = new Request();
          request.setOptions({
            host: config.api.host,
            port: config.api.port,
            path: '/api/v2/easytalk/getEasypickMessages',
            method: 'POST',
            headers: ezpApi.getHeaders()
          });
    
          request.send({
            data: {
              buyer_no: buyer_no,
              seller_no: seller_no,
              is_join_seller: is_join_seller,
              easytalk_message_nos: easytalk_message_nos
            },
            ondata: (res) => {
              success(JSON.parse(res))
            }
          })
        } catch (err) {
          return log.error(err);
        }
      }
    }
    
    export default ezpApi

     

    Docker

    docker-build.sh

    #!/usr/bin/env bash
    docker build -t image-ezp-node:0.1 -f $PWD/docker/Dockerfile .

     

    docker-run.sh

    #!/usr/bin/env bash
    HOSTNAME=`hostname -s`
    
    run_script() {
        MODE='local'
    
        if [[ $HOSTNAME =~ ^test-dev ]]; then
            MODE='dev'
        elif [[ $HOSTNAME =~ ^test-qa ]]; then
            MODE='qa'
        elif [[ $HOSTNAME =~ ^test- || $HOSTNAME =~ ^ezp- ]]; then
            MODE='prd'
        elif [[ $HOSTNAME =~ ^test-demo ]]; then
            MODE='demo'
        fi
    
        if [[ $MODE == 'local' ]]; then        
            docker run --name server-ezp-node -d \
            -p 10002:10002 \
            -v /home/djkang/project-src/ezp-node/src:/home/ezp-node/src \
            --env MODE=$MODE \
            --env HOST_NAME=$HOSTNAME \
            image-ezp-node:0.1 sh /start-node.sh
        else
            docker run --name server-ezp-node -u 1000 -d \
            -p 10002:10002 \
            -v /home/mes-docker/ezp-node/src:/home/ezp-node/src \
            --read-only \
            --env MODE=$MODE \
            --env HOST_NAME=$HOSTNAME \
            --restart=always \
            image-ezp-node:0.1 sh /start-node.sh
        fi
    }
    
    docker stop server-ezp-node
    docker rm server-ezp-node
    run_script

     

    docker/Dockerfile

    FROM node:16-alpine
    
    WORKDIR /home/ezp-node
    
    COPY ./yarn* ./package.json ./
    COPY ./docker/start-node.sh /
    
    ENV TZ=Asia/Seoul
    
    # RUN yarn
    RUN yarn install &&\
        chmod +x /start-node.sh

     

    docker/start-node.sh

    #!/usr/bin/env bash
    
    if [ $MODE = 'local' ]; then
      getent hosts host.docker.internal | awk '{ print $1  "  test.aaaa.com" }' >> /etc/hosts
    
      # yarn --cwd /home/djkang/project-src/ezp-node start:dev
      yarn start:local
    else
      cd /home/ezp-node/
      node src/server.js
    fi

     

    이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

    댓글

Designed by Tistory.