From 53766364f66ad2f735f290064239f3ba80bfb009 Mon Sep 17 00:00:00 2001 From: bLue Date: Fri, 12 Apr 2024 15:42:07 +0800 Subject: [PATCH] feat(socket-io): add competition controller and docker compose file --- .github/workflows/socket-io.yml | 44 ++++++++++++++++++ src/sub-app/socket-io/Dockerfile | 17 +++++++ src/sub-app/socket-io/compose.template.yml | 45 ++++++++++++++++++ src/sub-app/socket-io/docker.template.env | 1 + src/sub-app/socket-io/package-lock.json | 40 ++++++++++------ src/sub-app/socket-io/package.json | 3 ++ src/sub-app/socket-io/src/app.ts | 2 + .../src/app/io/controller/competition.ts | 46 +++++++++++++++++++ .../socket-io/src/app/io/controller/judger.ts | 36 ++++++--------- .../socket-io/src/app/io/utils/auth.ts | 12 +++++ src/sub-app/socket-io/src/app/router.ts | 6 +++ .../src/config/config.prod.from-file.ts | 27 +++++++++++ 12 files changed, 243 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/socket-io.yml create mode 100644 src/sub-app/socket-io/Dockerfile create mode 100644 src/sub-app/socket-io/compose.template.yml create mode 100644 src/sub-app/socket-io/docker.template.env create mode 100644 src/sub-app/socket-io/src/app/io/controller/competition.ts create mode 100644 src/sub-app/socket-io/src/app/io/utils/auth.ts create mode 100644 src/sub-app/socket-io/src/config/config.prod.from-file.ts diff --git a/.github/workflows/socket-io.yml b/.github/workflows/socket-io.yml new file mode 100644 index 0000000..4c39b47 --- /dev/null +++ b/.github/workflows/socket-io.yml @@ -0,0 +1,44 @@ +name: Build Socket.IO Docker Image +on: + push: + branches: + - master +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + build-and-push: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./src/sub-app/socket-io + steps: + - name: Checkout + uses: actions/checkout@v3 + # - uses: actions/setup-node@v3 + # with: + # node-version: 16 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to TCR + uses: docker/login-action@v2 + with: + registry: ccr.ccs.tencentyun.com + username: ${{ secrets.TCR_USERNAME }} + password: ${{ secrets.TCR_TOKEN }} + - name: Build and Push Image + uses: docker/build-push-action@v3 + with: + context: ./src/sub-app/socket-io + file: ./src/sub-app/socket-io/Dockerfile + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/onlinejudge3-socket-io:1 + ${{ secrets.DOCKERHUB_USERNAME }}/onlinejudge3-socket-io:latest + ccr.ccs.tencentyun.com/${{ secrets.TCR_NSP }}/onlinejudge3-socket-io:1 + ccr.ccs.tencentyun.com/${{ secrets.TCR_NSP }}/onlinejudge3-socket-io:latest diff --git a/src/sub-app/socket-io/Dockerfile b/src/sub-app/socket-io/Dockerfile new file mode 100644 index 0000000..fd01a9c --- /dev/null +++ b/src/sub-app/socket-io/Dockerfile @@ -0,0 +1,17 @@ +FROM sdutacm/nodebase:16.15.0 + +WORKDIR /app + +COPY package.json ./ +COPY package-lock.json ./ +COPY .node-version ./ +COPY tsconfig.json ./ +COPY tsconfig.prod.json ./ +COPY src ./src +RUN mv src/config/config.prod.from-file.ts src/config/config.prod.ts +RUN npm i -g nodeinstall +RUN nodeinstall --install-alinode 7.6.0 +RUN npm ci +RUN npm run build + +CMD npm run start:foreground diff --git a/src/sub-app/socket-io/compose.template.yml b/src/sub-app/socket-io/compose.template.yml new file mode 100644 index 0000000..0dd7996 --- /dev/null +++ b/src/sub-app/socket-io/compose.template.yml @@ -0,0 +1,45 @@ +version: '3' +services: + io-1: + image: sdutacm/onlinejudge3-socket-io + network_mode: host + env_file: + - ./docker.env + environment: + - PORT=7011 + volumes: + - ./logs/onlinejudge3-be-socket-io-1:/root/logs/onlinejudge3-be-socket-io + - ./config.js:/app/dist/config/config.dynamic.js:ro + + io-2: + image: sdutacm/onlinejudge3-socket-io + network_mode: host + env_file: + - ./docker.env + environment: + - PORT=7012 + volumes: + - ./logs/onlinejudge3-be-socket-io-2:/root/logs/onlinejudge3-be-socket-io + - ./config.js:/app/dist/config/config.dynamic.js:ro + + io-3: + image: sdutacm/onlinejudge3-socket-io + network_mode: host + env_file: + - ./docker.env + environment: + - PORT=7013 + volumes: + - ./logs/onlinejudge3-be-socket-io-3:/root/logs/onlinejudge3-be-socket-io + - ./config.js:/app/dist/config/config.dynamic.js:ro + + io-4: + image: sdutacm/onlinejudge3-socket-io + network_mode: host + env_file: + - ./docker.env + environment: + - PORT=7014 + volumes: + - ./logs/onlinejudge3-be-socket-io-4:/root/logs/onlinejudge3-be-socket-io + - ./config.js:/app/dist/config/config.dynamic.js:ro diff --git a/src/sub-app/socket-io/docker.template.env b/src/sub-app/socket-io/docker.template.env new file mode 100644 index 0000000..c5f9a92 --- /dev/null +++ b/src/sub-app/socket-io/docker.template.env @@ -0,0 +1 @@ +TZ=Asia/Shanghai diff --git a/src/sub-app/socket-io/package-lock.json b/src/sub-app/socket-io/package-lock.json index 3a303d8..7d56522 100644 --- a/src/sub-app/socket-io/package-lock.json +++ b/src/sub-app/socket-io/package-lock.json @@ -13,10 +13,12 @@ "egg-scripts": "^2.10.0", "egg-socket.io": "^4.1.6", "ip": "^1.1.5", + "lodash": "^4.17.15", "midway": "^1.0.0" }, "devDependencies": { "@types/ip": "^1.1.0", + "@types/lodash": "^4.14.151", "@types/mocha": "^5.2.7", "@types/node": "^10.17.18", "@typescript-eslint/eslint-plugin": "^2.24.0", @@ -1388,9 +1390,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "version": "4.14.151", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.151.tgz", + "integrity": "sha512-Zst90IcBX5wnwSu7CAS0vvJkTjTELY4ssKbHiTnGcJgi170uiS8yQDdc3v6S77bRqYQIN1App5a1Pc2lceE5/g==", "dev": true }, "node_modules/@types/marked": { @@ -6674,6 +6676,12 @@ "node": ">=8" } }, + "node_modules/inquirer/node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "node_modules/inquirer/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", @@ -8345,10 +8353,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "version": "4.17.15", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "node_modules/lodash._reinterpolate": { "version": "3.0.0", @@ -16449,9 +16456,9 @@ } }, "@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "version": "4.14.151", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.151.tgz", + "integrity": "sha512-Zst90IcBX5wnwSu7CAS0vvJkTjTELY4ssKbHiTnGcJgi170uiS8yQDdc3v6S77bRqYQIN1App5a1Pc2lceE5/g==", "dev": true }, "@types/marked": { @@ -20739,6 +20746,12 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", @@ -22110,10 +22123,9 @@ } }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "version": "4.17.15", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash._reinterpolate": { "version": "3.0.0", diff --git a/src/sub-app/socket-io/package.json b/src/sub-app/socket-io/package.json index 14eceb2..cd1d5a0 100644 --- a/src/sub-app/socket-io/package.json +++ b/src/sub-app/socket-io/package.json @@ -15,6 +15,7 @@ "devtest": "cross-env TS_NODE_PROJECT=test/tsconfig.json NODE_ENV=test DEBUG=-* midway-bin test --ts --sticky", "lint": "eslint --fix {src,test}/**/*.ts", "start": "egg-scripts start --daemon --title=egg-server-onlinejudge3-be-socket-io --framework=midway --ts --sticky --workers=1", + "start:foreground": "egg-scripts start --title=egg-server-onlinejudge3-be-socket-io --framework=midway --ts --sticky --workers=1", "stop": "egg-scripts stop --title=egg-server-onlinejudge3-be-socket-io", "test": "cross-env DEBUG=-* npm run lint && npm run devtest" }, @@ -24,10 +25,12 @@ "egg-scripts": "^2.10.0", "egg-socket.io": "^4.1.6", "ip": "^1.1.5", + "lodash": "^4.17.15", "midway": "^1.0.0" }, "devDependencies": { "@types/ip": "^1.1.0", + "@types/lodash": "^4.14.151", "@types/mocha": "^5.2.7", "@types/node": "^10.17.18", "@typescript-eslint/eslint-plugin": "^2.24.0", diff --git a/src/sub-app/socket-io/src/app.ts b/src/sub-app/socket-io/src/app.ts index 06e27e9..27f7677 100644 --- a/src/sub-app/socket-io/src/app.ts +++ b/src/sub-app/socket-io/src/app.ts @@ -10,6 +10,8 @@ module.exports = (app: Application) => { `πŸš€ Sub App socket.io is launching... (NODE_ENV: ${process.env.NODE_ENV}, EGG_SERVER_ENV: ${process.env.EGG_SERVER_ENV})`, ); + app.config.io.path && app.io.path(app.config.io.path); + console.log('βœ… Sub App socket.io launched'); }); diff --git a/src/sub-app/socket-io/src/app/io/controller/competition.ts b/src/sub-app/socket-io/src/app/io/controller/competition.ts new file mode 100644 index 0000000..3b6a98a --- /dev/null +++ b/src/sub-app/socket-io/src/app/io/controller/competition.ts @@ -0,0 +1,46 @@ +import { Application } from 'midway'; +import { checkEmitAuth } from '../utils/auth'; + +module.exports = (app: Application) => { + class CompetitionController extends app.Controller { + async subscribe() { + const [competitionId, userId] = this.ctx.args; + if (!(competitionId > 0 && userId > 0)) { + return; + } + console.log(`[competition] client subscribe: ${competitionId} ${userId}`); + const room = `competition:${competitionId}_${userId}`; + this.ctx.socket.join(room); + this.ctx.socket.emit('res', 'subscribed'); + } + + async innerHttpAcceptPushData() { + if (!checkEmitAuth(this.ctx, this.config)) { + this.ctx.status = 403; + this.ctx.body = { + success: false, + code: -1, + msg: '403', + }; + return; + } + + const { competitionId, userId, data } = this.ctx.request.body as { + competitionId: number; + userId: number; + data: any; + }; + this.ctx.logger.info('[competition] innerHttpAcceptPushData:', competitionId, userId, data); + this.ctx.app.io + .of('/competition') + .to(`competition:${competitionId}_${userId}`) + .emit('d', data); + this.ctx.body = { + success: true, + data: {}, + }; + } + } + + return CompetitionController; +}; diff --git a/src/sub-app/socket-io/src/app/io/controller/judger.ts b/src/sub-app/socket-io/src/app/io/controller/judger.ts index f78363c..fad6499 100644 --- a/src/sub-app/socket-io/src/app/io/controller/judger.ts +++ b/src/sub-app/socket-io/src/app/io/controller/judger.ts @@ -1,5 +1,6 @@ import { Application } from 'midway'; import { isPrivate as isPrivateIp } from 'ip'; +import { checkEmitAuth } from '../utils/auth'; /** * ηΌ–η θ―„ζ΅‹ηŠΆζ€γ€‚ @@ -28,34 +29,22 @@ export function encodeJudgeStatusBuffer( } module.exports = (app: Application) => { - class Controller extends app.Controller { + class JudgerController extends app.Controller { async subscribe() { - console.log('rooms:', this.ctx.socket.rooms); const solutionIds: number[] = this.ctx.args[0]; - console.log('subscribe:', solutionIds); + if (!Array.isArray(solutionIds) || solutionIds.length === 0) { + return; + } + console.log(`[judger] client subscribe: ${solutionIds}`); solutionIds.forEach((solutionId) => { - const room = `s:${solutionId}`; + const room = `solution:${solutionId}`; this.ctx.socket.join(room); this.ctx.socket.emit('res', `subscribed ${solutionId}`); }); } async innerHttpAcceptPushStatus() { - const check = () => { - if ( - this.config.emitAuthKey && - this.config.emitAuthKey === this.ctx.request.headers['x-emit-auth'] - ) { - return true; - } - if (isPrivateIp(this.ctx.ip)) { - return true; - } - return false; - }; - - const statusFormArray = this.ctx.request.body as any[]; - if (!check()) { + if (!checkEmitAuth(this.ctx, this.config)) { this.ctx.status = 403; this.ctx.body = { success: false, @@ -64,6 +53,8 @@ module.exports = (app: Application) => { }; return; } + + const statusFormArray = this.ctx.request.body as any[]; if (!Array.isArray(statusFormArray)) { this.ctx.status = 422; this.ctx.body = { @@ -74,15 +65,16 @@ module.exports = (app: Application) => { return; } const solutionId = statusFormArray[0]; - this.ctx.logger.info('innerHttpAcceptPushStatus', statusFormArray); + this.ctx.logger.info('[judger] innerHttpAcceptPushStatus:', statusFormArray); // @ts-ignore const status = encodeJudgeStatusBuffer(...statusFormArray); - this.ctx.app.io.of('/judger').to(`s:${solutionId}`).emit('s', status); + this.ctx.app.io.of('/judger').to(`solution:${solutionId}`).emit('s', status); this.ctx.body = { success: true, data: {}, }; } } - return Controller; + + return JudgerController; }; diff --git a/src/sub-app/socket-io/src/app/io/utils/auth.ts b/src/sub-app/socket-io/src/app/io/utils/auth.ts new file mode 100644 index 0000000..77a2d19 --- /dev/null +++ b/src/sub-app/socket-io/src/app/io/utils/auth.ts @@ -0,0 +1,12 @@ +import { Context } from 'midway'; +import { IAppConfig } from '@/config/config.interface'; + +export function checkEmitAuth(ctx: Context, config: IAppConfig) { + if (config.emitAuthKey && config.emitAuthKey === ctx.request.headers['x-emit-auth']) { + return true; + } + // if (isPrivateIp(ctx.ip)) { + // return true; + // } + return false; +} diff --git a/src/sub-app/socket-io/src/app/router.ts b/src/sub-app/socket-io/src/app/router.ts index d5e88ac..707535b 100644 --- a/src/sub-app/socket-io/src/app/router.ts +++ b/src/sub-app/socket-io/src/app/router.ts @@ -8,4 +8,10 @@ module.exports = (app: Application) => { '/socketBridge/pushJudgeStatus', app.io.controller.judger.innerHttpAcceptPushStatus, ); + + app.io.of('/competition').route('subscribe', app.io.controller.competition.subscribe); + app.router.post( + '/socketBridge/pushCompetitionData', + app.io.controller.competition.innerHttpAcceptPushData, + ); }; diff --git a/src/sub-app/socket-io/src/config/config.prod.from-file.ts b/src/sub-app/socket-io/src/config/config.prod.from-file.ts new file mode 100644 index 0000000..98f5e43 --- /dev/null +++ b/src/sub-app/socket-io/src/config/config.prod.from-file.ts @@ -0,0 +1,27 @@ +import { EggAppInfo } from 'midway'; +import { formatLoggerHelper } from './config.default'; +import { IAppConfig } from './config.interface'; +// @ts-ignore +import configDynamic from './config.dynamic'; +import _ from 'lodash'; + +export default (appInfo: EggAppInfo) => { + let config = {} as IAppConfig; + + config.proxy = true; + config.maxIpsCount = 1; + + config.logger = { + // @ts-ignore + formatter(meta: any) { + return formatLoggerHelper(meta, `[${meta.hostname}:${meta.pid}]`); + }, + contextFormatter(meta: any) { + return formatLoggerHelper(meta, `[${meta.hostname}:${meta.pid}] ${meta.paddingMessage}`); + }, + }; + + config = _.merge(config, configDynamic); + + return config; +};