core: 优化钩子逻辑

This commit is contained in:
undefined committed 2020-09-06 11:31:49 +08:00
1 parent 03dc8903e2
commit f2f0212947
27 files changed
+205 -124

No files matched your search

+1
View File
@@ -29,6 +29,7 @@ module.exports = {
'max-len': ['warn', 150], 'max-len': ['warn', 150],
'no-bitwise': 'off', 'no-bitwise': 'off',
'no-console': 'off', 'no-console': 'off',
'no-continue': 'off',
'no-dupe-class-members': 'off', 'no-dupe-class-members': 'off',
'no-extend-native': 'off', 'no-extend-native': 'off',
'no-inner-declarations': 'off', 'no-inner-declarations': 'off',
+2 -1
View File
@@ -4,4 +4,5 @@ SETTINGS_PRIVACY.allowRegisteredUsers
model.setting.ui.name model.setting.ui.name
pdoc.secretConfig? pdoc.secretConfig?
message.markAsRead message.markAsRead
基于document的model难以lint,建议拆分 基于document的model难以lint,建议拆分
检查多余的索引
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hydrooj", "name": "hydrooj",
"version": "2.13.0", "version": "2.13.1",
"bin": "bin/hydrooj.js", "bin": "bin/hydrooj.js",
"main": "dist/loader.js", "main": "dist/loader.js",
"typings": "dist/loader.d.ts", "typings": "dist/loader.d.ts",
+3 -7
View File
@@ -5,6 +5,7 @@ import {
lib, service, model, lib, service, model,
builtinLib, builtinModel, builtinLib, builtinModel,
} from './common'; } from './common';
import * as bus from '../service/bus';
const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const ARR = /=>.*$/mg; const ARR = /=>.*$/mg;
@@ -51,13 +52,8 @@ export async function load() {
require('../utils'); require('../utils');
require('../error'); require('../error');
require('../options'); require('../options');
const bus = require('../service/bus');
await new Promise((resolve) => { await new Promise((resolve) => {
const h = () => { bus.once('database/connect', resolve);
bus.unsubscribe(['system_database_connected'], h);
resolve();
};
bus.subscribe(['system_database_connected'], h);
require('../service/db'); require('../service/db');
}); });
for (const i of builtinLib) require(`../lib/${i}`); for (const i of builtinLib) require(`../lib/${i}`);
@@ -66,6 +62,6 @@ export async function load() {
await service(pending, fail); await service(pending, fail);
for (const i of builtinModel) require(`../model/${i}`); for (const i of builtinModel) require(`../model/${i}`);
await model(pending, fail); await model(pending, fail);
for (const postInit of global.Hydro.postInit) await postInit(); await bus.parallel('app/started');
await cli(); await cli();
} }
+6 -12
View File
@@ -7,6 +7,7 @@ import pslist from 'ps-list';
import { argv } from 'yargs'; import { argv } from 'yargs';
import { builtinModel } from './common'; import { builtinModel } from './common';
import { Entry } from '../loader'; import { Entry } from '../loader';
import * as bus from '../service/bus';
const tmpdir = path.resolve(os.tmpdir(), 'hydro'); const tmpdir = path.resolve(os.tmpdir(), 'hydro');
const lockfile = path.resolve(tmpdir, 'lock.json'); const lockfile = path.resolve(tmpdir, 'lock.json');
@@ -31,9 +32,6 @@ export async function load(call: Entry) {
ppid: process.ppid, ppid: process.ppid,
}; };
await fs.writeFile(lockfile, JSON.stringify(context)); await fs.writeFile(lockfile, JSON.stringify(context));
global.onDestory.push(() => {
fs.removeSync(lockfile);
});
require('../lib/i18n'); require('../lib/i18n');
require('../utils'); require('../utils');
require('../error'); require('../error');
@@ -45,14 +43,15 @@ export async function load(call: Entry) {
process.exit(1); process.exit(1);
}); });
} }
const bus = require('../service/bus'); bus.once('app/exit', () => {
fs.removeSync(lockfile);
});
await new Promise((resolve) => { await new Promise((resolve) => {
const h = () => { const h = () => {
console.log('Database connected'); console.log('Database connected');
bus.unsubscribe(['system_database_connected'], h);
resolve(); resolve();
}; };
bus.subscribe(['system_database_connected'], h); bus.once('database/connect', h);
require('../service/db'); require('../service/db');
}); });
require('../service/monitor'); require('../service/monitor');
@@ -63,11 +62,6 @@ export async function load(call: Entry) {
const ins = require('../script/upgrade0_1'); const ins = require('../script/upgrade0_1');
await ins.run({ username: 'Root', password: 'rootroot' }); await ins.run({ username: 'Root', password: 'rootroot' });
} }
for (const postInit of global.Hydro.postInit) { bus.serial('app/started');
await postInit().catch((e) => {
console.error('Error running postInit:', postInit.toString());
console.error(e);
});
}
return await modelSystem.get('server.worker'); return await modelSystem.get('server.worker');
} }
+3 -4
View File
@@ -4,6 +4,7 @@ import {
locale, template, lib, service, model, handler, script, setting, uistatic, locale, template, lib, service, model, handler, script, setting, uistatic,
builtinLib, builtinScript, builtinHandler, builtinModel, builtinLib, builtinScript, builtinHandler, builtinModel,
} from './common'; } from './common';
import * as bus from '../service/bus';
export async function load() { export async function load() {
const pending = global.addons; const pending = global.addons;
@@ -18,14 +19,12 @@ export async function load() {
template(pending, fail), template(pending, fail),
uistatic(pending, fail), uistatic(pending, fail),
]); ]);
const bus = require('../service/bus');
await new Promise((resolve) => { await new Promise((resolve) => {
const h = () => { const h = () => {
console.log('Database connected'); console.log('Database connected');
bus.unsubscribe(['system_database_connected'], h);
resolve(); resolve();
}; };
bus.subscribe(['system_database_connected'], h); bus.once('database/connect', h);
require('../service/db'); require('../service/db');
}); });
for (const i of builtinLib) require(`../lib/${i}`); for (const i of builtinLib) require(`../lib/${i}`);
@@ -48,7 +47,7 @@ export async function load() {
await notfound.apply(); await notfound.apply();
for (const i of builtinScript) require(`../script/${i}`); for (const i of builtinScript) require(`../script/${i}`);
await script(pending, fail, active); await script(pending, fail, active);
for (const postInit of global.Hydro.postInit) await postInit(); await bus.serial('app/started');
await server.start(); await server.start();
setInterval(() => { setInterval(() => {
process.send({ event: 'stat', count: global.Hydro.stat.reqCount }); process.send({ event: 'stat', count: global.Hydro.stat.reqCount });
+1 -1
View File
@@ -310,7 +310,7 @@ class ContestProblemSubmitHandler extends ContestProblemHandler {
record.get(domainId, rid), record.get(domainId, rid),
contest.updateStatus(domainId, this.tdoc.docId, this.user._id, rid, this.pdoc.docId), contest.updateStatus(domainId, this.tdoc.docId, this.user._id, rid, this.pdoc.docId),
]); ]);
bus.publish('record_change', rdoc); bus.boardcast('record/change', rdoc);
if (!this.canShowRecord(this.tdoc)) { if (!this.canShowRecord(this.tdoc)) {
this.response.body = { tid }; this.response.body = { tid };
this.response.redirect = this.url('contest_detail', { tid }); this.response.redirect = this.url('contest_detail', { tid });
+7 -5
View File
@@ -25,6 +25,7 @@ import { PERM, PRIV, CONSTANT } from '../model/builtin';
import { import {
isContent, isPassword, isEmail, isTitle, isContent, isPassword, isEmail, isTitle,
} from '../lib/validator'; } from '../lib/validator';
import { Mdoc } from '../interface';
const { geoip, useragent } = global.Hydro.lib; const { geoip, useragent } = global.Hydro.lib;
@@ -358,18 +359,19 @@ class HomeMessagesHandler extends Handler {
} }
class HomeMessagesConnectionHandler extends ConnectionHandler { class HomeMessagesConnectionHandler extends ConnectionHandler {
id: string; dispose: bus.Disposable;
async prepare() { async prepare() {
bus.subscribe([`user_message-${this.user._id}`], this.onMessageReceived.bind(this)); this.dispose = bus.on('user/message', this.onMessageReceived.bind(this));
} }
async onMessageReceived(e: any) { async onMessageReceived(uid: number, e: any) {
this.send(e.value); if (uid !== this.user._id) return;
this.send(e);
} }
async cleanup() { async cleanup() {
bus.unsubscribe([`user_message-${this.user._id}`], this.id); if (this.dispose) this.dispose();
} }
} }
+1 -1
View File
@@ -189,7 +189,7 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
contest.updateStatus(domainId, tid, this.user._id, contest.updateStatus(domainId, tid, this.user._id,
rid, pid, false, 0, document.TYPE_HOMEWORK), rid, pid, false, 0, document.TYPE_HOMEWORK),
]); ]);
bus.publish('record_change', rdoc); bus.boardcast('record/change', rdoc);
this.response.body = { tid, rid }; this.response.body = { tid, rid };
if (this.canShowRecord(this.tdoc)) this.response.redirect = this.url('record_detail', { rid }); if (this.canShowRecord(this.tdoc)) this.response.redirect = this.url('record_detail', { rid });
else this.response.redirect = this.url('homework_detail', { tid }); else this.response.redirect = this.url('homework_detail', { tid });
+2 -2
View File
@@ -62,7 +62,7 @@ export async function next(body: JudgeResultBody) {
if (body.memory) $set.memory = body.memory; if (body.memory) $set.memory = body.memory;
if (body.progress) $set.progress = body.progress; if (body.progress) $set.progress = body.progress;
rdoc = await record.update(body.domainId, body.rid, $set, $push); rdoc = await record.update(body.domainId, body.rid, $set, $push);
bus.publish('record_change', { rdoc, $set, $push }); bus.boardcast('record/change', rdoc, $set, $push);
} }
export async function end(body: JudgeResultBody) { export async function end(body: JudgeResultBody) {
@@ -87,7 +87,7 @@ export async function end(body: JudgeResultBody) {
$set.judger = body.judger; $set.judger = body.judger;
rdoc = await record.update(body.domainId, body.rid, $set, $push, $unset); rdoc = await record.update(body.domainId, body.rid, $set, $push, $unset);
await _postJudge(rdoc); await _postJudge(rdoc);
bus.publish('record_change', { rdoc }); // trigger a full update bus.boardcast('record/change', rdoc); // trigger a full update
} }
class JudgeHandler extends Handler { class JudgeHandler extends Handler {
+9 -12
View File
@@ -1,6 +1,6 @@
import { isSafeInteger, flatten } from 'lodash'; import { isSafeInteger, flatten } from 'lodash';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { ObjectID } from 'mongodb'; import { FilterQuery, ObjectID } from 'mongodb';
import { import {
NoProblemError, ProblemDataNotFoundError, BadRequestError, NoProblemError, ProblemDataNotFoundError, BadRequestError,
SolutionNotFoundError, ProblemNotFoundError, ValidationError, SolutionNotFoundError, ProblemNotFoundError, ValidationError,
@@ -61,16 +61,14 @@ class ProblemMainHandler extends ProblemHandler {
@param('q', Types.String, true) @param('q', Types.String, true)
async get(domainId: string, page = 1, q = '') { async get(domainId: string, page = 1, q = '') {
this.response.template = 'problem_main.html'; this.response.template = 'problem_main.html';
const query: any = {}; const query: FilterQuery<Pdoc> = {};
let psdict = {}; let psdict = {};
const path: PathComponent[] = [ const path: PathComponent[] = [
['Hydro', 'homepage'], ['Hydro', 'homepage'],
['problem_main', null], ['problem_main', null],
]; ];
if (q) { if (q) {
q = q.toLowerCase(); query.$text = { $search: q };
const $regex = new RegExp(`\\A\\Q${q}\\E`, 'gmi');
query.title = { $regex };
path.push([q, null, null, true]); path.push([q, null, null, true]);
} }
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) query.hidden = false; if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) query.hidden = false;
@@ -252,7 +250,7 @@ class ProblemSubmitHandler extends ProblemDetailHandler {
record.get(domainId, rid), record.get(domainId, rid),
domain.incUserInDomain(domainId, this.user._id, 'nSubmit'), domain.incUserInDomain(domainId, this.user._id, 'nSubmit'),
]); ]);
bus.publish('record_change', { rdoc }); bus.boardcast('record/change', rdoc);
this.response.body = { rid }; this.response.body = { rid };
this.response.redirect = this.url('record_detail', { rid }); this.response.redirect = this.url('record_detail', { rid });
} }
@@ -275,7 +273,7 @@ class ProblemPretestHandler extends ProblemDetailHandler {
}, },
); );
const rdoc = await record.get(domainId, rid); const rdoc = await record.get(domainId, rid);
bus.publish('record_change', { rdoc }); bus.boardcast('record/change', rdoc);
this.response.body = { rid }; this.response.body = { rid };
} }
} }
@@ -285,7 +283,7 @@ class ProblemPretestConnectionHandler extends ConnectionHandler {
domainId: string; domainId: string;
id: string; dispose: bus.Disposable;
@param('pid', Types.String) @param('pid', Types.String)
async prepare(domainId: string, pid: string) { async prepare(domainId: string, pid: string) {
@@ -294,11 +292,10 @@ class ProblemPretestConnectionHandler extends ConnectionHandler {
if (!pdoc) throw new ProblemNotFoundError(domainId, pid); if (!pdoc) throw new ProblemNotFoundError(domainId, pid);
this.pid = pdoc.docId.toString(); this.pid = pdoc.docId.toString();
this.domainId = domainId; this.domainId = domainId;
this.id = bus.subscribe(['record_change'], this.onRecordChange.bind(this)); this.dispose = bus.on('record/change', this.onRecordChange.bind(this));
} }
async onRecordChange(data) { async onRecordChange(rdoc: Rdoc) {
const rdoc: Rdoc = data.value.rdoc;
if ( if (
rdoc.uid !== this.user._id rdoc.uid !== this.user._id
|| rdoc.pid.toString() !== this.pid || rdoc.pid.toString() !== this.pid
@@ -316,7 +313,7 @@ class ProblemPretestConnectionHandler extends ConnectionHandler {
} }
async cleanup() { async cleanup() {
bus.unsubscribe(['record_change'], this.id); if (this.dispose) this.dispose();
} }
} }
+12 -11
View File
@@ -102,6 +102,8 @@ class RecordRejudgeHandler extends Handler {
const RecordConnectionHandler = contest.ContestHandlerMixin(ConnectionHandler); const RecordConnectionHandler = contest.ContestHandlerMixin(ConnectionHandler);
class RecordMainConnectionHandler extends RecordConnectionHandler { class RecordMainConnectionHandler extends RecordConnectionHandler {
dispose: bus.Disposable;
@param('tid', Types.ObjectID, true) @param('tid', Types.ObjectID, true)
async prepare(domainId: string, tid: ObjectID) { async prepare(domainId: string, tid: ObjectID) {
this.domainId = domainId; this.domainId = domainId;
@@ -113,7 +115,7 @@ class RecordMainConnectionHandler extends RecordConnectionHandler {
return; return;
} }
} }
this.id = bus.subscribe(['record_change'], this.onRecordChange.bind(this)); this.dispose = bus.on('record/change', this.onRecordChange.bind(this));
} }
async message(msg) { async message(msg) {
@@ -123,17 +125,16 @@ class RecordMainConnectionHandler extends RecordConnectionHandler {
this.domainId, { _id: { $in: rids } }, this.domainId, { _id: { $in: rids } },
).toArray(); ).toArray();
for (const rdoc of rdocs) { for (const rdoc of rdocs) {
this.onRecordChange({ value: { rdoc } }); this.onRecordChange(rdoc);
} }
} }
} }
async cleanup() { async cleanup() {
bus.unsubscribe(['record_change'], this.id); if (this.dispose) this.dispose();
} }
async onRecordChange(data) { async onRecordChange(rdoc: Rdoc) {
const rdoc: Rdoc = data.value.rdoc;
if (rdoc.contest && rdoc.contest.tid.toString() !== this.tid) return; if (rdoc.contest && rdoc.contest.tid.toString() !== this.tid) return;
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let [udoc, pdoc] = await Promise.all([ let [udoc, pdoc] = await Promise.all([
@@ -146,6 +147,8 @@ class RecordMainConnectionHandler extends RecordConnectionHandler {
} }
class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(ConnectionHandler) { class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(ConnectionHandler) {
dispose: bus.Disposable;
@param('rid', Types.ObjectID) @param('rid', Types.ObjectID)
async prepare(domainId: string, rid: ObjectID) { async prepare(domainId: string, rid: ObjectID) {
const rdoc = await record.get(domainId, rid); const rdoc = await record.get(domainId, rid);
@@ -157,13 +160,11 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti
} }
} }
this.rid = rid.toString(); this.rid = rid.toString();
this.id = bus.subscribe(['record_change'], this.onRecordChange.bind(this)); this.dispose = bus.on('record/change', this.onRecordChange.bind(this));
this.onRecordChange({ value: { rdoc } }); this.onRecordChange(rdoc);
} }
async onRecordChange(data) { async onRecordChange(rdoc: Rdoc, $set?: any, $push?: any) {
const rdoc: Rdoc = data.value.rdoc;
const { $set, $push } = data.value;
if (rdoc._id.toString() !== this.rid) return; if (rdoc._id.toString() !== this.rid) return;
if ($set) this.send({ $set, $push }); if ($set) this.send({ $set, $push });
else { else {
@@ -175,7 +176,7 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti
} }
async cleanup() { async cleanup() {
bus.unsubscribe(['record_change'], this.id); if (this.dispose) this.dispose();
} }
} }
-2
View File
@@ -455,9 +455,7 @@ declare global {
ui: UI, ui: UI,
error: typeof import('./error'), error: typeof import('./error'),
locales: Dict<Dict<string>>, locales: Dict<Dict<string>>,
postInit: Array<() => Promise<any>>,
}, },
onDestory: Array<() => void | Promise<void>>,
addons: string[], addons: string[],
} }
} }
+5 -4
View File
@@ -36,9 +36,7 @@ if (!global.Hydro) {
// @ts-ignore // @ts-ignore
error: {}, error: {},
locales: {}, locales: {},
postInit: [],
}; };
global.onDestory = [];
global.addons = []; global.addons = [];
} }
@@ -47,9 +45,12 @@ if (argv.debug) {
console.log(argv); console.log(argv);
} }
// eslint-disable-next-line import/first
import * as bus from './service/bus';
async function terminate() { async function terminate() {
try { try {
for (const task of global.onDestory) await task(); await bus.parallel('app/exit');
} catch (e) { } catch (e) {
process.exit(1); process.exit(1);
} }
@@ -127,7 +128,7 @@ async function messageHandler(worker: cluster.Worker, msg: any) {
cluster.workers[i].send(msg); cluster.workers[i].send(msg);
} }
} else { } else {
global.Hydro.service.bus.publish(msg.eventName, msg.payload, false); global.Hydro.service.bus.emit(msg.eventName, ...msg.payload);
} }
} else if (msg.event === 'stat') { } else if (msg.event === 'stat') {
global.Hydro.stat.reqCount += msg.count; global.Hydro.stat.reqCount += msg.count;
+2 -1
View File
@@ -1,5 +1,6 @@
import { Collection } from 'mongodb'; import { Collection } from 'mongodb';
import * as db from '../service/db'; import * as db from '../service/db';
import * as bus from '../service/bus';
import { Bdoc } from '../interface'; import { Bdoc } from '../interface';
const coll: Collection<Bdoc> = db.collection('blacklist'); const coll: Collection<Bdoc> = db.collection('blacklist');
@@ -36,5 +37,5 @@ function ensureIndexes() {
return coll.createIndex('expireAt', { expireAfterSeconds: 0 }); return coll.createIndex('expireAt', { expireAfterSeconds: 0 });
} }
global.Hydro.postInit.push(ensureIndexes); bus.once('app/started', ensureIndexes);
global.Hydro.model.blacklist = { add, get, del }; global.Hydro.model.blacklist = { add, get, del };
+9 -3
View File
@@ -1,11 +1,12 @@
import assert from 'assert'; import assert from 'assert';
import { ObjectID } from 'mongodb'; import { ObjectID } from 'mongodb';
import * as db from '../service/db'; import * as db from '../service/db';
import * as bus from '../service/bus';
type DocID = ObjectID | string | number; type DocID = ObjectID | string | number;
const coll = db.collection('document'); export const coll = db.collection('document');
const collStatus = db.collection('document.status'); export const collStatus = db.collection('document.status');
export const TYPE_PROBLEM = 10; export const TYPE_PROBLEM = 10;
export const TYPE_PROBLEM_SOLUTION = 11; export const TYPE_PROBLEM_SOLUTION = 11;
@@ -51,6 +52,7 @@ export async function add(
doc.parentType = parentType; doc.parentType = parentType;
doc.parentId = parentId; doc.parentId = parentId;
} }
await bus.serial('document/add', doc);
const res = await coll.insertOne(doc); const res = await coll.insertOne(doc);
return docId || res.insertedId; return docId || res.insertedId;
} }
@@ -337,6 +339,7 @@ async function ensureIndexes() {
await ic({ domainId: 1, docType: 1, docId: 1 }, u); await ic({ domainId: 1, docType: 1, docId: 1 }, u);
await ic({ domainId: 1, docType: 1, owner: 1, docId: -1 }); await ic({ domainId: 1, docType: 1, owner: 1, docId: -1 });
// For problem // For problem
await ic({ domainId: 1, docType: 1, title: 'text' }, s);
await ic({ domainId: 1, docType: 1, category: 1, docId: 1 }, s); await ic({ domainId: 1, docType: 1, category: 1, docId: 1 }, s);
await ic({ domainId: 1, docType: 1, hidden: 1, category: 1, docId: 1 }, s); await ic({ domainId: 1, docType: 1, hidden: 1, category: 1, docId: 1 }, s);
await ic({ domainId: 1, docType: 1, tag: 1, docId: 1 }, s); await ic({ domainId: 1, docType: 1, tag: 1, docId: 1 }, s);
@@ -364,8 +367,11 @@ async function ensureIndexes() {
await is({ domainId: 1, docType: 1, uid: 1, enroll: 1, docId: 1 }, s); await is({ domainId: 1, docType: 1, uid: 1, enroll: 1, docId: 1 }, s);
} }
global.Hydro.postInit.push(ensureIndexes); bus.once('app/started', ensureIndexes);
global.Hydro.model.document = { global.Hydro.model.document = {
coll,
collStatus,
add, add,
addToSet, addToSet,
cappedIncStatus, cappedIncStatus,
+3 -2
View File
@@ -2,10 +2,11 @@ import { ObjectID, Collection } from 'mongodb';
import * as fs from 'fs'; import * as fs from 'fs';
import { Dictionary } from 'lodash'; import { Dictionary } from 'lodash';
import { ForbiddenError, NotFoundError } from '../error'; import { ForbiddenError, NotFoundError } from '../error';
import { Ufdoc } from '../interface';
import * as db from '../service/db'; import * as db from '../service/db';
import * as bus from '../service/bus';
import gridfs from '../service/gridfs'; import gridfs from '../service/gridfs';
import hash from '../lib/hash.hydro'; import hash from '../lib/hash.hydro';
import { Ufdoc } from '../interface';
const coll: Collection<Ufdoc> = db.collection('file'); const coll: Collection<Ufdoc> = db.collection('file');
const collFile = db.collection('fs.files'); const collFile = db.collection('fs.files');
@@ -121,7 +122,7 @@ function ensureIndexes() {
]); ]);
} }
global.Hydro.postInit.push(ensureIndexes); bus.once('app/started', ensureIndexes);
global.Hydro.model.file = { global.Hydro.model.file = {
add, get, del, getMeta, getWithSecret, getMulti, getMetaDict, url, add, get, del, getMeta, getWithSecret, getMulti, getMetaDict, url,
}; };
+2 -2
View File
@@ -22,7 +22,7 @@ export async function send(
if (from !== to) { if (from !== to) {
// ENHANCE domainId? // ENHANCE domainId?
const udoc = await user.getById('system', to); const udoc = await user.getById('system', to);
bus.publish(`user_message-${to}`, { mdoc, udoc }); bus.boardcast('user/message', to, mdoc, udoc);
} }
return mdoc; return mdoc;
} }
@@ -69,7 +69,7 @@ function ensureIndexes() {
]); ]);
} }
global.Hydro.postInit.push(ensureIndexes); bus.once('app/started', ensureIndexes);
global.Hydro.model.message = { global.Hydro.model.message = {
FLAG_UNREAD, FLAG_UNREAD,
FLAG_ALERT, FLAG_ALERT,
+2 -1
View File
@@ -1,4 +1,5 @@
import { OpcountExceededError } from '../error'; import { OpcountExceededError } from '../error';
import * as bus from '../service/bus';
import * as db from '../service/db'; import * as db from '../service/db';
const coll = db.collection('opcount'); const coll = db.collection('opcount');
@@ -23,5 +24,5 @@ function ensureIndexes() {
return coll.createIndex('expireAt', { expireAfterSeconds: 0 }); return coll.createIndex('expireAt', { expireAfterSeconds: 0 });
} }
global.Hydro.postInit.push(ensureIndexes); bus.once('app/started', ensureIndexes);
global.Hydro.model.opcount = { inc }; global.Hydro.model.opcount = { inc };
+9 -10
View File
@@ -3,6 +3,7 @@ import moment from 'moment-timezone';
import { Dictionary } from 'lodash'; import { Dictionary } from 'lodash';
import * as builtin from './builtin'; import * as builtin from './builtin';
import { Setting as _Setting } from '../interface'; import { Setting as _Setting } from '../interface';
import * as bus from '../service/bus';
type SettingDict = Dictionary<_Setting>; type SettingDict = Dictionary<_Setting>;
@@ -139,18 +140,16 @@ SystemSetting(
Setting('setting_storage', 'user', null, 1, 'number', 'User Counter', null, FLAG_DISABLED | FLAG_HIDDEN), Setting('setting_storage', 'user', null, 1, 'number', 'User Counter', null, FLAG_DISABLED | FLAG_HIDDEN),
); );
global.Hydro.postInit.push( bus.once('app/started', async () => {
async () => { for (const setting of SYSTEM_SETTINGS) {
for (const setting of SYSTEM_SETTINGS) { if (setting.value) {
if (setting.value) { const current = await global.Hydro.model.system.get(setting.key);
const current = await global.Hydro.model.system.get(setting.key); if (current === null || current === '') {
if (current === null || current === '') { await global.Hydro.model.system.set(setting.key, setting.value);
await global.Hydro.model.system.set(setting.key, setting.value);
}
} }
} }
}, }
); });
global.Hydro.model.setting = { global.Hydro.model.setting = {
langRange, langRange,
+2 -1
View File
@@ -1,6 +1,7 @@
import { Collection } from 'mongodb'; import { Collection } from 'mongodb';
import { TokenDoc } from '../interface'; import { TokenDoc } from '../interface';
import * as db from '../service/db'; import * as db from '../service/db';
import * as bus from '../service/bus';
export const coll: Collection<TokenDoc> = db.collection('token'); export const coll: Collection<TokenDoc> = db.collection('token');
@@ -87,7 +88,7 @@ export function delByUid(uid: number) {
return coll.deleteMany({ uid }); return coll.deleteMany({ uid });
} }
global.Hydro.postInit.push(ensureIndexes); bus.once('app/started', ensureIndexes);
global.Hydro.model.token = { global.Hydro.model.token = {
coll, coll,
+3 -2
View File
@@ -5,9 +5,10 @@ import * as setting from './setting';
import * as domain from './domain'; import * as domain from './domain';
import { BUILTIN_USERS, PRIV } from './builtin'; import { BUILTIN_USERS, PRIV } from './builtin';
import { UserNotFoundError, UserAlreadyExistError, LoginError } from '../error'; import { UserNotFoundError, UserAlreadyExistError, LoginError } from '../error';
import { User as _User, Udoc, Udict } from '../interface';
import pwhash from '../lib/hash.hydro'; import pwhash from '../lib/hash.hydro';
import * as db from '../service/db'; import * as db from '../service/db';
import { User as _User, Udoc, Udict } from '../interface'; import * as bus from '../service/bus';
const coll: Collection<Udoc> = db.collection('user'); const coll: Collection<Udoc> = db.collection('user');
@@ -248,7 +249,7 @@ function ensureIndexes() {
]); ]);
} }
global.Hydro.postInit.push(ensureIndexes); bus.once('app/started', ensureIndexes);
global.Hydro.model.user = { global.Hydro.model.user = {
User, User,
create, create,
+105 -24
View File
@@ -1,41 +1,122 @@
/* eslint-disable no-await-in-loop */
import cluster from 'cluster'; import cluster from 'cluster';
import { Db } from 'mongodb';
import { Mdoc, Rdoc, User } from '../interface';
const bus = {}; const _hooks: Record<keyof any, Array<(...args: any[]) => any>> = {};
const _disposables = [];
export function subscribe(events: string[], handler: any) { function isBailed(value: any) {
const id = String.random(16); return value !== null && value !== false && value !== undefined;
for (const event of events) {
if (!bus[event]) bus[event] = {};
bus[event][id] = handler;
}
return id;
} }
export function unsubscribe(events: string[], id: string) { export type Disposable = () => void
for (const event of events) {
if (!bus[event]) bus[event] = {}; interface EventMap {
delete bus[event][id]; 'app/started': () => void
} 'app/exit': () => Promise<void> | void
'dispose': () => void
'database/connect': (db: Db) => void
'user/message': (uid: number, mdoc: Mdoc, udoc: User) => void
'document/add': (doc: any) => string | void
'record/change': (rdoc: Rdoc, $set?: any, $push?: any) => void
} }
export function publish(event: string, payload: any, isMaster = true) { function getHooks<K extends keyof EventMap>(name: K) {
const hooks = _hooks[name] || (_hooks[name] = []);
if (hooks.length >= 128) {
console.warn(
'max listener count (128) for event "%s" exceeded, which may be caused by a memory leak',
name,
);
}
return hooks;
}
export function removeListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
const index = (_hooks[name] || []).findIndex((callback) => callback === listener);
if (index >= 0) {
_hooks[name].splice(index, 1);
return true;
}
return false;
}
export function addListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
getHooks(name).push(listener);
const dispose = () => removeListener(name, listener);
_disposables.push(name === 'dispose' ? listener as Disposable : dispose);
return dispose;
}
export function prependListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
getHooks(name).unshift(listener);
const dispose = () => removeListener(name, listener);
_disposables.push(name === 'dispose' ? listener as Disposable : dispose);
return dispose;
}
export function once<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
// @ts-ignore
const dispose = addListener(name, (...args: Parameters<EventMap[K]>) => {
dispose();
// @ts-ignore
return listener.apply(this, args);
});
return dispose;
}
export function on<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
return addListener(name, listener);
}
export function off<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
return removeListener(name, listener);
}
export async function parallel<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): Promise<void> {
const tasks: Promise<any>[] = [];
for (const callback of _hooks[name] || []) {
tasks.push(callback.apply(this, args));
}
await Promise.all(tasks);
}
export function emit<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>) {
return parallel(name, ...args);
}
export async function serial<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): Promise<ReturnType<EventMap[K]>> {
for (const callback of _hooks[name] || []) {
const result = await callback.apply(this, args);
if (isBailed(result)) return result;
}
return null;
}
export function bail<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): ReturnType<EventMap[K]> {
for (const callback of _hooks[name] || []) {
const result = callback.apply(this, args);
if (isBailed(result)) return result;
}
return null;
}
export function boardcast<K extends keyof EventMap>(event: K, ...payload: Parameters<EventMap[K]>) {
// Process forked by pm2 would also have process.send // Process forked by pm2 would also have process.send
if (isMaster && process.send && !cluster.isMaster) { if (process.send && !cluster.isMaster) {
process.send({ process.send({
event: 'bus', event: 'bus',
eventName: event, eventName: event,
payload, payload,
}); });
} else { } else parallel(event, ...payload);
if (!bus[event]) bus[event] = {};
const funcs = Object.keys(bus[event]);
Promise.all(funcs.map((func) => bus[event][func]({
event,
value: payload,
})));
}
} }
global.Hydro.service.bus = { global.Hydro.service.bus = {
subscribe, unsubscribe, publish, addListener, bail, boardcast, emit, on, off, once, parallel, prependListener, removeListener, serial,
}; };
+1 -1
View File
@@ -21,7 +21,7 @@ mongodb.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopolog
.then((Client1) => { .then((Client1) => {
db2 = Client1.db(opts.name); db2 = Client1.db(opts.name);
global.Hydro.service.db.db2 = db2; global.Hydro.service.db.db2 = db2;
bus.publish('system_database_connected', null); bus.parallel('database/connect', db);
}); });
}); });
+10 -11
View File
@@ -1,5 +1,6 @@
import cluster from 'cluster'; import cluster from 'cluster';
import * as db from './db'; import * as db from './db';
import * as bus from './bus';
import * as sysinfo from '../lib/sysinfo'; import * as sysinfo from '../lib/sysinfo';
const coll = db.collection('status'); const coll = db.collection('status');
@@ -24,17 +25,15 @@ export function updateJudger(args) {
} }
if (cluster.isMaster) { if (cluster.isMaster) {
global.Hydro.postInit.push( bus.on('app/started', async () => {
async () => { const info = await sysinfo.get();
const info = await sysinfo.get(); await coll.updateOne(
await coll.updateOne( { mid: info.mid, type: 'server' },
{ mid: info.mid, type: 'server' }, { $set: { ...info, updateAt: new Date(), type: 'server' } },
{ $set: { ...info, updateAt: new Date(), type: 'server' } }, { upsert: true },
{ upsert: true }, );
); setInterval(update, 60 * 1000);
setInterval(update, 60 * 1000); });
},
);
} }
global.Hydro.service.monitor = { update, updateJudger }; global.Hydro.service.monitor = { update, updateJudger };
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@hydrooj/login-with-qq", "name": "@hydrooj/login-with-qq",
"version": "0.0.3", "version": "0.0.4",
"main": "package.json", "main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git", "repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <masnn0@outlook.com>", "author": "undefined <masnn0@outlook.com>",
@@ -10,7 +10,7 @@
"build": "tsc" "build": "tsc"
}, },
"peerDependencies": { "peerDependencies": {
"hydrooj": "^2.12.1||^2.13.0" "hydrooj": "^2.13.1"
}, },
"dependencies": { "dependencies": {
"ws": "^7.3.1" "ws": "^7.3.1"
+2 -1
View File
@@ -1,4 +1,5 @@
import { } from 'hydrooj'; import { } from 'hydrooj';
import * as bus from 'hydrooj/dist/service/bus';
import WebSocket from 'ws'; import WebSocket from 'ws';
export async function postInit() { export async function postInit() {
@@ -32,4 +33,4 @@ export async function postInit() {
}); });
} }
global.Hydro.postInit.push(postInit); bus.subscribe(['app/started'], postInit);