• CodingSouls团队项目冲刺(8)-个人概况


    团队冲刺第八天:

      题库后端收尾:

      1 import * as TypeORM from "typeorm";
      2 import Model from "./common";
      3 
      4 declare var syzoj, ErrorMessage: any;
      5 
      6 import User from "./user";
      7 import File from "./file";
      8 import JudgeState from "./judge_state";
      9 import Contest from "./contest";
     10 import ProblemTag from "./problem_tag";
     11 import ProblemTagMap from "./problem_tag_map";
     12 import SubmissionStatistics, { StatisticsType } from "./submission_statistics";
     13 
     14 import * as fs from "fs-extra";
     15 import * as path from "path";
     16 import * as util from "util";
     17 import * as LRUCache from "lru-cache";
     18 import * as DeepCopy from "deepcopy";
     19 
     20 const problemTagCache = new LRUCache<number, number[]>({
     21   max: syzoj.config.db.cache_size
     22 });
     23 
     24 enum ProblemType {
     25   Traditional = "traditional",
     26   SubmitAnswer = "submit-answer",
     27   Interaction = "interaction"
     28 }
     29 
     30 const statisticsTypes = {
     31   fastest: ['total_time', 'ASC'],
     32   slowest: ['total_time', 'DESC'],
     33   shortest: ['code_length', 'ASC'],
     34   longest: ['code_length', 'DESC'],
     35   min: ['max_memory', 'ASC'],
     36   max: ['max_memory', 'DESC'],
     37   earliest: ['submit_time', 'ASC']
     38 };
     39 
     40 const statisticsCodeOnly = ["fastest", "slowest", "min", "max"];
     41 
     42 @TypeORM.Entity()
     43 export default class Problem extends Model {
     44   static cache = true;
     45 
     46   @TypeORM.PrimaryGeneratedColumn()
     47   id: number;
     48 
     49   @TypeORM.Column({ nullable: true, type: "varchar", length: 80 })
     50   title: string;
     51 
     52   @TypeORM.Index()
     53   @TypeORM.Column({ nullable: true, type: "integer" })
     54   user_id: number;
     55 
     56   @TypeORM.Column({ nullable: true, type: "integer" })
     57   publicizer_id: number;
     58 
     59   @TypeORM.Column({ nullable: true, type: "boolean" })
     60   is_anonymous: boolean;
     61 
     62   @TypeORM.Column({ nullable: true, type: "text" })
     63   description: string;
     64 
     65   @TypeORM.Column({ nullable: true, type: "text" })
     66   input_format: string;
     67 
     68   @TypeORM.Column({ nullable: true, type: "text" })
     69   output_format: string;
     70 
     71   @TypeORM.Column({ nullable: true, type: "text" })
     72   example: string;
     73 
     74   @TypeORM.Column({ nullable: true, type: "text" })
     75   limit_and_hint: string;
     76 
     77   @TypeORM.Column({ nullable: true, type: "integer" })
     78   time_limit: number;
     79 
     80   @TypeORM.Column({ nullable: true, type: "integer" })
     81   memory_limit: number;
     82 
     83   @TypeORM.Column({ nullable: true, type: "integer" })
     84   additional_file_id: number;
     85 
     86   @TypeORM.Column({ nullable: true, type: "integer" })
     87   ac_num: number;
     88 
     89   @TypeORM.Column({ nullable: true, type: "integer" })
     90   submit_num: number;
     91 
     92   @TypeORM.Index()
     93   @TypeORM.Column({ nullable: true, type: "boolean" })
     94   is_public: boolean;
     95 
     96   @TypeORM.Column({ nullable: true, type: "boolean" })
     97   file_io: boolean;
     98 
     99   @TypeORM.Column({ nullable: true, type: "text" })
    100   file_io_input_name: string;
    101 
    102   @TypeORM.Column({ nullable: true, type: "text" })
    103   file_io_output_name: string;
    104 
    105   @TypeORM.Index()
    106   @TypeORM.Column({ nullable: true, type: "datetime" })
    107   publicize_time: Date;
    108 
    109   @TypeORM.Column({ nullable: true,
    110       type: "enum",
    111       enum: ProblemType,
    112       default: ProblemType.Traditional
    113   })
    114   type: ProblemType;
    115 
    116   user?: User;
    117   publicizer?: User;
    118   additional_file?: File;
    119 
    120   async loadRelationships() {
    121     this.user = await User.findById(this.user_id);
    122     this.publicizer = await User.findById(this.publicizer_id);
    123     this.additional_file = await File.findById(this.additional_file_id);
    124   }
    125 
    126   async isAllowedEditBy(user) {
    127     if (!user) return false;
    128     if (await user.hasPrivilege('manage_problem')) return true;
    129     return this.user_id === user.id;
    130   }
    131 
    132   async isAllowedUseBy(user) {
    133     if (this.is_public) return true;
    134     if (!user) return false;
    135     if (await user.hasPrivilege('manage_problem')) return true;
    136     return this.user_id === user.id;
    137   }
    138 
    139   async isAllowedManageBy(user) {
    140     if (!user) return false;
    141     if (await user.hasPrivilege('manage_problem')) return true;
    142     return user.is_admin;
    143   }
    144 
    145   getTestdataPath() {
    146     return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata', this.id.toString());
    147   }
    148 
    149   getTestdataArchivePath() {
    150     return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata-archive', this.id.toString() + '.zip');
    151   }
    152 
    153   async updateTestdata(path, noLimit) {
    154     await syzoj.utils.lock(['Problem::Testdata', this.id], async () => {
    155       let unzipSize = 0, unzipCount = 0;
    156       let p7zip = new (require('node-7z'));
    157       await p7zip.list(path).progress(files => {
    158         unzipCount += files.length;
    159         for (let file of files) unzipSize += file.size;
    160       });
    161       if (!noLimit && unzipCount > syzoj.config.limit.testdata_filecount) throw new ErrorMessage('数据包中的文件太多。');
    162       if (!noLimit && unzipSize > syzoj.config.limit.testdata) throw new ErrorMessage('数据包太大。');
    163 
    164       let dir = this.getTestdataPath();
    165       await fs.remove(dir);
    166       await fs.ensureDir(dir);
    167 
    168       let execFileAsync = util.promisify(require('child_process').execFile);
    169       await execFileAsync(__dirname + '/../bin/unzip', ['-j', '-o', '-d', dir, path]);
    170       await fs.move(path, this.getTestdataArchivePath(), { overwrite: true });
    171     });
    172   }
    173 
    174   async uploadTestdataSingleFile(filename, filepath, size, noLimit) {
    175     await syzoj.utils.lock(['Promise::Testdata', this.id], async () => {
    176       let dir = this.getTestdataPath();
    177       await fs.ensureDir(dir);
    178 
    179       let oldSize = 0, list = await this.listTestdata(), replace = false, oldCount = 0;
    180       if (list) {
    181         oldCount = list.files.length;
    182         for (let file of list.files) {
    183           if (file.filename !== filename) oldSize += file.size;
    184           else replace = true;
    185         }
    186       }
    187 
    188       if (!noLimit && oldSize + size > syzoj.config.limit.testdata) throw new ErrorMessage('数据包太大。');
    189       if (!noLimit && oldCount + (!replace as any as number) > syzoj.config.limit.testdata_filecount) throw new ErrorMessage('数据包中的文件太多。');
    190 
    191       await fs.move(filepath, path.join(dir, filename), { overwrite: true });
    192 
    193       let execFileAsync = util.promisify(require('child_process').execFile);
    194       try { await execFileAsync('dos2unix', [path.join(dir, filename)]); } catch (e) {}
    195 
    196       await fs.remove(this.getTestdataArchivePath());
    197     });
    198   }
    199 
    200   async deleteTestdataSingleFile(filename) {
    201     await syzoj.utils.lock(['Promise::Testdata', this.id], async () => {
    202       await fs.remove(path.join(this.getTestdataPath(), filename));
    203       await fs.remove(this.getTestdataArchivePath());
    204     });
    205   }
    206 
    207   async makeTestdataZip() {
    208     await syzoj.utils.lock(['Promise::Testdata', this.id], async () => {
    209       let dir = this.getTestdataPath();
    210       if (!await syzoj.utils.isDir(dir)) throw new ErrorMessage('无测试数据。');
    211 
    212       let p7zip = new (require('node-7z'));
    213 
    214       let list = await this.listTestdata(), pathlist = list.files.map(file => path.join(dir, file.filename));
    215       if (!pathlist.length) throw new ErrorMessage('无测试数据。');
    216       await fs.ensureDir(path.resolve(this.getTestdataArchivePath(), '..'));
    217       await p7zip.add(this.getTestdataArchivePath(), pathlist);
    218     });
    219   }
    220 
    221   async hasSpecialJudge() {
    222     try {
    223       let dir = this.getTestdataPath();
    224       let list = await fs.readdir(dir);
    225       return list.includes('spj.js') || list.find(x => x.startsWith('spj_')) !== undefined;
    226     } catch (e) {
    227       return false;
    228     }
    229   }
    230 
    231   async listTestdata() {
    232     try {
    233       let dir = this.getTestdataPath();
    234       let filenameList = await fs.readdir(dir);
    235       let list = await Promise.all(filenameList.map(async x => {
    236         let stat = await fs.stat(path.join(dir, x));
    237         if (!stat.isFile()) return undefined;
    238         return {
    239           filename: x,
    240           size: stat.size
    241         };
    242       }));
    243 
    244       list = list.filter(x => x);
    245 
    246       let res = {
    247         files: list,
    248         zip: null
    249       };
    250 
    251       try {
    252         let stat = await fs.stat(this.getTestdataArchivePath());
    253         if (stat.isFile()) {
    254           res.zip = {
    255             size: stat.size
    256           };
    257         }
    258       } catch (e) {
    259         if (list) {
    260           res.zip = {
    261             size: null
    262           };
    263         }
    264       }
    265 
    266       return res;
    267     } catch (e) {
    268       return null;
    269     }
    270   }
    271 
    272   async updateFile(path, type, noLimit) {
    273     let file = await File.upload(path, type, noLimit);
    274 
    275     if (type === 'additional_file') {
    276       this.additional_file_id = file.id;
    277     }
    278 
    279     await this.save();
    280   }
    281 
    282   async validate() {
    283     if (this.time_limit <= 0) return 'Invalid time limit';
    284     if (this.time_limit > syzoj.config.limit.time_limit) return 'Time limit too large';
    285     if (this.memory_limit <= 0) return 'Invalid memory limit';
    286     if (this.memory_limit > syzoj.config.limit.memory_limit) return 'Memory limit too large';
    287     if (!['traditional', 'submit-answer', 'interaction'].includes(this.type)) return 'Invalid problem type';
    288 
    289     if (this.type === 'traditional') {
    290       let filenameRE = /^[w -+.]*$/;
    291       if (this.file_io_input_name && !filenameRE.test(this.file_io_input_name)) return 'Invalid input file name';
    292       if (this.file_io_output_name && !filenameRE.test(this.file_io_output_name)) return 'Invalid output file name';
    293 
    294       if (this.file_io) {
    295         if (!this.file_io_input_name) return 'No input file name';
    296         if (!this.file_io_output_name) return 'No output file name';
    297       }
    298     }
    299 
    300     return null;
    301   }
    302 
    303   async getJudgeState(user, acFirst) {
    304     if (!user) return null;
    305 
    306     let where: any = {
    307       user_id: user.id,
    308       problem_id: this.id
    309     };
    310 
    311     if (acFirst) {
    312       where.status = 'Accepted';
    313 
    314       let state = await JudgeState.findOne({
    315         where: where,
    316         order: {
    317           submit_time: 'DESC'
    318         }
    319       });
    320 
    321       if (state) return state;
    322     }
    323 
    324     if (where.status) delete where.status;
    325 
    326     return await JudgeState.findOne({
    327       where: where,
    328       order: {
    329         submit_time: 'DESC'
    330       }
    331     });
    332   }
    333 
    334   async resetSubmissionCount() {
    335     await syzoj.utils.lock(['Problem::resetSubmissionCount', this.id], async () => {
    336       this.submit_num = await JudgeState.count({ problem_id: this.id, type: TypeORM.Not(1) });
    337       this.ac_num = await JudgeState.count({ score: 100, problem_id: this.id, type: TypeORM.Not(1) });
    338       await this.save();
    339     });
    340   }
    341 
    342   async updateStatistics(user_id) {
    343     await Promise.all(Object.keys(statisticsTypes).map(async type => {
    344       if (this.type === ProblemType.SubmitAnswer && statisticsCodeOnly.includes(type)) return;
    345 
    346       await syzoj.utils.lock(['Problem::UpdateStatistics', this.id, type], async () => {
    347         const [column, order] = statisticsTypes[type];
    348         const result = await JudgeState.createQueryBuilder()
    349                                        .select([column, "id"])
    350                                        .where("user_id = :user_id", { user_id })
    351                                        .andWhere("status = :status", { status: "Accepted" })
    352                                        .andWhere("problem_id = :problem_id", { problem_id: this.id })
    353                                        .orderBy({ [column]: order })
    354                                        .take(1)
    355                                        .getRawMany();
    356         const resultRow = result[0];
    357 
    358         let toDelete = false;
    359         if (!resultRow || resultRow[column] == null) {
    360           toDelete = true;
    361         }
    362 
    363         const baseColumns = {
    364           user_id,
    365           problem_id: this.id,
    366           type: type as StatisticsType
    367         };
    368 
    369         let record = await SubmissionStatistics.findOne(baseColumns);
    370 
    371         if (toDelete) {
    372           if (record) {
    373             await record.destroy();
    374           }
    375 
    376           return;
    377         }
    378 
    379         if (!record) {
    380           record = SubmissionStatistics.create(baseColumns);
    381         }
    382 
    383         record.key = resultRow[column];
    384         record.submission_id = resultRow["id"];
    385 
    386         await record.save();
    387       });
    388     }));
    389   }
    390 
    391   async countStatistics(type) {
    392     if (!statisticsTypes[type] || this.type === ProblemType.SubmitAnswer && statisticsCodeOnly.includes(type)) {
    393       return null;
    394     }
    395 
    396     return await SubmissionStatistics.count({
    397       problem_id: this.id,
    398       type: type
    399     });
    400   }
    401 
    402   async getStatistics(type, paginate) {
    403     if (!statisticsTypes[type] || this.type === ProblemType.SubmitAnswer && statisticsCodeOnly.includes(type)) {
    404       return null;
    405     }
    406 
    407     const statistics = {
    408       type: type,
    409       judge_state: null,
    410       scoreDistribution: null,
    411       prefixSum: null,
    412       suffixSum: null
    413     };
    414 
    415     const order = statisticsTypes[type][1];
    416     const ids = (await SubmissionStatistics.queryPage(paginate, {
    417       problem_id: this.id,
    418       type: type
    419     }, {
    420       '`key`': order
    421     })).map(x => x.submission_id);
    422 
    423     statistics.judge_state = ids.length ? await JudgeState.createQueryBuilder()
    424                                                           .whereInIds(ids)
    425                                                           .orderBy(`FIELD(id,${ids.join(',')})`)
    426                                                           .getMany()
    427                                         : [];
    428 
    429     const a = await JudgeState.createQueryBuilder()
    430                               .select('score')
    431                               .addSelect('COUNT(*)', 'count')
    432                               .where('problem_id = :problem_id', { problem_id: this.id })
    433                               .andWhere('type = 0')
    434                               .andWhere('pending = false')
    435                               .groupBy('score')
    436                               .getRawMany();
    437 
    438     let scoreCount = [];
    439     for (let score of a) {
    440       score.score = Math.min(Math.round(score.score), 100);
    441       scoreCount[score.score] = score.count;
    442     }
    443     if (scoreCount[0] === undefined) scoreCount[0] = 0;
    444     if (scoreCount[100] === undefined) scoreCount[100] = 0;
    445 
    446     if (a[null as any]) {
    447       a[0] += a[null as any];
    448       delete a[null as any];
    449     }
    450 
    451     statistics.scoreDistribution = [];
    452     for (let i = 0; i < scoreCount.length; i++) {
    453       if (scoreCount[i] !== undefined) statistics.scoreDistribution.push({ score: i, count: parseInt(scoreCount[i]) });
    454     }
    455 
    456     statistics.prefixSum = DeepCopy(statistics.scoreDistribution);
    457     statistics.suffixSum = DeepCopy(statistics.scoreDistribution);
    458 
    459     for (let i = 1; i < statistics.prefixSum.length; i++) {
    460       statistics.prefixSum[i].count += statistics.prefixSum[i - 1].count;
    461     }
    462 
    463     for (let i = statistics.prefixSum.length - 1; i >= 1; i--) {
    464       statistics.suffixSum[i - 1].count += statistics.suffixSum[i].count;
    465     }
    466 
    467     return statistics;
    468   }
    469 
    470   async getTags() {
    471     let tagIDs;
    472     if (problemTagCache.has(this.id)) {
    473       tagIDs = problemTagCache.get(this.id);
    474     } else {
    475       let maps = await ProblemTagMap.find({
    476         where: {
    477           problem_id: this.id
    478         }
    479       });
    480 
    481       tagIDs = maps.map(x => x.tag_id);
    482       problemTagCache.set(this.id, tagIDs);
    483     }
    484 
    485     let res = await (tagIDs as any).mapAsync(async tagID => {
    486       return ProblemTag.findById(tagID);
    487     });
    488 
    489     res.sort((a, b) => {
    490       return a.color > b.color ? 1 : -1;
    491     });
    492 
    493     return res;
    494   }
    495 
    496   async setTags(newTagIDs) {
    497     let oldTagIDs = (await this.getTags()).map(x => x.id);
    498 
    499     let delTagIDs = oldTagIDs.filter(x => !newTagIDs.includes(x));
    500     let addTagIDs = newTagIDs.filter(x => !oldTagIDs.includes(x));
    501 
    502     for (let tagID of delTagIDs) {
    503       let map = await ProblemTagMap.findOne({
    504         where: {
    505           problem_id: this.id,
    506           tag_id: tagID
    507         }
    508       });
    509 
    510       await map.destroy();
    511     }
    512 
    513     for (let tagID of addTagIDs) {
    514       let map = await ProblemTagMap.create({
    515         problem_id: this.id,
    516         tag_id: tagID
    517       });
    518 
    519       await map.save();
    520     }
    521 
    522     problemTagCache.set(this.id, newTagIDs);
    523   }
    524 
    525   async changeID(id) {
    526     const entityManager = TypeORM.getManager();
    527 
    528     id = parseInt(id);
    529     await entityManager.query('UPDATE `problem`               SET `id`         = ' + id + ' WHERE `id`         = ' + this.id);
    530     await entityManager.query('UPDATE `judge_state`           SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
    531     await entityManager.query('UPDATE `problem_tag_map`       SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
    532     await entityManager.query('UPDATE `article`               SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
    533     await entityManager.query('UPDATE `submission_statistics` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
    534 
    535     let contests = await Contest.find();
    536     for (let contest of contests) {
    537       let problemIDs = await contest.getProblems();
    538 
    539       let flag = false;
    540       for (let i in problemIDs) {
    541         if (problemIDs[i] === this.id) {
    542           problemIDs[i] = id;
    543           flag = true;
    544         }
    545       }
    546 
    547       if (flag) {
    548         await contest.setProblemsNoCheck(problemIDs);
    549         await contest.save();
    550       }
    551     }
    552 
    553     let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = this.getTestdataArchivePath();
    554 
    555     const oldID = this.id;
    556     this.id = id;
    557 
    558     // Move testdata
    559     let newTestdataDir = this.getTestdataPath(), newTestdataZip = this.getTestdataArchivePath();
    560     if (await syzoj.utils.isDir(oldTestdataDir)) {
    561       await fs.move(oldTestdataDir, newTestdataDir);
    562     }
    563 
    564     if (await syzoj.utils.isFile(oldTestdataZip)) {
    565       await fs.move(oldTestdataZip, newTestdataZip);
    566     }
    567 
    568     await this.save();
    569 
    570     await Problem.deleteFromCache(oldID);
    571     await problemTagCache.del(oldID);
    572   }
    573 
    574   async delete() {
    575     const entityManager = TypeORM.getManager();
    576 
    577     let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = this.getTestdataPath();
    578     await fs.remove(oldTestdataDir);
    579     await fs.remove(oldTestdataZip);
    580 
    581     let submissions = await JudgeState.find({
    582       where: {
    583         problem_id: this.id
    584       }
    585     }), submitCnt = {}, acUsers = new Set();
    586     for (let sm of submissions) {
    587       if (sm.status === 'Accepted') acUsers.add(sm.user_id);
    588       if (!submitCnt[sm.user_id]) {
    589         submitCnt[sm.user_id] = 1;
    590       } else {
    591         submitCnt[sm.user_id]++;
    592       }
    593     }
    594 
    595     for (let u in submitCnt) {
    596       let user = await User.findById(parseInt(u));
    597       user.submit_num -= submitCnt[u];
    598       if (acUsers.has(parseInt(u))) user.ac_num--;
    599       await user.save();
    600     }
    601 
    602     problemTagCache.del(this.id);
    603 
    604     await entityManager.query('DELETE FROM `judge_state`           WHERE `problem_id` = ' + this.id);
    605     await entityManager.query('DELETE FROM `problem_tag_map`       WHERE `problem_id` = ' + this.id);
    606     await entityManager.query('DELETE FROM `article`               WHERE `problem_id` = ' + this.id);
    607     await entityManager.query('DELETE FROM `submission_statistics` WHERE `problem_id` = ' + this.id);
    608 
    609     await this.destroy();
    610   }
    611 }
    完整

  • 相关阅读:
    lazyload【思路】
    图片旋转
    模拟滚动条【大体功能实现】
    Firefox window.close()的使用注意事项
    修改KindEditor的CSS文件
    ownerDocument property
    网易新闻图片展示效果
    关于td不支持position问题
    strcat strncat
    Tcl_FindExecutable
  • 原文地址:https://www.cnblogs.com/125418a/p/12781835.html
Copyright © 2020-2023  润新知