Language/NodeJS
NodeJS/채팅 만들기 (BackEnd)
건담아빠
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