CodingSouls团队项目冲刺(6)-个人概况
团队冲刺第六天:
题库数据库设计与交互:
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 type: ProblemType; 74 75 user?: User; 76 publicizer?: User; 77 additional_file?: File; 78 79 async loadRelationships() { 80 this.user = await User.findById(this.user_id); 81 this.publicizer = await User.findById(this.publicizer_id); 82 this.additional_file = await File.findById(this.additional_file_id); 83 } 84 85 async isAllowedEditBy(user) { 86 if (!user) return false; 87 if (await user.hasPrivilege('manage_problem')) return true; 88 return this.user_id === user.id; 89 } 90 91 async isAllowedUseBy(user) { 92 if (this.is_public) return true; 93 if (!user) return false; 94 if (await user.hasPrivilege('manage_problem')) return true; 95 return this.user_id === user.id; 96 } 97 98 async isAllowedManageBy(user) { 99 if (!user) return false; 100 if (await user.hasPrivilege('manage_problem')) return true; 101 return user.is_admin; 102 } 103 104 getTestdataPath() { 105 return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata', this.id.toString()); 106 } 107 108 getTestdataArchivePath() { 109 return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata-archive', this.id.toString() + '.zip'); 110 } 111 async deleteTestdataSingleFile(filename) { 112 await syzoj.utils.lock(['Promise::Testdata', this.id], async () => { 113 await fs.remove(path.join(this.getTestdataPath(), filename)); 114 await fs.remove(this.getTestdataArchivePath()); 115 }); 116 } 117 118 async makeTestdataZip() { 119 await syzoj.utils.lock(['Promise::Testdata', this.id], async () => { 120 let dir = this.getTestdataPath(); 121 if (!await syzoj.utils.isDir(dir)) throw new ErrorMessage('无测试数据。'); 122 123 let p7zip = new (require('node-7z')); 124 125 let list = await this.listTestdata(), pathlist = list.files.map(file => path.join(dir, file.filename)); 126 if (!pathlist.length) throw new ErrorMessage('无测试数据。'); 127 await fs.ensureDir(path.resolve(this.getTestdataArchivePath(), '..')); 128 await p7zip.add(this.getTestdataArchivePath(), pathlist); 129 }); 130 } 131 132 async hasSpecialJudge() { 133 try { 134 let dir = this.getTestdataPath(); 135 let list = await fs.readdir(dir); 136 return list.includes('spj.js') || list.find(x => x.startsWith('spj_')) !== undefined; 137 } catch (e) { 138 return false; 139 } 140 } 141 142 async listTestdata() { 143 try { 144 let dir = this.getTestdataPath(); 145 let filenameList = await fs.readdir(dir); 146 let list = await Promise.all(filenameList.map(async x => { 147 let stat = await fs.stat(path.join(dir, x)); 148 if (!stat.isFile()) return undefined; 149 return { 150 filename: x, 151 size: stat.size 152 }; 153 })); 154 155 list = list.filter(x => x); 156 157 let res = { 158 files: list, 159 zip: null 160 }; 161 162 try { 163 let stat = await fs.stat(this.getTestdataArchivePath()); 164 if (stat.isFile()) { 165 res.zip = { 166 size: stat.size 167 }; 168 } 169 } catch (e) { 170 if (list) { 171 res.zip = { 172 size: null 173 }; 174 } 175 } 176 177 return res; 178 } catch (e) { 179 return null; 180 } 181 } 182 183 async updateFile(path, type, noLimit) { 184 let file = await File.upload(path, type, noLimit); 185 186 if (type === 'additional_file') { 187 this.additional_file_id = file.id; 188 } 189 190 await this.save(); 191 } 192 193 async validate() { 194 if (this.time_limit <= 0) return 'Invalid time limit'; 195 if (this.time_limit > syzoj.config.limit.time_limit) return 'Time limit too large'; 196 if (this.memory_limit <= 0) return 'Invalid memory limit'; 197 if (this.memory_limit > syzoj.config.limit.memory_limit) return 'Memory limit too large'; 198 if (!['traditional', 'submit-answer', 'interaction'].includes(this.type)) return 'Invalid problem type'; 199 200 if (this.type === 'traditional') { 201 let filenameRE = /^[\w \-\+\.]*$/; 202 if (this.file_io_input_name && !filenameRE.test(this.file_io_input_name)) return 'Invalid input file name'; 203 if (this.file_io_output_name && !filenameRE.test(this.file_io_output_name)) return 'Invalid output file name'; 204 205 if (this.file_io) { 206 if (!this.file_io_input_name) return 'No input file name'; 207 if (!this.file_io_output_name) return 'No output file name'; 208 } 209 } 210 211 return null; 212 } 213 async setTags(newTagIDs) { 214 let oldTagIDs = (await this.getTags()).map(x => x.id); 215 216 let delTagIDs = oldTagIDs.filter(x => !newTagIDs.includes(x)); 217 let addTagIDs = newTagIDs.filter(x => !oldTagIDs.includes(x)); 218 219 for (let tagID of delTagIDs) { 220 let map = await ProblemTagMap.findOne({ 221 where: { 222 problem_id: this.id, 223 tag_id: tagID 224 } 225 }); 226 227 await map.destroy(); 228 } 229 230 for (let tagID of addTagIDs) { 231 let map = await ProblemTagMap.create({ 232 problem_id: this.id, 233 tag_id: tagID 234 }); 235 236 await map.save(); 237 } 238 239 problemTagCache.set(this.id, newTagIDs); 240 } 241 242 async changeID(id) { 243 const entityManager = TypeORM.getManager(); 244 245 id = parseInt(id); 246 await entityManager.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id); 247 await entityManager.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); 248 await entityManager.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); 249 await entityManager.query('UPDATE `article` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); 250 await entityManager.query('UPDATE `submission_statistics` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); 251 252 let contests = await Contest.find(); 253 for (let contest of contests) { 254 let problemIDs = await contest.getProblems(); 255 256 let flag = false; 257 for (let i in problemIDs) { 258 if (problemIDs[i] === this.id) { 259 problemIDs[i] = id; 260 flag = true; 261 } 262 } 263 264 if (flag) { 265 await contest.setProblemsNoCheck(problemIDs); 266 await contest.save(); 267 } 268 } 269 270 let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = this.getTestdataArchivePath(); 271 272 const oldID = this.id; 273 this.id = id; 274 275 // Move testdata 276 let newTestdataDir = this.getTestdataPath(), newTestdataZip = this.getTestdataArchivePath(); 277 if (await syzoj.utils.isDir(oldTestdataDir)) { 278 await fs.move(oldTestdataDir, newTestdataDir); 279 } 280 281 if (await syzoj.utils.isFile(oldTestdataZip)) { 282 await fs.move(oldTestdataZip, newTestdataZip); 283 } 284 285 await this.save(); 286 287 await Problem.deleteFromCache(oldID); 288 await problemTagCache.del(oldID); 289 } 290 291 async delete() { 292 const entityManager = TypeORM.getManager(); 293 294 let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = this.getTestdataPath(); 295 await fs.remove(oldTestdataDir); 296 await fs.remove(oldTestdataZip); 297 298 let submissions = await JudgeState.find({ 299 where: { 300 problem_id: this.id 301 } 302 }), submitCnt = {}, acUsers = new Set(); 303 for (let sm of submissions) { 304 if (sm.status === 'Accepted') acUsers.add(sm.user_id); 305 if (!submitCnt[sm.user_id]) { 306 submitCnt[sm.user_id] = 1; 307 } else { 308 submitCnt[sm.user_id]++; 309 } 310 } 311 312 for (let u in submitCnt) { 313 let user = await User.findById(parseInt(u)); 314 user.submit_num -= submitCnt[u]; 315 if (acUsers.has(parseInt(u))) user.ac_num--; 316 await user.save(); 317 } 318 319 problemTagCache.del(this.id); 320 321 await entityManager.query('DELETE FROM `judge_state` WHERE `problem_id` = ' + this.id); 322 await entityManager.query('DELETE FROM `problem_tag_map` WHERE `problem_id` = ' + this.id); 323 await entityManager.query('DELETE FROM `article` WHERE `problem_id` = ' + this.id); 324 await entityManager.query('DELETE FROM `submission_statistics` WHERE `problem_id` = ' + this.id); 325 326 await this.destroy(); 327 } 328 }
没写完,太难了吧。