core: 支持联合排名

pull/158/head
undefined 3 years ago
parent c0e67917a2
commit f906178de3

@ -1,6 +1,6 @@
{ {
"name": "hydrooj", "name": "hydrooj",
"version": "2.28.40", "version": "2.28.41",
"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",

@ -33,6 +33,7 @@ async function cli() {
const [, modelName, func, ...args] = argv.args as [string, string, string, ...any[]]; const [, modelName, func, ...args] = argv.args as [string, string, string, ...any[]];
if (modelName === 'script') { if (modelName === 'script') {
let arg: any; let arg: any;
console.log(args.join(' '));
try { try {
arg = JSON.parse(args.join(' ')); arg = JSON.parse(args.join(' '));
} catch (e) { } catch (e) {

@ -341,6 +341,11 @@ export interface DomainDoc extends Record<string, any> {
host?: string[], host?: string[],
} }
export interface DomainUnion {
_id: string;
union: string[];
}
// Message // Message
export interface MessageDoc { export interface MessageDoc {
_id: ObjectID, _id: ObjectID,
@ -481,7 +486,7 @@ export interface Task {
} }
export interface UploadStream extends Writable { export interface UploadStream extends Writable {
id: ObjectID id: ObjectID;
} }
export interface HydroFileSystem { export interface HydroFileSystem {
@ -516,27 +521,28 @@ export interface FileNode {
} }
export interface Collections { export interface Collections {
'blacklist': BlacklistDoc, 'blacklist': BlacklistDoc;
'contest': Tdoc, 'contest': Tdoc;
'domain': DomainDoc, 'domain': DomainDoc;
'domain.user': any, 'domain.user': any;
'record': RecordDoc, 'domain.union': DomainUnion;
'document': any, 'record': RecordDoc;
'problem': ProblemDoc, 'document': any;
'user': Udoc, 'problem': ProblemDoc;
'check': any, 'user': Udoc;
'message': MessageDoc, 'check': any;
'token': TokenDoc, 'message': MessageDoc;
'status': any, 'token': TokenDoc;
'oauth': any, 'status': any;
'system': System, 'oauth': any;
'task': Task, 'system': System;
'storage': FileNode, 'task': Task;
'oplog': OplogDoc, 'storage': FileNode;
'opcount': any, 'oplog': OplogDoc;
'fs.chunks': any, 'opcount': any;
'fs.files': any, 'fs.chunks': any;
'document.status': any, 'fs.files': any;
'document.status': any;
} }
export interface Model { export interface Model {

@ -10,6 +10,7 @@ import UserModel, { deleteUserCache } from './user';
const coll = db.collection('domain'); const coll = db.collection('domain');
const collUser = db.collection('domain.user'); const collUser = db.collection('domain.user');
const collUnion = db.collection('domain.union');
interface DomainUserArg { interface DomainUserArg {
_id: number, _id: number,
@ -256,6 +257,21 @@ class DomainModel {
await collUser.deleteMany({ domainId }); await collUser.deleteMany({ domainId });
await bus.parallel('domain/delete', domainId); await bus.parallel('domain/delete', domainId);
} }
@ArgMethod
static async addUnion(domainId: string, union: string[]) {
return await collUnion.updateOne({ _id: domainId }, { $set: { union } }, { upsert: true });
}
@ArgMethod
static async removeUnion(domainId: string) {
return await collUnion.deleteOne({ _id: domainId });
}
@ArgMethod
static async getUnion(domainId: string) {
return await collUnion.findOne({ _id: domainId });
}
} }
export default DomainModel; export default DomainModel;

@ -1,7 +1,8 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import { NumericDictionary } from 'lodash'; import { unionWith, NumericDictionary } from 'lodash';
import { ObjectID, FilterQuery } from 'mongodb'; import { ObjectID, FilterQuery } from 'mongodb';
import { Tdoc, ProblemDoc, Udoc } from '../interface'; import { Tdoc, ProblemDoc, Udoc } from '../interface';
import db from '../service/db';
import domain from '../model/domain'; import domain from '../model/domain';
import * as contest from '../model/contest'; import * as contest from '../model/contest';
import problem from '../model/problem'; import problem from '../model/problem';
@ -39,7 +40,7 @@ async function runProblem(...arg: any[]) {
), ),
page, page,
100, 100,
); ) as any[][];
const rdict = await record.getList(pdoc.domainId, psdocs.map((psdoc) => psdoc.rid), true); const rdict = await record.getList(pdoc.domainId, psdocs.map((psdoc) => psdoc.rid), true);
for (const psdoc of psdocs) { for (const psdoc of psdocs) {
if (rdict[psdoc.rid.toHexString()]) { if (rdict[psdoc.rid.toHexString()]) {
@ -61,7 +62,7 @@ async function runContest(...arg: any[]) {
: arg[0]; : arg[0];
const udict: ND = (typeof arg[0] === 'string') ? arg[2] : arg[1]; const udict: ND = (typeof arg[0] === 'string') ? arg[2] : arg[1];
const report = (typeof arg[0] === 'string') ? arg[3] : arg[2]; const report = (typeof arg[0] === 'string') ? arg[3] : arg[2];
const cursor = await contest.getMultiStatus(tdoc.domainId, tdoc.docId, tdoc.docType) const cursor = contest.getMultiStatus(tdoc.domainId, tdoc.docId, tdoc.docType)
.sort(contest.RULES[tdoc.rule].statusSort); .sort(contest.RULES[tdoc.rule].statusSort);
if (!await cursor.count()) return; if (!await cursor.count()) return;
const rankedTsdocs = await contest.RULES[tdoc.rule].ranked(tdoc, cursor); const rankedTsdocs = await contest.RULES[tdoc.rule].ranked(tdoc, cursor);
@ -90,7 +91,7 @@ export async function calcLevel(domainId: string, report: Function) {
let last = { rp: null }; let last = { rp: null };
let rank = 0; let rank = 0;
let count = 0; let count = 0;
const coll = global.Hydro.service.db.collection('domain.user'); const coll = db.collection('domain.user');
const ducur = domain.getMultiUserInDomain(domainId, filter).project({ rp: 1 }).sort({ rp: -1 }); const ducur = domain.getMultiUserInDomain(domainId, filter).project({ rp: 1 }).sort({ rp: -1 });
let bulk = coll.initializeUnorderedBulkOp(); let bulk = coll.initializeUnorderedBulkOp();
while (await ducur.hasNext()) { while (await ducur.hasNext()) {
@ -111,22 +112,26 @@ export async function calcLevel(domainId: string, report: Function) {
domainId, domainId,
$and: [{ rank: { $lte: (levels[i] * count) / 100 } }], $and: [{ rank: { $lte: (levels[i] * count) / 100 } }],
}; };
if (i < levels.length) query.$and.push({ rank: { $gt: (levels[i + 1] * count) / 100 } }); if (i < levels.length - 1) query.$and.push({ rank: { $gt: (levels[i + 1] * count) / 100 } });
bulk.find(query).update({ $set: { level: i } }); bulk.find(query).update({ $set: { level: i } });
} }
await bulk.execute(); await bulk.execute();
} }
async function runInDomain(domainId: string, isSub: boolean, report: Function) { async function runInDomain(id: string, isSub: boolean, report: Function) {
const info = await domain.getUnion(id);
info.union.push(id);
const udict: ND = {}; const udict: ND = {};
const deltaudict: ND = {}; const deltaudict: ND = {};
const dudocs = await domain.getMultiUserInDomain(domainId).toArray(); const domainId = info ? { $in: info.union } : { $exists: false };
for (const dudoc of dudocs) { const dudocs = unionWith([
deltaudict[dudoc.uid] = dudoc.rpdelta || 0; ...await domain.getMultiUserInDomain(id).toArray(),
} ...await domain.getMultiUserInDomain('', { domainId }).toArray(),
const contests: Tdoc<30 | 60>[] = await contest.getMulti(domainId, { rated: true }) ], (a, b) => a.uid === b.uid);
for (const dudoc of dudocs) deltaudict[dudoc.uid] = dudoc.rpdelta;
const contests: Tdoc<30 | 60>[] = await contest.getMulti('', { domainId, rated: true })
.sort('endAt', -1).toArray() as any; .sort('endAt', -1).toArray() as any;
await report({ message: `Found ${contests.length} contests in ${domainId}` }); await report({ message: `Found ${contests.length} contests in ${id}` });
for (const i in contests) { for (const i in contests) {
const tdoc = contests[i]; const tdoc = contests[i];
await runContest(tdoc, udict, report); await runContest(tdoc, udict, report);
@ -137,8 +142,8 @@ async function runInDomain(domainId: string, isSub: boolean, report: Function) {
} }
} }
// TODO pagination // TODO pagination
const problems = await problem.getMulti(domainId, { hidden: false }).toArray(); const problems = await problem.getMulti('', { domainId, hidden: false }).toArray();
await report({ message: `Found ${problems.length} problems in ${domainId}` }); await report({ message: `Found ${problems.length} problems in ${id}` });
for (const i in problems) { for (const i in problems) {
const pdoc = problems[i]; const pdoc = problems[i];
await runProblem(pdoc, udict); await runProblem(pdoc, udict);
@ -148,16 +153,16 @@ async function runInDomain(domainId: string, isSub: boolean, report: Function) {
}); });
} }
} }
await domain.setMultiUserInDomain(domainId, {}, { rp: 1500 }); await domain.setMultiUserInDomain(id, {}, { rp: 1500 });
const tasks = []; const tasks = [];
for (const uid in udict) { for (const uid in udict) {
const rp = udict[uid] + (deltaudict[uid] || 0); const rp = udict[uid] + (deltaudict[uid] || 0);
tasks.push( const update: any = { $set: { rp } };
domain.updateUserInDomain(domainId, +uid, { $set: { rp }, $push: { ratingHistory: rp } }), if (isSub) update.$push = { ratingHistory: rp };
); tasks.push(domain.updateUserInDomain(id, +uid, update));
} }
await Promise.all(tasks); await Promise.all(tasks);
await calcLevel(domainId, report); await calcLevel(id, report);
} }
export async function run({ domainId }, report: Function) { export async function run({ domainId }, report: Function) {

Loading…
Cancel
Save