From 415a4630275b33df8a8833597a38fa9e249797de Mon Sep 17 00:00:00 2001 From: xuecong <> Date: Tue, 9 Nov 2021 18:10:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=94=99=E8=AF=AF=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=EF=BC=8C=E6=8A=BD=E7=A6=BB=E4=B8=AD=E9=97=B4=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/app.ts | 123 ++++----------- server/src/middleware/header.ts | 28 ++++ server/src/middleware/ip.ts | 23 +++ server/src/middleware/model.ts | 31 ++++ server/src/middleware/watcher.ts | 51 +++++++ server/src/model/index.ts | 25 ++++ .../module/mp/controller/user/supplement.ts | 3 +- server/src/module/mp/index.ts | 8 +- server/src/module/notify/index.ts | 3 +- server/src/module/oa/index.ts | 3 +- server/src/module/pc/index.ts | 8 +- server/src/service/redis.ts | 75 +--------- server/src/service/validator.ts | 140 +++++++++--------- server/src/store/mysql-session.ts | 14 +- server/src/types/action.d.ts | 1 + server/src/types/koa.d.ts | 3 - server/src/wss/index.ts | 84 +++++++++++ 17 files changed, 375 insertions(+), 248 deletions(-) create mode 100644 server/src/middleware/header.ts create mode 100644 server/src/middleware/ip.ts create mode 100644 server/src/middleware/model.ts create mode 100644 server/src/middleware/watcher.ts create mode 100644 server/src/model/index.ts create mode 100644 server/src/wss/index.ts diff --git a/server/src/app.ts b/server/src/app.ts index 6d4d220..d10b980 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -19,9 +19,6 @@ import KoaBodyMiddleware from 'koa-body'; import KoaSessionMilddleware from 'koa-session'; import KoaLogMiddleware from 'koa-logger'; import MysqlSessionStore from '~/store/mysql-session'; -import Knex from 'knex'; -import WebSocket from 'ws'; -import * as redisService from '~/service/redis'; import http from 'http'; import cwlog from 'chowa-log'; import config from '~/config'; @@ -30,8 +27,11 @@ import MpModule from '~/module/mp'; import PcModule from '~/module/pc'; import NotifyModule from '~/module/notify'; import OaModule from '~/module/oa'; -import utils from '~/utils'; -import { SYSTEMT_NOT_INIT } from '~/constant/code'; +import wss from '~/wss'; +import * as redisService from '~/service/redis'; +import ModelMiddleware from '~/middleware/model'; +import IpMiddleware from '~/middleware/ip'; +import HeaderMiddleware from '~/middleware/header'; if (cluster.isMaster) { cwlog.success(`main process ${process.pid}`); @@ -43,16 +43,7 @@ if (cluster.isMaster) { const app = new Koa(); const router = new KoaRouter(); const server = http.createServer(app.callback()); - const wss = new WebSocket.Server({ server, path: '/cws' }); - const model = Knex({ - client: 'mysql', - connection: config.mysqlConfig, - pool: { - min: 0, - max: 200 - } - }); - console.log(config); + cwlog.setProject(`${config.name}-${process.pid}`); cwlog.displayDate(); @@ -65,87 +56,33 @@ if (cluster.isMaster) { NotifyModule(router); OaModule(router); - // for socket - redisService.subscribe(model, wss); - - app.use(KoaBodyMiddleware({ multipart: true })); - app.use( - KoaLogMiddleware({ - transporter: str => { - cwlog.log(`${str}`); - } - }) - ); - app.use( - KoaSessionMilddleware( - { - store: new MysqlSessionStore(model), - ...config.session - }, - app + app.use(KoaBodyMiddleware({ multipart: true })) + .use( + KoaLogMiddleware({ + transporter: str => { + cwlog.log(`${str}`); + } + }) ) - ); - app.use(async (ctx, next) => { - ctx.model = model; - ctx.request.ip = (ctx.request.header['x-real-ip'] as string) || ctx.request.ip; + .use( + KoaSessionMilddleware( + { + store: new MysqlSessionStore(), + ...config.session + }, + app + ) + ) + .use(ModelMiddleware()) + .use(IpMiddleware()) + .use(HeaderMiddleware()) + .use(router.routes()); - ctx.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); + // WebSocket + wss.init(server); - if (ctx.method == 'OPTIONS') { - ctx.body = ''; - ctx.status = 204; - } else { - const isInitAction = /^\/pc\/init\/\w+$/.test(ctx.request.path); - - if (!config.inited && !/^\/pc\/upload\/sign$/.test(ctx.request.path)) { - const total = utils.sql.countReader(await ctx.model.from('ejyy_property_company_admin').count()); - - if (total === 0) { - if (!isInitAction) { - return (ctx.body = { - code: SYSTEMT_NOT_INIT, - message: '系统未初始化' - }); - } - } else { - config.inited = true; - } - } else { - if (isInitAction) { - ctx.redirect('https://www.chowa.cn'); - } - } - - try { - await next(); - } catch (error) { - ctx.status = 500; - - if (config.debug) { - cwlog.error('===============错误捕捉开始================='); - console.log(error); - cwlog.error('===============错误捕捉结束================='); - } else { - utils.mail.send({ - subject: `错误捕获`, - content: [ - `访问地址:${ctx.request.path}`, - `进程号:${process.pid}`, - `body参数: ${JSON.stringify(ctx.request.body)}`, - `params参数: ${JSON.stringify(ctx.params)}`, - `进程号:${process.pid}`, - `错误原因:${error}` - ] - }); - } - } - } - - if (ctx.status === 404) { - ctx.redirect('https://www.chowa.cn'); - } - }); - app.use(router.routes()); + // for socket + redisService.subscribe(); const port = process.env.port ? parseInt(process.env.port, 10) : config.server.port; diff --git a/server/src/middleware/header.ts b/server/src/middleware/header.ts new file mode 100644 index 0000000..d0624f9 --- /dev/null +++ b/server/src/middleware/header.ts @@ -0,0 +1,28 @@ +/** + * +---------------------------------------------------------------------- + * | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主 + * +---------------------------------------------------------------------- + * | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权 + * +---------------------------------------------------------------------- + * | Author: jixuecong@chowa.cn + * +---------------------------------------------------------------------- + */ + +import { Middleware, DefaultState, DefaultContext } from 'koa'; + +function HeaderMiddleware(): Middleware { + return async (ctx: DefaultContext, next) => { + ctx.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); + + if (ctx.method == 'OPTIONS') { + ctx.body = ''; + return (ctx.status = 204); + } + + await next(); + }; +} + +export default HeaderMiddleware; diff --git a/server/src/middleware/ip.ts b/server/src/middleware/ip.ts new file mode 100644 index 0000000..c757755 --- /dev/null +++ b/server/src/middleware/ip.ts @@ -0,0 +1,23 @@ +/** + * +---------------------------------------------------------------------- + * | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主 + * +---------------------------------------------------------------------- + * | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权 + * +---------------------------------------------------------------------- + * | Author: jixuecong@chowa.cn + * +---------------------------------------------------------------------- + */ + +import { Middleware, DefaultState, DefaultContext } from 'koa'; + +function IpMiddleware(): Middleware { + return async (ctx, next) => { + ctx.request.ip = (ctx.request.header['x-real-ip'] as string) || ctx.request.ip; + + await next(); + }; +} + +export default IpMiddleware; diff --git a/server/src/middleware/model.ts b/server/src/middleware/model.ts new file mode 100644 index 0000000..11755fe --- /dev/null +++ b/server/src/middleware/model.ts @@ -0,0 +1,31 @@ +/** + * +---------------------------------------------------------------------- + * | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主 + * +---------------------------------------------------------------------- + * | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权 + * +---------------------------------------------------------------------- + * | Author: jixuecong@chowa.cn + * +---------------------------------------------------------------------- + */ + +import { Middleware, DefaultState, DefaultContext } from 'koa'; +import Knex from 'knex'; +import model from '~/model'; + +declare module 'koa' { + interface BaseContext { + model: Knex; + } +} + +function ModelMiddleware(): Middleware { + return async (ctx, next) => { + ctx.model = model; + + await next(); + }; +} + +export default ModelMiddleware; diff --git a/server/src/middleware/watcher.ts b/server/src/middleware/watcher.ts new file mode 100644 index 0000000..ce3554a --- /dev/null +++ b/server/src/middleware/watcher.ts @@ -0,0 +1,51 @@ +/** + * +---------------------------------------------------------------------- + * | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主 + * +---------------------------------------------------------------------- + * | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权 + * +---------------------------------------------------------------------- + * | Author: jixuecong@chowa.cn + * +---------------------------------------------------------------------- + */ + +import { Middleware, DefaultState, DefaultContext } from 'koa'; +import utils from '~/utils'; +import cwlog from 'chowa-log'; +import config from '~/config'; + +function WatcherMiddleware(): Middleware { + return async (ctx, next) => { + try { + await next(); + } catch (error) { + ctx.status = 500; + + if (config.debug) { + cwlog.error('===============错误捕捉开始================='); + console.log(error); + cwlog.error('===============错误捕捉结束================='); + } else { + utils.mail.send({ + to: config.smtp.to, + subject: `${config.name}异常捕获`, + content: [ + `访问地址:${ctx.request.path}`, + `进程号:${process.pid}`, + `body参数: ${JSON.stringify(ctx.request.body)}`, + `params参数: ${JSON.stringify(ctx.params)}`, + `进程号:${process.pid}`, + `错误原因:${error}` + ] + }); + } + } + + if (ctx.status === 404) { + ctx.redirect('https://www.chowa.cn'); + } + }; +} + +export default WatcherMiddleware; diff --git a/server/src/model/index.ts b/server/src/model/index.ts new file mode 100644 index 0000000..b7e96d3 --- /dev/null +++ b/server/src/model/index.ts @@ -0,0 +1,25 @@ +/** + * +---------------------------------------------------------------------- + * | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主 + * +---------------------------------------------------------------------- + * | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权 + * +---------------------------------------------------------------------- + * | Author: jixuecong@chowa.cn + * +---------------------------------------------------------------------- + */ + +import Knex from 'knex'; +import config from '~/config'; + +const model = Knex({ + client: 'mysql', + connection: config.mysqlConfig, + pool: { + min: 0, + max: 200 + } +}); + +export default model; diff --git a/server/src/module/mp/controller/user/supplement.ts b/server/src/module/mp/controller/user/supplement.ts index 7d2f3db..fd5bfdd 100644 --- a/server/src/module/mp/controller/user/supplement.ts +++ b/server/src/module/mp/controller/user/supplement.ts @@ -49,7 +49,8 @@ const MpUserSupplementAction = { { name: 'idcard', required: true, - validator: val => utils.idcard.verify(val) + validator: val => utils.idcard.verify(val), + message: '身份证号码验证失败' }, { name: 'avatar_url', diff --git a/server/src/module/mp/index.ts b/server/src/module/mp/index.ts index e23a376..7d42da4 100644 --- a/server/src/module/mp/index.ts +++ b/server/src/module/mp/index.ts @@ -70,17 +70,19 @@ function MpModule(appRouter: KoaRouter) { } } - if (validatorService(ctx, validator)) { + const vs = validatorService(ctx, validator); + + if (!vs.success) { return (ctx.body = { code: PARAMS_ERROR, - message: '参数错误' + message: vs.message }); } await response.apply(this, [ctx, next]); }); - if (process.env.NODE_ENV !== 'production') { + if (config.debug) { cwlog.info(`${name} mounted and request from ${path.posix.join('/mp', router.path)} by ${router.method}`); } } diff --git a/server/src/module/notify/index.ts b/server/src/module/notify/index.ts index 761f463..1281379 100644 --- a/server/src/module/notify/index.ts +++ b/server/src/module/notify/index.ts @@ -16,6 +16,7 @@ import { NotifyAction } from '~/types/action'; import KoaRouter from 'koa-router'; import * as NotifyModuleRouter from './router'; import cwlog from 'chowa-log'; +import config from '~/config'; function MpModule(appRouter: KoaRouter) { for (const name in NotifyModuleRouter) { @@ -25,7 +26,7 @@ function MpModule(appRouter: KoaRouter) { await response.apply(this, [ctx, next]); }); - if (process.env.NODE_ENV !== 'production') { + if (config.debug) { cwlog.info( `${name} mounted and request from ${path.posix.join('/notify', router.path)} by ${router.method}` ); diff --git a/server/src/module/oa/index.ts b/server/src/module/oa/index.ts index a923f9a..6fa96a2 100644 --- a/server/src/module/oa/index.ts +++ b/server/src/module/oa/index.ts @@ -18,6 +18,7 @@ import * as OaModuleRouter from './router'; import cwlog from 'chowa-log'; import * as wechatService from '~/service/wechat'; import menu from './menu'; +import config from '~/config'; async function OaModule(appRouter: KoaRouter) { for (const name in OaModuleRouter) { @@ -27,7 +28,7 @@ async function OaModule(appRouter: KoaRouter) { await response.apply(this, [ctx, next]); }); - if (process.env.NODE_ENV !== 'production') { + if (config.debug) { cwlog.info(`${name} mounted and request from ${path.posix.join('/oa', router.path)} by ${router.method}`); } } diff --git a/server/src/module/pc/index.ts b/server/src/module/pc/index.ts index 2ae824f..11c4cd3 100644 --- a/server/src/module/pc/index.ts +++ b/server/src/module/pc/index.ts @@ -115,17 +115,19 @@ function PcModule(appRouter: KoaRouter) { } } - if (validatorService(ctx, validator)) { + const vs = validatorService(ctx, validator); + + if (!vs.success) { return (ctx.body = { code: PARAMS_ERROR, - message: '参数错误' + message: vs.message }); } await response.apply(this, [ctx, next]); }); - if (process.env.NODE_ENV !== 'production') { + if (config.debug) { cwlog.info(`${name} mounted and request from ${path.posix.join('/pc', router.path)} by ${router.method}`); } } diff --git a/server/src/service/redis.ts b/server/src/service/redis.ts index 4177ed1..1d24930 100644 --- a/server/src/service/redis.ts +++ b/server/src/service/redis.ts @@ -11,40 +11,16 @@ */ import redis from 'redis'; -import Knex from 'knex'; -import WebSocket from 'ws'; -import http from 'http'; -import quertString from 'query-string'; -import { CwWebSocket } from '~/types/ws'; +import wss, { PcData } from '~/wss'; import config from '~/config'; -import { Role } from '~/constant/role_access'; -const pub = process.env.NODE_ENV === 'production' ? redis.createClient(config.redis) : null; -const sub = process.env.NODE_ENV === 'production' ? redis.createClient(config.redis) : null; - -let wss = null; +const pub = config.debug ? null : redis.createClient(config.redis); +const sub = config.debug ? null : redis.createClient(config.redis); export const WS_NOTICE_TO_PROPERTY_COMPANY = 'WS_NOTICE_TO_PROPERTY_COMPANY'; export const WS_NOTICE_TO_REMOTE_SERVER = 'WS_NOTICE_TO_REMOTE_SERVER'; -interface PcData { - id: number; - community_id: number; - type: Role; - urge: boolean; -} - -function sendToPc(data: PcData) { - if (!(wss instanceof WebSocket.Server)) { - return; - } - wss.clients.forEach((client: CwWebSocket) => { - if (client.readyState === WebSocket.OPEN && client.access.includes(data.type)) { - client.send(JSON.stringify(data)); - } - }); -} - +// todo interface RsData { remote_id: number; door_id: number; @@ -57,7 +33,7 @@ function sendToRs(data: RsData) { function dispatch(channel: string, data: Object) { switch (channel) { case WS_NOTICE_TO_PROPERTY_COMPANY: - return sendToPc(data as PcData); + return wss.sendToPc(data as PcData); case WS_NOTICE_TO_REMOTE_SERVER: return sendToRs(data as RsData); @@ -65,50 +41,15 @@ function dispatch(channel: string, data: Object) { } export function pubish(channel: string, data: Object) { - if (process.env.NODE_ENV === 'production') { + if (!config.debug) { pub.publish(channel, JSON.stringify(data)); } else { dispatch(channel, data); } } -export function subscribe(model: Knex, w: WebSocket.Server) { - wss = w; - - wss.on('connection', async (ws: CwWebSocket, request: http.IncomingMessage) => { - const { - query: { token } - } = quertString.parseUrl(request.url); - - if (!token) { - return ws.close(); - } - - const pcUserInfo = await model - .table('ejyy_property_company_auth') - .leftJoin( - 'ejyy_property_company_user', - 'ejyy_property_company_user.id', - 'ejyy_property_company_auth.property_company_user_id' - ) - .leftJoin( - 'ejyy_property_company_access', - 'ejyy_property_company_access.id', - 'ejyy_property_company_user.access_id' - ) - .where('ejyy_property_company_auth.token', token) - .select('ejyy_property_company_user.id', 'ejyy_property_company_access.content as access') - .first(); - - if (!pcUserInfo) { - return ws.close(); - } - - ws.user_id = pcUserInfo.id; - ws.access = pcUserInfo.access; - }); - - if (process.env.NODE_ENV === 'production') { +export async function subscribe() { + if (!config.debug) { sub.subscribe(WS_NOTICE_TO_PROPERTY_COMPANY); sub.subscribe(WS_NOTICE_TO_REMOTE_SERVER); diff --git a/server/src/service/validator.ts b/server/src/service/validator.ts index e130043..d476b80 100644 --- a/server/src/service/validator.ts +++ b/server/src/service/validator.ts @@ -11,80 +11,88 @@ */ import { Context } from 'koa'; -import { ValidatorDeclare } from '~/types/action'; -import config from '~/config'; -import cwlog from 'chowa-log'; +import { ValidatorDeclare, FieldVerifyDeclare } from '~/types/action'; -function validatorService(ctx: Context, validator: ValidatorDeclare): boolean { - if (!validator) { - return false; +interface ValidatorResult { + success: boolean; + message?: string; +} + +interface FieldVeirfy extends FieldVerifyDeclare { + value: any; +} + +function validatorService(ctx: Context, validatorDeclare: ValidatorDeclare): ValidatorResult { + if (!validatorDeclare) { + return { success: true }; } - return !['body', 'params', 'query', 'files'].every(refer => { - const origin = validator[refer]; + const fileds: FieldVeirfy[] = []; - if (!Array.isArray(origin)) { - return true; + ['body', 'params', 'query', 'files'].forEach((refer: 'body' | 'params' | 'query' | 'files') => { + if (!Array.isArray(validatorDeclare[refer])) { + return; } - return origin.every(({ name, required, length, min, max, regex, validator }) => { - let value = undefined; - - if (refer === 'params') { - value = ctx.params[name]; - } else { - value = ctx.request[refer][name]; - } - - if ( - required === true && - ((Array.isArray(value) && value.length === 0) || - (!Array.isArray(value) && (value == undefined || value === ''))) - ) { - if (config.debug) { - cwlog.warning(`${name} 字段必须`); - } - return false; - } - - if (length && value && value.length !== length) { - if (config.debug) { - cwlog.warning(`${name} 长度必须等于 ${length},当前值:${value}`); - } - return false; - } - - if (min && value && value.length < min) { - if (config.debug) { - cwlog.warning(`${name} 长度必须小于 ${min},当前值:${value}`); - } - return false; - } - - if (max && value && value.length > max) { - if (config.debug) { - cwlog.warning(`${name} 长度必须大于 ${max},当前值:${value}`); - } - return false; - } - - if (regex && value && !regex.test(value)) { - if (config.debug) { - cwlog.warning(`${name} 必须满足正则 ${regex},当前值:${value}`); - } - return false; - } - - if (validator && value && !validator(value)) { - if (config.debug) { - cwlog.warning(`${name} 自定义验证未通过,当前值:${value}`); - } - return false; - } - - return true; + validatorDeclare[refer].forEach(declare => { + fileds.push({ + value: refer === 'params' ? ctx.params[declare.name] : ctx.request[refer][declare.name], + ...declare + }); }); }); + + for (let i = 0; i < fileds.length; i++) { + const { name, required, length, min, max, regex, validator, value, message } = fileds[i]; + + if ( + required === true && + ((Array.isArray(value) && value.length === 0) || + (!Array.isArray(value) && (value == undefined || value === ''))) + ) { + return { + success: false, + message: message ? message : `参数错误,${name}字段是必填字段` + }; + } + + if (length && value && value.length !== length) { + return { + success: false, + message: message ? message : `参数错误,${name}字段长度必须等于 ${length}` + }; + } + + if (min && value && value.length < min) { + return { + success: false, + message: message ? message : `参数错误,${name}字段长度必须大于 ${min}` + }; + } + + if (max && value && value.length > max) { + return { + success: false, + message: message ? message : `参数错误,${name}字段长度必须小于 ${max}` + }; + } + + if (regex && value && !regex.test(value)) { + return { + success: false, + message: message ? message : `参数错误,${name}字段必须满足正则 ${regex}` + }; + } + + if (validator && value && !validator(value)) { + return { + success: false, + message: message ? message : `参数错误,${name}字段验证未通过` + }; + } + } + + return { success: true }; } export default validatorService; diff --git a/server/src/store/mysql-session.ts b/server/src/store/mysql-session.ts index 2a1b818..f069aee 100644 --- a/server/src/store/mysql-session.ts +++ b/server/src/store/mysql-session.ts @@ -10,7 +10,7 @@ * +---------------------------------------------------------------------- */ -import Knex from 'knex'; +import model from '~/model'; import { Session } from 'koa-session'; const FORTY_FIVE_MINUTES = 45 * 60 * 1000; @@ -34,14 +34,8 @@ function getExpiresOn(session: Session, ttl: number): number { } class MysqlSessionStore { - constructor(model: Knex) { - this.model = model; - } - - model = null; - async get(sid: string): Promise { - const row = await this.model + const row = await model .from('ejyy_session_store') .where('id', sid) .where('expire', '>', Date.now()) @@ -60,14 +54,14 @@ class MysqlSessionStore { let expire = getExpiresOn(session, ttl).valueOf(); let data = JSON.stringify(session); - await this.model.raw( + await model.raw( 'INSERT INTO ejyy_session_store (id, expire, data) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE expire=?, data =?', [sid, expire, data, expire, data] ); } async destroy(sid: string) { - await this.model + await model .from('ejyy_session_store') .where('id', sid) .delete(); diff --git a/server/src/types/action.d.ts b/server/src/types/action.d.ts index 0d35ad1..c3f0679 100644 --- a/server/src/types/action.d.ts +++ b/server/src/types/action.d.ts @@ -32,6 +32,7 @@ declare namespace Action { min?: number; max?: number; regex?: RegExp; + message?: string; validator?: (value: any) => boolean; } diff --git a/server/src/types/koa.d.ts b/server/src/types/koa.d.ts index 788207f..ab2c828 100644 --- a/server/src/types/koa.d.ts +++ b/server/src/types/koa.d.ts @@ -9,8 +9,6 @@ * | Author: jixuecong@chowa.cn * +---------------------------------------------------------------------- */ - -import Knex from 'knex'; import { MpUserInfo, PcUserInfo, OaUserInfo } from './user-info'; interface InterfaceBody { @@ -24,7 +22,6 @@ declare module 'koa' { mpUserInfo: MpUserInfo; pcUserInfo: PcUserInfo; OaUserInfo: OaUserInfo; - model: Knex; } interface ContextDelegatedResponse { diff --git a/server/src/wss/index.ts b/server/src/wss/index.ts new file mode 100644 index 0000000..70be7f4 --- /dev/null +++ b/server/src/wss/index.ts @@ -0,0 +1,84 @@ +/** + * +---------------------------------------------------------------------- + * | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主 + * +---------------------------------------------------------------------- + * | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权 + * +---------------------------------------------------------------------- + * | Author: jixuecong@chowa.cn + * +---------------------------------------------------------------------- + */ + +import WebSocket from 'ws'; +import http from 'http'; +import quertString from 'query-string'; +import model from '~/model'; +import { Role } from '~/constant/role_access'; + +export interface CwWebSocket extends WebSocket { + access?: Role[]; + user_id?: number; +} + +export interface PcData { + id: number; + community_id: number; + type: Role; + urge: boolean; +} + +class ws { + static ws: WebSocket.Server; + + static init(server: http.Server) { + this.ws = new WebSocket.Server({ server, path: '/cws' }); + + this.ws.on('connection', async (ws: CwWebSocket, request: http.IncomingMessage) => { + const { + query: { token } + } = quertString.parseUrl(request.url); + + if (!token) { + return ws.close(); + } + + const userInfo = await model + .table('ejyy_property_company_auth') + .leftJoin( + 'ejyy_property_company_user', + 'ejyy_property_company_user.id', + 'ejyy_property_company_auth.property_company_user_id' + ) + .leftJoin( + 'ejyy_property_company_access', + 'ejyy_property_company_access.id', + 'ejyy_property_company_user.access_id' + ) + .where('ejyy_property_company_auth.token', token) + .select('ejyy_property_company_user.id', 'ejyy_property_company_access.content') + .first(); + + if (!userInfo) { + return ws.close(); + } + + ws.user_id = userInfo.id; + ws.access = userInfo.content; + }); + } + + static sendToPc(data: PcData) { + if (!(this.ws instanceof WebSocket.Server)) { + return; + } + + this.ws.clients.forEach((client: CwWebSocket) => { + if (client.readyState === WebSocket.OPEN && client.access.includes(data.type)) { + client.send(JSON.stringify(data)); + } + }); + } +} + +export default ws;