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.