├── .gitignore ├── LICENSE ├── README.md ├── server ├── .gitignore ├── api │ ├── admin.js │ ├── common.js │ ├── contest.js │ ├── fileUpload.js │ ├── judge.js │ ├── judgeWorker(Baltamatica).js │ ├── judgeWorker(C++).js │ ├── judgeWorker(Python3).js │ ├── problem.js │ ├── rabbit.js │ └── user.js ├── app.js ├── app.log ├── comparer │ ├── comparer │ ├── comparer.cpp │ └── data.in ├── config.json ├── data │ └── 1 │ │ ├── 1.in │ │ ├── 1.out │ │ ├── 10.in │ │ ├── 10.out │ │ ├── 2.in │ │ ├── 2.out │ │ ├── 3.in │ │ ├── 3.out │ │ ├── 4.in │ │ ├── 4.out │ │ ├── 5.in │ │ ├── 5.out │ │ ├── 6.in │ │ ├── 6.out │ │ ├── 7.in │ │ ├── 7.out │ │ ├── 8.in │ │ ├── 8.out │ │ ├── 9.in │ │ ├── 9.out │ │ ├── config.json │ │ ├── data.zip │ │ └── preview.json ├── db │ └── index.js ├── file.js ├── hitokoto │ ├── categories.json │ └── hitokoto.json ├── package-lock.json ├── package.json ├── refererCheck.js ├── router.js ├── static.js └── sync_data.sh └── web ├── .gitignore ├── babel.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── default-avatar.svg ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── common.js │ ├── icon.png │ ├── longlong.jpg │ ├── nrabbit.jpg │ ├── rabbit.jpg │ └── serpent.jpg ├── chart │ └── myChart.js ├── components │ ├── NotFoundPage.vue │ ├── admin │ │ └── userManage.vue │ ├── announcement │ │ ├── announcementEdit.vue │ │ └── announcementView.vue │ ├── contest │ │ ├── components │ │ │ ├── contestProblemList.vue │ │ │ ├── contestRank.vue │ │ │ ├── contestSubmission.vue │ │ │ └── problemManage.vue │ │ ├── contestList.vue │ │ ├── contestMain.vue │ │ ├── contestPlayer.vue │ │ └── contestProblem.vue │ ├── indexPage.vue │ ├── monacoEditor.vue │ ├── myHeader.vue │ ├── paste │ │ ├── pasteEdit.vue │ │ ├── pasteList.vue │ │ └── pasteView.vue │ ├── problem │ │ ├── caseManage.vue │ │ ├── problemEdit.vue │ │ ├── problemList.vue │ │ ├── problemStat.vue │ │ └── problemView.vue │ ├── rabbit │ │ ├── cuteRabbit.vue │ │ ├── cuteRankList.vue │ │ └── rabbitClickData.vue │ ├── submission │ │ ├── caseDisplay.vue │ │ ├── submissionList.vue │ │ └── submissionView.vue │ └── user │ │ ├── edit │ │ ├── userAudit.vue │ │ ├── userEdit.vue │ │ ├── userProfile.vue │ │ ├── userSecurity.vue │ │ └── userSession.vue │ │ ├── userInfo.vue │ │ ├── userLogin.vue │ │ └── userReg.vue ├── main.js ├── router │ └── router.js └── sto │ └── store.js └── vue.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nywOJ 2 | > An Online Judge System 3 | 4 | ### [demo: https://ty.szsyzx.cn](![]https://ty.szsyzx.cn) 5 | 6 | ### author 7 | + name: ty 8 | + School: Jiangsu Suzhou Experimental Middle School 9 | 10 | ### Powered by 11 | + Vue3 12 | + element-ui-plus 13 | + express 14 | + mariaDB 15 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | comparer/testlib.h 5 | data_backup -------------------------------------------------------------------------------- /server/api/admin.js: -------------------------------------------------------------------------------- 1 | const SqlString = require('mysql/lib/protocol/SqlString'); 2 | const db = require('../db/index'); 3 | 4 | exports.getUserInfoList = (req, res) => { 5 | if (req.session.gid < 3) return res.status(403).end('403 Forbidden'); 6 | let pageId = req.body.pageId, filter = req.body.filter, pageSize = 20; 7 | if (!pageId) pageId = 1; 8 | let sql = "SELECT uid,name,email,gid,inUse FROM userInfo WHERE uid > 0 "; 9 | 10 | pageId = SqlString.escape(pageId); 11 | 12 | if (filter.uid) { 13 | filter.uid = SqlString.escape(filter.uid); 14 | sql += `AND uid=${filter.uid} `; 15 | } 16 | if (filter.name) { 17 | filter.name = SqlString.escape('%' + filter.name + '%'); 18 | sql += `AND name like ${filter.name} `; 19 | } 20 | if (filter.email) { 21 | filter.email = SqlString.escape('%' + filter.email + '%'); 22 | sql += `AND email like ${filter.email} `; 23 | } 24 | if (filter.gid) { 25 | filter.gid = SqlString.escape(filter.gid); 26 | sql += `AND gid=${filter.gid} `; 27 | } 28 | if (filter.inUse) { 29 | filter.inUse = SqlString.escape(filter.inUse); 30 | sql += `AND inUse=${2 - filter.inUse} `; // fit for front-end 31 | } 32 | sql += " LIMIT " + (pageId - 1) * pageSize + "," + pageSize; 33 | 34 | db.query(sql, (err, data) => { 35 | if (err) return res.status(202).send({ message: err }); 36 | let list = data, csql = "SELECT COUNT(*) as total FROM userInfo WHERE uid > 0 "; 37 | if (filter.uid) 38 | csql += `AND uid=${filter.uid} `; 39 | if (filter.name) 40 | csql += `AND name like ${filter.name} `; 41 | if (filter.email) 42 | csql += `AND email like ${filter.email} `; 43 | if (filter.gid) 44 | csql += `AND gid=${filter.gid} `; 45 | if (filter.inUse) 46 | csql += `AND inUse=${2 - filter.inUse} `; // fit for front-end 47 | db.query(csql, (err, data) => { 48 | if (err) return res.status(202).send({ message: err }); 49 | return res.status(200).send({ 50 | total: data[0].total, 51 | userList: list 52 | }); 53 | }); 54 | }); 55 | } 56 | 57 | exports.setBlock = (req, res) => { 58 | if (req.session.gid < 3) return res.status(403).end('403 Forbidden'); 59 | const uid = req.body.uid, status = req.body.status; 60 | if (uid === null || status === null) { 61 | return res.status(202).send({ 62 | message: '请确认信息完善' 63 | }); 64 | } 65 | let sql = "UPDATE userInfo SET inUse=? WHERE uid=?"; 66 | db.query(sql, [status, uid], (err, data) => { 67 | if (err) return res.status(202).send({ message: err }); 68 | if (data.affectedRows > 0) { 69 | return res.status(200).send({ 70 | message: 'sueecss' 71 | }); 72 | } else { 73 | return res.status(202).send({ 74 | message: 'failed' 75 | }); 76 | } 77 | }) 78 | } 79 | 80 | exports.updateUserInfo = (req, res) => { 81 | if (req.session.gid < 3) return res.status(403).end('403 Forbidden'); 82 | const newInfo = req.body.info; 83 | const uid = newInfo.uid, name = newInfo.name, email = newInfo.email, gid = newInfo.gid; 84 | if (!uid || !name || !gid) { 85 | return res.status(202).send({ 86 | message: '请确认信息完善' 87 | }); 88 | } 89 | db.query("UPDATE userInfo SET name=?,email=?,gid=? WHERE uid=?", [name, email, gid, uid], (err, data) => { 90 | if (err) return res.status(202).send({ message: err }); 91 | if (data.affectedRows > 0) { 92 | return res.status(200).send({ 93 | message: 'sueecss' 94 | }); 95 | } else { 96 | return res.status(202).send({ 97 | message: 'failed' 98 | }); 99 | } 100 | }) 101 | } 102 | 103 | exports.addAnnouncement = (req, res) => { 104 | if (req.session.gid < 3) return res.status(403).end('403 Forbidden'); 105 | db.query('INSERT INTO announcement(title,description,weight,time) VALUES (?,?,?,?)', ["请输入公告标题", "请输入公告描述", 10, new Date()], (err, data) => { 106 | if (err) return res.status(202).send({ 107 | message: err 108 | }); 109 | if (data.affectedRows > 0) { 110 | return res.status(200).send({ 111 | aid: data.insertId 112 | }) 113 | } else { 114 | return res.status(202).send({ 115 | message: 'error', 116 | }) 117 | } 118 | }); 119 | } 120 | 121 | exports.updateAnnouncement = (req, res) => { 122 | if (req.session.gid < 3) return res.status(403).end('403 Forbidden'); 123 | const info = req.body.info; 124 | const aid = info.aid, title = info.title, description = info.description, weight = info.weight; 125 | db.query("UPDATE announcement SET title=?,description=?,weight=? WHERE aid=?", [title, description, weight, aid], (err, data) => { 126 | if (err) return res.status(202).send({ message: err }); 127 | if (data.affectedRows > 0) { 128 | return res.status(200).send({ 129 | message: 'sueecss' 130 | }); 131 | } else { 132 | return res.status(202).send({ 133 | message: 'failed' 134 | }); 135 | } 136 | }) 137 | } 138 | -------------------------------------------------------------------------------- /server/api/common.js: -------------------------------------------------------------------------------- 1 | const db = require('../db/index'); 2 | const { briefFormat, Format } = require('../static'); 3 | const SqlString = require('mysql/lib/protocol/SqlString'); 4 | const { randomInt } = require('crypto'); 5 | 6 | const getMark = () => { 7 | const time = Date.now().toString(36); 8 | const str = Math.random().toString(36).slice(2, 7); 9 | return `${time}-${str}`; 10 | } 11 | 12 | exports.getAnnouncementList = (req, res) => { 13 | let sql = "SELECT aid,time,title FROM announcement ORDER BY weight desc LIMIT 5"; 14 | db.query(sql, (err, data) => { 15 | if (err) return res.status(202).send({ message: err }); 16 | for (let i = 0; i < data.length; i++) data[i].time = briefFormat(data[i].time); 17 | return res.status(200).send({ 18 | data: data 19 | }); 20 | }); 21 | } 22 | 23 | exports.getAnnouncementInfo = (req, res) => { 24 | const aid = req.body.aid; 25 | let sql = "SELECT * FROM announcement WHERE aid=?"; 26 | db.query(sql, [aid], (err, data) => { 27 | if (err) return res.status(202).send({ message: err }); 28 | if (!data.length) return res.status(202).send({ 29 | message: 'error' 30 | }); 31 | else { 32 | data[0].time = briefFormat(data[0].time); 33 | return res.status(200).send({ 34 | data: data[0] 35 | }); 36 | } 37 | }); 38 | } 39 | 40 | exports.getPaste = (req, res) => { 41 | db.query('SELECT title,mark,content,uid,time,isPublic FROM pastes WHERE mark=?', [req.body.mark], (err, data) => { 42 | if (err) return res.status(202).send({ message: err }); 43 | if (!data.length) return res.status(202).send({ message: '未找到' }); 44 | if (req.session.gid < 3 && (!data[0].isPublic && data[0].uid !== req.session.uid)) 45 | return res.status(202).send({ message: '无权限查看' }); 46 | data[0].time = Format(data[0].time); 47 | db.query('SELECT name FROM userInfo WHERE uid=?', [data[0].uid], (err2, data2) => { 48 | if (err) return res.status(202).send({ message: err2 }); 49 | data[0].paster = data2[0].name; 50 | return res.status(200).send({ 51 | data: data[0] 52 | }); 53 | }) 54 | }); 55 | } 56 | 57 | exports.addPaste = (req, res) => { 58 | const mark = getMark(); 59 | db.query('INSERT INTO pastes(mark,title,content,uid,time,isPublic) VALUES (?,?,?,?,?,?)', [mark, '请输入标题', '请输入内容', req.session.uid, new Date(), 0], (err, data) => { 60 | if (err) return res.status(202).send({ message: err }); 61 | if (data.affectedRows > 0) 62 | return res.status(200).send({ mark: mark }); 63 | else 64 | return res.status(202).send({ message: 'error' }); 65 | }); 66 | } 67 | 68 | exports.updatePaste = (req, res) => { 69 | const paste = req.body.paste, content = paste.content, title = paste.title; 70 | if (title.length > 20) { 71 | return res.status(202).send({ 72 | message: '标题长度不可大于20' 73 | }); 74 | } 75 | if (paste.length > 10000) { 76 | return res.status(202).send({ 77 | message: '内容长度不可大于10000' 78 | }); 79 | } 80 | db.query("SELECT uid FROM pastes WHERE mark=?", [paste.mark], (err, data) => { 81 | if (err) return res.status(202).send({ message: err }); 82 | if (req.session.gid < 3 && req.session.uid !== data[0].uid) return res.status(202).send({ message: '你只能修改自己的paste' }); 83 | db.query("UPDATE pastes SET title=?,content=?,isPublic=?,time=? WHERE mark=?", [title, content, paste.isPublic, new Date(), paste.mark], (err, data) => { 84 | if (err) return res.status(202).send({ message: err }); 85 | if (data.affectedRows > 0) { 86 | return res.status(200).send({ 87 | message: 'sueecss' 88 | }); 89 | } else { 90 | return res.status(202).send({ 91 | message: 'failed' 92 | }); 93 | } 94 | }) 95 | }) 96 | } 97 | 98 | exports.delPaste = (req, res) => { 99 | const mark = req.body.mark; 100 | db.query("SELECT uid FROM pastes WHERE mark=?", [mark], (err, data) => { 101 | if (err) return res.status(202).send({ message: err }); 102 | if (req.session.gid < 3 && req.session.uid !== data[0].uid) return res.status(202).send({ message: '你只能删除自己的paste' }); 103 | db.query("DELETE FROM pastes WHERE mark=?", [mark], (err, data) => { 104 | if (err) return res.status(202).send({ message: err }); 105 | if (data.affectedRows > 0) { 106 | return res.status(200).send({ 107 | message: 'sueecss' 108 | }); 109 | } else { 110 | return res.status(202).send({ 111 | message: 'failed' 112 | }); 113 | } 114 | }) 115 | }) 116 | } 117 | 118 | exports.getPasteList = (req, res) => { 119 | let pageId = req.body.pageId, 120 | pageSize = 20, uid = null; 121 | if (req.body.uid) uid = req.body.uid; 122 | if (!pageId) pageId = 1; 123 | if (req.session.gid < 3) uid = req.session.uid; 124 | let sql = "SELECT p.id,p.mark,p.title,p.uid,p.time,p.isPublic,u.name as publisher FROM pastes p INNER JOIN userInfo u ON u.uid = p.uid"; 125 | if (uid) sql += ` WHERE p.uid=${uid}`; 126 | sql += " ORDER BY p.id DESC LIMIT " + (pageId - 1) * pageSize + "," + pageSize; 127 | db.query(sql, async (err, data) => { 128 | if (err) return res.status(202).send({ message: err }); 129 | let list = data; 130 | for (let i = 0; i < list.length; i++) 131 | list[i].time = Format(list[i].time); 132 | sql = 'SELECT COUNT(*) as total FROM pastes'; 133 | if (uid) sql += ` WHERE uid=${uid}`; 134 | db.query(sql, (err, data) => { 135 | if (err) return res.status(202).send({ message: err }); 136 | return res.status(200).send({ 137 | total: data[0].total, 138 | data: list 139 | }); 140 | }); 141 | }); 142 | } 143 | 144 | const hitokoto = require('../hitokoto/hitokoto.json'), len = hitokoto.length; 145 | 146 | exports.getHitokoto = (req, res) => { 147 | return res.status(200).send(hitokoto[randomInt(len)]); 148 | } -------------------------------------------------------------------------------- /server/api/fileUpload.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const multer = require('multer'); 4 | const yauzl = require('yauzl'); 5 | const { setFile } = require('../file'); 6 | const compressing = require('compressing'); 7 | const { problemAuth } = require('./problem'); 8 | 9 | const CASE_MAX_TOTAL_SIZE = 200 * 1024 * 1024; // 200MB limit 10 | 11 | // Check zip file size before extraction 12 | const checkZipSize = (zipPath, userGid, maxTotalSize) => { 13 | return new Promise((resolve, reject) => { 14 | if (userGid >= 3) { 15 | resolve(true); 16 | return; 17 | } 18 | 19 | let totalSize = 0; 20 | yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => { 21 | if (err) reject(err); 22 | 23 | zipfile.on('entry', (entry) => { 24 | totalSize += entry.uncompressedSize; 25 | if (totalSize > maxTotalSize) { 26 | zipfile.close(); 27 | resolve(false); 28 | } 29 | zipfile.readEntry(); 30 | }); 31 | 32 | zipfile.on('end', () => { 33 | resolve(true); 34 | }); 35 | 36 | zipfile.on('error', (err) => { 37 | reject(err); 38 | }); 39 | 40 | zipfile.readEntry(); 41 | }); 42 | }); 43 | }; 44 | 45 | const caseUpload = () => { 46 | return multer({ 47 | fileFilter: (req, file, cb) => { 48 | cb(null, true); 49 | }, 50 | storage: multer.diskStorage({ 51 | destination: (req, file, cb) => { 52 | const dir = "./data/" + req.body.pid; 53 | if (fs.existsSync(dir)) { 54 | fs.rmSync(dir, { recursive: true }); 55 | } 56 | fs.mkdirSync(dir, { recursive: true }); 57 | cb(null, dir); 58 | }, 59 | filename: (req, file, cb) => { 60 | cb(null, 'data.zip'); 61 | } 62 | }) 63 | }); 64 | }; 65 | 66 | const processUploadedFiles = async (files, destination) => { 67 | let cases = []; 68 | for (const file of files) { 69 | if (file.endsWith('.in')) { 70 | const name = file.slice(0, -3); 71 | if (fs.existsSync(path.join(destination, `${name}.out`))) { 72 | cases.push({ 73 | name: name, 74 | input: file, 75 | output: `${name}.out`, 76 | }); 77 | } 78 | } 79 | } 80 | 81 | cases.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true })); 82 | 83 | return cases.map((c, index) => ({ 84 | index: index + 1, 85 | input: c.input, 86 | output: c.output, 87 | subtaskId: 1 88 | })); 89 | }; 90 | 91 | const handleCaseUpload = async (req, res) => { 92 | try { 93 | if (req.session.gid < 2 || !((await problemAuth(req, req.body.pid)).manage)) { 94 | return res.status(403).end('403 Forbidden'); 95 | } 96 | 97 | const isSizeValid = await checkZipSize(req.file.path, req.session.gid, CASE_MAX_TOTAL_SIZE); 98 | if (!isSizeValid) { 99 | fs.unlinkSync(req.file.path); // delete 100 | return res.status(202).send({ 101 | err: "Total uncompressed size exceeds 200MB limit" 102 | }); 103 | } 104 | 105 | await compressing.zip.uncompress(req.file.path, req.file.destination); 106 | 107 | fs.readdir(req.file.destination, async (err, files) => { 108 | if (err) 109 | return res.status(202).send({ err: err }); 110 | const uniqueCases = await processUploadedFiles(files, req.file.destination); 111 | const config = { 112 | cases: uniqueCases, 113 | subtask: [{ 114 | index: 1, 115 | score: 100, 116 | option: 0, 117 | skip: false 118 | }] 119 | }; 120 | await setFile(`${req.file.destination}/config.json`, JSON.stringify(config)); 121 | res.json({ file: req.file }); 122 | }); 123 | } catch (error) { 124 | if (req.file && req.file.path && fs.existsSync(req.file.path)) { 125 | fs.unlinkSync(req.file.path); 126 | } 127 | res.status(202).send({ err: error.message }); 128 | } 129 | }; 130 | 131 | module.exports = { 132 | caseUpload: caseUpload(), 133 | handleCaseUpload 134 | }; -------------------------------------------------------------------------------- /server/api/rabbit.js: -------------------------------------------------------------------------------- 1 | const db = require('../db/index'); 2 | const { Format, ip2loc } = require('../static'); 3 | let dayClick = {}; 4 | const SqlString = require('mysql/lib/protocol/SqlString'); 5 | 6 | exports.all = (req, res) => { 7 | let sql = 'SELECT clickList.id,clickList.time,clickList.uid,userInfo.name,clickList.ip,clickList.iploc,userInfo.clickCnt,userInfo.gid FROM clickList INNER JOIN userInfo ON userInfo.uid = clickList.uid ORDER BY clickList.id DESC LIMIT 20'; 8 | db.query(sql, (err, data) => { 9 | if (err) return res.status(202).send({ 10 | message: err 11 | }); 12 | for (let i = 0; i < data.length; i++) data[i].time = Format(data[i].time); 13 | return res.status(200).send({ 14 | data: data 15 | }); 16 | }) 17 | } 18 | 19 | exports.add = (req, res) => { 20 | const ip = req.session.ip, uid = req.session.uid; 21 | 22 | if (!dayClick.lastClick || new Date().getDate() !== dayClick.lastClick.getDate()) { 23 | dayClick = {}; 24 | } 25 | if (!dayClick[uid]) dayClick[uid] = 0; 26 | const cnt = dayClick[uid]; 27 | 28 | if (cnt >= 1000000) { 29 | return res.status(202).send({ 30 | message: "请明天再试" 31 | }) 32 | } 33 | 34 | const iploc = ip2loc(ip); 35 | db.query('INSERT INTO clickList(uid,time,ip,iploc) values (?,?,?,?)', [uid, new Date(), ip, iploc], (err, data) => { 36 | if (err) return res.status(202).send({ 37 | message: err 38 | }); 39 | if (data.affectedRows > 0) { 40 | let sql = "INSERT INTO rabbitstat (uid, click, date) VALUES (?, 1, ?) ON DUPLICATE KEY UPDATE click = click + 1"; 41 | db.query(sql, [uid, new Date()], (err2, data2) => { 42 | if (err2) return res.status(202).send({ 43 | message: err2 44 | }); 45 | dayClick[uid]++; 46 | dayClick.lastClick = new Date(); 47 | db.query("UPDATE userInfo SET clickCnt=clickCnt+1 WHERE uid=?", [uid]); 48 | return res.status(200).send({ 49 | message: 'success', 50 | }); 51 | }); 52 | } else { 53 | return res.status(202).send({ 54 | message: 'error', 55 | }) 56 | } 57 | }); 58 | } 59 | 60 | exports.getClickCnt = (req, res) => { 61 | let uid = req.session.uid; 62 | if (req.body.uid) uid = req.body.uid; 63 | let sql = 'SELECT clickCnt FROM userInfo WHERE uid=?'; 64 | db.query(sql, [uid], (err, data) => { 65 | if (err) return res.status(202).send({ 66 | message: err 67 | }); 68 | return res.status(200).send({ clickCnt: data[0].clickCnt }); 69 | }) 70 | } 71 | 72 | exports.getRankInfo = (req, res) => { 73 | let pageId = req.body.pageId, 74 | pageSize = 20; 75 | if (!pageId) pageId = 1; 76 | pageId = SqlString.escape(pageId); 77 | let sql = 'SELECT uid,name,clickCnt,motto,gid,qq FROM userInfo GROUP BY uid ORDER BY clickCnt DESC LIMIT ' + (pageId - 1) * pageSize + ',' + pageSize; 78 | db.query(sql, (err, data) => { 79 | if (err) return res.status(202).send({ 80 | message: err 81 | }); 82 | for (let i = 0; i < data.length; i++) { 83 | if (String(data[i].motto).length > 50) 84 | data[i].motto = data[i].motto.substring(0, 50) + '...'; 85 | data[i].rk = (pageId - 1) * pageSize + i + 1; 86 | data[i].avatar = 87 | data[i].qq ? `https://q1.qlogo.cn/g?b=qq&nk=${data[i].qq}&s=3` 88 | : '/default-avatar.svg'; 89 | 90 | } 91 | db.query("SELECT COUNT(*) as total FROM userInfo", (err2, data2) => { 92 | if (err2) return res.status(202).send({ message: err2 }); 93 | return res.status(200).send({ 94 | total: data2[0].total, 95 | data: data 96 | }); 97 | }); 98 | }) 99 | } 100 | 101 | exports.getClickData = (req, res) => { 102 | let quid = req.body.uid, day = req.body.day; 103 | if (typeof day === 'undefined' || day > 100) 104 | day = 6; 105 | else day = day - 1; 106 | const sql = ` 107 | SELECT 108 | date, 109 | SUM(click) AS clickCnt, 110 | COUNT(DISTINCT uid) AS userCnt 111 | FROM rabbitstat 112 | WHERE date >= DATE_SUB(CURDATE(), INTERVAL ? DAY) ` + 113 | (typeof quid !== 'undefined' ? `AND uid = ${SqlString.escape(quid)} ` : "") + 114 | `GROUP BY date 115 | ORDER BY date; 116 | `; 117 | db.query(sql, [day], (err, data) => { 118 | if (err) return res.status(202).send({ 119 | message: err 120 | }); 121 | let mp = [], now = new Date(); 122 | for (let i = day; i >= 0; i--) 123 | mp[Format(new Date(now.getTime() - 1000 * 3600 * 24 * i)).substring(5, 10)] = [0, 0]; 124 | for (let i = 0; i < data.length; i++) 125 | mp[Format(data[i].date).substring(5, 10)] = [data[i].clickCnt, data[i].userCnt]; 126 | let result = [] 127 | for (let key in mp) { 128 | result.push({ 129 | date: key, 130 | clickCnt: mp[key][0], 131 | userCnt: mp[key][1] 132 | }) 133 | } 134 | return res.status(200).send({ 135 | data: result 136 | }); 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const session = require('express-session') 3 | const app = express() 4 | const cors = require('cors') 5 | const router = require('./router') 6 | const config = require('./config.json') 7 | const MySQLStore = require('express-mysql-session')(session); 8 | const options = { 9 | host: config.DB.host, 10 | port: config.DB.port, 11 | user: config.DB.username, 12 | password: config.DB.password, 13 | database: config.DB.databasename 14 | }; 15 | 16 | const refererCheck = require('./refererCheck'); 17 | const whiteList = ['localhost', 'https://ty.szsyzx.cn/', 'https://www.niyiwei.com']; 18 | app.use(refererCheck(whiteList, { allowEmpty: true })); 19 | 20 | const sessionStore = new MySQLStore(options); 21 | 22 | app.use(session({ 23 | store: sessionStore, 24 | secret: '114514-nywOJ-1919810', 25 | resave: true, 26 | saveUninitialized: true, 27 | cookie: { maxAge: parseInt(config.SESSION.expire) }, 28 | name: 'token' 29 | })); 30 | const parser = require('ua-parser-js'); 31 | const db = require('./db/index'); 32 | 33 | app.use((req, res, next) => { 34 | const ip = req.headers['x-forwarded-for']?.split(',')[0] || 35 | req.headers['x-real-ip'] || 36 | req.socket.remoteAddress || 37 | null; 38 | if (ip === null) 39 | res.status(403).end('403 Forbidden'); 40 | else { 41 | req.session.ip = ip; 42 | next(); 43 | } 44 | }); 45 | 46 | app.use((req, res, next) => { 47 | req.useragent = parser(req.headers['user-agent']); 48 | if (req.session.uid) { 49 | db.query('UPDATE userSession SET lastact=? WHERE token=? AND uid=?', [new Date(), req.sessionID, req.session.uid]); 50 | if (req.url.match('^\/api\/admin') && req.session.gid !== 3) 51 | return res.status(403).end('403 Forbidden'); 52 | next(); 53 | } else { 54 | req.session.gid = 1; 55 | if (req.url === '/api/user/login' || 56 | req.url === '/api/user/reg' || 57 | req.url === '/api/user/setUserEmail' || 58 | req.url === '/api/user/sendEmailVerifyCode' || 59 | req.url === '/api/user/getUserInfo' || 60 | req.url === '/api/common/getAnnouncementList' || 61 | req.url === '/api/common/getHitokoto' || 62 | req.url === '/api/rabbit/getRankInfo' || 63 | req.url === '/api/rabbit/getClickData' || 64 | // grant new access 65 | req.url === '/api/problem/getProblemList' || 66 | req.url === '/api/rabbit/all' || 67 | req.url === '/api/contest/getContestList' || 68 | req.url === '/api/judge/getSubmissionList' || 69 | req.url === '/api/common/getAnnouncementInfo' || 70 | req.url === '/api/problem/getProblemTags' || 71 | req.url === '/api/problem/getProblemPublishers' || 72 | req.url === '/api/judge/getLangs' || 73 | req.url === '/api/judge/receiveTask' 74 | ) 75 | next(); 76 | else return res.status(404).end('404 Not Found'); 77 | } 78 | }); 79 | 80 | app.use(express.json({ extended: true, limit: '10mb' })); 81 | app.use(express.urlencoded({ extended: true, limit: '10mb' })); 82 | app.use(cors()) //配置跨域 83 | app.use(router) //配置路由 84 | 85 | const logFilePath = './app.log'; 86 | const fs = require('fs'); 87 | 88 | const logStream = fs.createWriteStream(logFilePath, { flags: 'a' }); 89 | 90 | const originalLog = console.log; 91 | const { Format } = require('./static') 92 | 93 | const logToFileAndConsole = (level, ...args) => { 94 | const message = `[${Format(new Date())}] ${level}: ${args.join(' ')}\n`; 95 | logStream.write(message); 96 | originalLog(message); 97 | }; 98 | 99 | console.log = (...args) => logToFileAndConsole('LOG', ...args); 100 | console.error = (...args) => logToFileAndConsole('ERROR', ...args); 101 | 102 | process.on('unhandledRejection', (reason, promise) => { 103 | console.error('Unhandled Rejection at:', promise, 'reason:', reason); 104 | }); 105 | 106 | process.on('uncaughtException', (err) => { 107 | console.error('Uncaught Exception:', `Error: ${err.message}\nStack: ${err.stack}`); 108 | }); 109 | 110 | app.listen(1234, () => { 111 | console.log('success!!!'); 112 | }); 113 | 114 | -------------------------------------------------------------------------------- /server/app.log: -------------------------------------------------------------------------------- 1 | [2024-12-31 21:00:28] LOG: success!!! 2 | -------------------------------------------------------------------------------- /server/comparer/comparer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baymin-ty/nywOJ/1f119b304d961a0a12001f40869a95f7f90ce414/server/comparer/comparer -------------------------------------------------------------------------------- /server/comparer/comparer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * It is the comparer for nywOJ 3 | * author: ty 4 | */ 5 | 6 | #include "testlib.h" 7 | 8 | #define SHOW_DIFF_LENGTH 40 9 | #define TRIM(s) s.erase(s.find_last_not_of(" \f\t\r\v\n") + 1) 10 | 11 | int line, difcol, l, r1, r2; 12 | bool ok = true; 13 | std::string Std, usr, Exp, found; 14 | 15 | int main(int argc, char *argv[]) 16 | { 17 | registerTestlibCmd(argc, argv); 18 | 19 | while (!ans.eof() && ok) 20 | { 21 | ++line; 22 | Std = ans.readLine(); 23 | usr = ouf.readLine(); 24 | 25 | TRIM(Std); 26 | TRIM(usr); 27 | 28 | if (Std == usr) 29 | continue; 30 | ok = false; 31 | for (int i = 0; i < usr.length(); i++) 32 | { 33 | if (Std[i] == usr[i]) 34 | continue; 35 | difcol = i; 36 | break; 37 | } 38 | } 39 | 40 | if (ok) 41 | quitf(_ok, "total line: %d", line); 42 | 43 | l = difcol - SHOW_DIFF_LENGTH; 44 | 45 | r1 = r2 = difcol + SHOW_DIFF_LENGTH; 46 | 47 | if (l < 0) 48 | l = 0; 49 | if (r1 > usr.length()) 50 | r1 = usr.length(); 51 | if (r2 > Std.length()) 52 | r2 = Std.length(); 53 | 54 | found = usr.substr(l, r1 - l + 1); 55 | Exp = Std.substr(l, r2 - l + 1); 56 | 57 | if (l > 0) 58 | { 59 | Exp = "......" + Exp; 60 | found = "......" + found; 61 | } 62 | 63 | if (r1 + 1 < usr.length()) 64 | found += "......"; 65 | if (r2 + 1 < Std.length()) 66 | Exp += "......"; 67 | 68 | quitf(_wa, "diff on line %d, column %d\nexpected \'%s\'\n found \'%s\'\n", line, difcol + 1, Exp.c_str(), found.c_str()); 69 | } -------------------------------------------------------------------------------- /server/comparer/data.in: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baymin-ty/nywOJ/1f119b304d961a0a12001f40869a95f7f90ce414/server/comparer/data.in -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "DB": { 3 | "host": "localhost", 4 | "port": 3306, 5 | "username": "root", 6 | "password": "114514", 7 | "databasename": "test" 8 | }, 9 | "EMAIL": { 10 | "username": "nywojservice@163.com", 11 | "password": "pwd" 12 | }, 13 | "SESSION": { 14 | "expire": "604800000" 15 | }, 16 | "JUDGE": { 17 | "ISSERVER": true, 18 | "NAME": "ty's MBP" 19 | } 20 | } -------------------------------------------------------------------------------- /server/data/1/1.in: -------------------------------------------------------------------------------- 1 | -114514 1919810 2 | -------------------------------------------------------------------------------- /server/data/1/1.out: -------------------------------------------------------------------------------- 1 | 1805296 2 | -------------------------------------------------------------------------------- /server/data/1/10.in: -------------------------------------------------------------------------------- 1 | 937032200 584941400 2 | -------------------------------------------------------------------------------- /server/data/1/10.out: -------------------------------------------------------------------------------- 1 | 1521973600 2 | -------------------------------------------------------------------------------- /server/data/1/2.in: -------------------------------------------------------------------------------- 1 | -1919810 114514 2 | -------------------------------------------------------------------------------- /server/data/1/2.out: -------------------------------------------------------------------------------- 1 | -1805296 2 | -------------------------------------------------------------------------------- /server/data/1/3.in: -------------------------------------------------------------------------------- 1 | 349154568 180247698 2 | -------------------------------------------------------------------------------- /server/data/1/3.out: -------------------------------------------------------------------------------- 1 | 529402266 2 | -------------------------------------------------------------------------------- /server/data/1/4.in: -------------------------------------------------------------------------------- 1 | -175136475 817059220 2 | -------------------------------------------------------------------------------- /server/data/1/4.out: -------------------------------------------------------------------------------- 1 | 641922745 2 | -------------------------------------------------------------------------------- /server/data/1/5.in: -------------------------------------------------------------------------------- 1 | 155692013 -501656708 2 | -------------------------------------------------------------------------------- /server/data/1/5.out: -------------------------------------------------------------------------------- 1 | -345964695 2 | -------------------------------------------------------------------------------- /server/data/1/6.in: -------------------------------------------------------------------------------- 1 | -255188006 -145225879 2 | -------------------------------------------------------------------------------- /server/data/1/6.out: -------------------------------------------------------------------------------- 1 | -400413885 2 | -------------------------------------------------------------------------------- /server/data/1/7.in: -------------------------------------------------------------------------------- 1 | -105074228 241408813 2 | -------------------------------------------------------------------------------- /server/data/1/7.out: -------------------------------------------------------------------------------- 1 | 136334585 2 | -------------------------------------------------------------------------------- /server/data/1/8.in: -------------------------------------------------------------------------------- 1 | -104387541 557196859 2 | -------------------------------------------------------------------------------- /server/data/1/8.out: -------------------------------------------------------------------------------- 1 | 452809318 2 | -------------------------------------------------------------------------------- /server/data/1/9.in: -------------------------------------------------------------------------------- 1 | 549325784 -690933930 2 | -------------------------------------------------------------------------------- /server/data/1/9.out: -------------------------------------------------------------------------------- 1 | -141608146 2 | -------------------------------------------------------------------------------- /server/data/1/config.json: -------------------------------------------------------------------------------- 1 | {"cases":[{"index":1,"input":"1.in","output":"1.out","subtaskId":1},{"index":2,"input":"2.in","output":"2.out","subtaskId":1},{"index":3,"input":"3.in","output":"3.out","subtaskId":1},{"index":4,"input":"4.in","output":"4.out","subtaskId":2},{"index":5,"input":"5.in","output":"5.out","subtaskId":2},{"index":6,"input":"6.in","output":"6.out","subtaskId":3},{"index":7,"input":"7.in","output":"7.out","subtaskId":3},{"index":8,"input":"8.in","output":"8.out","subtaskId":3},{"index":9,"input":"9.in","output":"9.out","subtaskId":3},{"index":10,"input":"10.in","output":"10.out","subtaskId":4}],"subtask":[{"index":1,"score":50,"option":0,"skip":0,"dependencies":[]},{"index":2,"score":20,"option":0,"skip":false,"dependencies":[]},{"index":3,"score":20,"option":1,"skip":true,"dependencies":[1]},{"index":4,"score":10,"option":1,"skip":true,"dependencies":[2,3]}]} -------------------------------------------------------------------------------- /server/data/1/data.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baymin-ty/nywOJ/1f119b304d961a0a12001f40869a95f7f90ce414/server/data/1/data.zip -------------------------------------------------------------------------------- /server/data/1/preview.json: -------------------------------------------------------------------------------- 1 | [{"index":1,"inName":"1.in","outName":"1.out","subtaskId":1,"input":{"content":"-114514 1919810\n","size":"16 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"output":{"content":"1805296\n","size":"8 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"edit":0},{"index":2,"inName":"2.in","outName":"2.out","subtaskId":1,"input":{"content":"-1919810 114514\n","size":"16 B","create":"2024-08-01 19:37:48","modified":"2025-01-01 00:26:45"},"output":{"content":"-1805296\n","size":"9 B","create":"2024-08-01 19:37:48","modified":"2025-01-01 01:14:25"},"edit":0},{"index":3,"inName":"3.in","outName":"3.out","subtaskId":1,"input":{"content":"349154568 180247698\n","size":"20 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"output":{"content":"529402266\n","size":"10 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"edit":0},{"index":4,"inName":"4.in","outName":"4.out","subtaskId":2,"input":{"content":"-175136475 817059220\n","size":"21 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"output":{"content":"641922745\n","size":"10 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"edit":0},{"index":5,"inName":"5.in","outName":"5.out","subtaskId":2,"input":{"content":"155692013 -501656708\n","size":"21 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"output":{"content":"-345964695\n","size":"11 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"edit":0},{"index":6,"inName":"6.in","outName":"6.out","subtaskId":3,"input":{"content":"-255188006 -145225879\n","size":"22 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"output":{"content":"-400413885\n","size":"11 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"edit":0},{"index":7,"inName":"7.in","outName":"7.out","subtaskId":3,"input":{"content":"-105074228 241408813\n","size":"21 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"output":{"content":"136334585\n","size":"10 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"edit":0},{"index":8,"inName":"8.in","outName":"8.out","subtaskId":3,"input":{"content":"-104387541 557196859\n","size":"21 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"output":{"content":"452809318\n","size":"10 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"edit":0},{"index":9,"inName":"9.in","outName":"9.out","subtaskId":3,"input":{"content":"549325784 -690933930\n","size":"21 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"output":{"content":"-141608146\n","size":"11 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"edit":0},{"index":10,"inName":"10.in","outName":"10.out","subtaskId":4,"input":{"content":"937032200 584941400\n","size":"20 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"output":{"content":"1521973600\n","size":"11 B","create":"2024-08-01 19:37:48","modified":"2024-08-01 19:37:48"},"edit":0}] -------------------------------------------------------------------------------- /server/db/index.js: -------------------------------------------------------------------------------- 1 | const mysql = require('mysql'); 2 | const config = require('../config.json'); 3 | const db = mysql.createPool({ 4 | host: config.DB.host, 5 | port: config.DB.port, 6 | user: config.DB.username, 7 | password: config.DB.password, 8 | database: config.DB.databasename, 9 | charset: 'utf8mb4', 10 | collation: 'utf8mb4_unicode_ci' 11 | }); 12 | 13 | module.exports = db; 14 | -------------------------------------------------------------------------------- /server/file.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const { promisify } = require('util'); 4 | const path = require('path'); 5 | 6 | const readFile = promisify(fs.readFile) 7 | const writeFile = promisify(fs.writeFile) 8 | 9 | const getFile = async (loc) => { 10 | const filePath = path.join(__dirname, loc) 11 | if (!fs.existsSync(filePath)) { 12 | return null; 13 | } 14 | const data = await readFile(filePath, 'utf-8') 15 | return data; 16 | } 17 | 18 | const setFile = async (loc, data) => { 19 | const filePath = path.join(__dirname, loc) 20 | await writeFile(filePath, data); 21 | } 22 | 23 | const delFile = async (loc) => { 24 | const filePath = path.join(__dirname, loc) 25 | await fs.unlinkSync(filePath); 26 | } 27 | 28 | module.exports = { 29 | getFile, 30 | setFile, 31 | delFile 32 | } -------------------------------------------------------------------------------- /server/hitokoto/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "动画", 5 | "desc": "Anime - 动画", 6 | "key": "a" 7 | }, 8 | { 9 | "id": 2, 10 | "name": "漫画", 11 | "desc": "Comic - 漫画", 12 | "key": "b" 13 | }, 14 | { 15 | "id": 3, 16 | "name": "游戏", 17 | "desc": "Game - 游戏", 18 | "key": "c" 19 | }, 20 | { 21 | "id": 4, 22 | "name": "文学", 23 | "desc": "Literature - 文学。主要收录现代文学:小说、散文、戏剧。", 24 | "key": "d" 25 | }, 26 | { 27 | "id": 5, 28 | "name": "原创", 29 | "desc": "Original - 原创", 30 | "key": "e" 31 | }, 32 | { 33 | "id": 6, 34 | "name": "网络", 35 | "desc": "Internet - 来自网络", 36 | "key": "f" 37 | }, 38 | { 39 | "id": 7, 40 | "name": "其他", 41 | "desc": "Other - 其他", 42 | "key": "g" 43 | }, 44 | { 45 | "id": 8, 46 | "name": "影视", 47 | "desc": "Video - 影视", 48 | "key": "h" 49 | }, 50 | { 51 | "id": 9, 52 | "name": "诗词", 53 | "desc": "Poem - 诗词。主要收录中国古代文学,如:诗、歌、词、赋、曲等。", 54 | "key": "i" 55 | }, 56 | { 57 | "id": 10, 58 | "name": "网易云", 59 | "desc": "NCM - 网易云。主要收录网易云音乐热评。", 60 | "key": "j" 61 | }, 62 | { 63 | "id": 11, 64 | "name": "哲学", 65 | "desc": "Philosophy - 哲学", 66 | "key": "k" 67 | }, 68 | { 69 | "id": 12, 70 | "name": "抖机灵", 71 | "desc": "Funny - 抖机灵", 72 | "key": "l" 73 | } 74 | ] -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "async": "^3.2.4", 4 | "axios": "^1.3.4", 5 | "bcryptjs": "^2.4.3", 6 | "body-parser": "^1.20.2", 7 | "compressing": "^1.8.0", 8 | "cors": "^2.8.5", 9 | "express": "^4.18.2", 10 | "express-mysql-session": "^3.0.0", 11 | "express-session": "^1.17.3", 12 | "express-zip": "^3.0.0", 13 | "ip2region": "^2.3.0", 14 | "multer": "^1.4.5-lts.1", 15 | "mysql": "^2.18.1", 16 | "nodemailer": "^6.9.1", 17 | "ua-parser-js": "^1.0.35", 18 | "yauzl": "^3.2.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/refererCheck.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | 3 | /** 4 | * 创建一个检查referer的中间件 5 | * @param {Array} whiteList - 允许的referer域名白名单 6 | * @param {Object} options - 配置选项 7 | * @returns {Function} Express/Node.js 中间件函数 8 | */ 9 | function refererCheck(whiteList = ['localhost'], options = {}) { 10 | // 标准化白名单URL 11 | const normalizedWhiteList = whiteList.map(domain => { 12 | // 处理localhost特殊情况 13 | if (domain === 'localhost') { 14 | return domain; 15 | } 16 | // 确保域名以https://或http://开头 17 | if (!domain.startsWith('http://') && !domain.startsWith('https://')) { 18 | domain = 'https://' + domain; 19 | } 20 | // 确保域名以/结尾 21 | if (!domain.endsWith('/')) { 22 | domain = domain + '/'; 23 | } 24 | return new URL(domain).origin; 25 | }); 26 | 27 | return (req, res, next) => { 28 | // 获取referer 29 | const referer = req.headers.referer || req.headers.referrer; 30 | 31 | // 如果没有referer 32 | if (!referer) { 33 | if (options.allowEmpty) { 34 | return next(); 35 | } 36 | return res.status(403).json({ 37 | error: 'Access Denied', 38 | message: 'No referer provided' 39 | }); 40 | } 41 | 42 | try { 43 | // 解析referer URL 44 | const refererUrl = new URL(referer); 45 | const refererOrigin = refererUrl.origin; 46 | 47 | // 检查是否为localhost 48 | if (refererUrl.hostname === 'localhost' && normalizedWhiteList.includes('localhost')) { 49 | return next(); 50 | } 51 | 52 | // 检查是否在白名单中 53 | if (normalizedWhiteList.some(domain => { 54 | if (domain === 'localhost') return false; 55 | return refererOrigin === domain; 56 | })) { 57 | return next(); 58 | } 59 | 60 | // 如果不在白名单中,返回403 61 | return res.status(403).json({ 62 | error: 'Access Denied', 63 | message: 'Invalid referer' 64 | }); 65 | 66 | } catch (error) { 67 | // 处理无效的URL 68 | return res.status(403).json({ 69 | error: 'Access Denied', 70 | message: 'Invalid referer format' 71 | }); 72 | } 73 | }; 74 | } 75 | 76 | module.exports = refererCheck; -------------------------------------------------------------------------------- /server/router.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const rabbit = require('./api/rabbit'); 5 | 6 | router.post('/api/rabbit/all', rabbit.all); 7 | router.post('/api/rabbit/add', rabbit.add); 8 | router.post('/api/rabbit/getClickCnt', rabbit.getClickCnt); 9 | router.post('/api/rabbit/getRankInfo', rabbit.getRankInfo); 10 | router.post('/api/rabbit/getClickData', rabbit.getClickData); 11 | 12 | const user = require('./api/user'); 13 | 14 | router.post('/api/user/login', user.login); 15 | router.post('/api/user/reg', user.reg); 16 | router.post('/api/user/logout', user.logout); 17 | router.post('/api/user/sendEmailVerifyCode', user.sendEmailVerifyCode); 18 | router.post('/api/user/setUserEmail', user.setUserEmail); 19 | router.post('/api/user/getUserInfo', user.getUserInfo); 20 | router.post('/api/user/getUserPublicInfo', user.getUserPublicInfo); 21 | router.post('/api/user/setUserMotto', user.setUserMotto); 22 | router.post('/api/user/listSessions', user.listSessions); 23 | router.post('/api/user/revokeSession', user.revokeSession); 24 | router.post('/api/user/updateUserPublicInfo', user.updateUserPublicInfo); 25 | router.post('/api/user/modifyPassword', user.modifyPassword); 26 | router.post('/api/user/listAudits', user.listAudits); 27 | 28 | const admin = require('./api/admin'); 29 | 30 | router.post('/api/admin/getUserInfoList', admin.getUserInfoList); 31 | router.post('/api/admin/setBlock', admin.setBlock); 32 | router.post('/api/admin/updateUserInfo', admin.updateUserInfo); 33 | router.post('/api/admin/addAnnouncement', admin.addAnnouncement); 34 | router.post('/api/admin/updateAnnouncement', admin.updateAnnouncement); 35 | 36 | const problem = require('./api/problem'); 37 | 38 | router.post('/api/problem/createProblem', problem.createProblem); 39 | router.post('/api/problem/getProblemList', problem.getProblemList); 40 | router.post('/api/problem/getProblemInfo', problem.getProblemInfo); 41 | router.post('/api/problem/updateProblem', problem.updateProblem); 42 | router.post('/api/problem/getProblemCasePreview', problem.getProblemCasePreview); 43 | router.post('/api/problem/clearCase', problem.clearCase); 44 | router.post('/api/problem/updateSubtaskId', problem.updateSubtaskId); 45 | router.post('/api/problem/getCase', problem.getCase); 46 | router.post('/api/problem/updateCase', problem.updateCase); 47 | router.get('/api/problem/downloadCase', problem.downloadCase); 48 | router.post('/api/problem/getProblemTags', problem.getProblemTags); 49 | router.post('/api/problem/getProblemPublishers', problem.getProblemPublishers); 50 | router.post('/api/problem/getProblemStat', problem.getProblemStat); 51 | router.post('/api/problem/getProblemFastestSubmission', problem.getProblemFastestSubmission); 52 | router.post('/api/problem/getProblemSol', problem.getProblemSol); 53 | router.post('/api/problem/bindPaste2Problem', problem.bindPaste2Problem); 54 | router.post('/api/problem/unbindSol', problem.unbindSol); 55 | router.post('/api/problem/getProblemAuth', problem.getProblemAuth); 56 | 57 | 58 | const fileUpload = require('./api/fileUpload'); 59 | router.post('/api/problem/uploadData', fileUpload.caseUpload.single('file'), fileUpload.handleCaseUpload); 60 | 61 | const judge = require('./api/judge'); 62 | 63 | router.post('/api/judge/submit', judge.submit); 64 | router.post('/api/judge/getSubmissionList', judge.getSubmissionList); 65 | router.post('/api/judge/getSubmissionInfo', judge.getSubmissionInfo); 66 | router.post('/api/judge/reJudge', judge.reJudge); 67 | router.post('/api/judge/reJudgeProblem', judge.reJudgeProblem); 68 | router.post('/api/judge/reJudgeContest', judge.reJudgeContest); 69 | router.post('/api/judge/cancelSubmission', judge.cancelSubmission); 70 | router.post('/api/judge/receiveTask', judge.receiveTask); 71 | router.post('/api/judge/getLangs', judge.getLangs); 72 | 73 | const common = require('./api/common'); 74 | 75 | router.post('/api/common/getAnnouncementList', common.getAnnouncementList); 76 | router.post('/api/common/getAnnouncementInfo', common.getAnnouncementInfo); 77 | router.post('/api/common/addPaste', common.addPaste); 78 | router.post('/api/common/getPaste', common.getPaste); 79 | router.post('/api/common/updatePaste', common.updatePaste); 80 | router.post('/api/common/delPaste', common.delPaste); 81 | router.post('/api/common/getPasteList', common.getPasteList); 82 | router.post('/api/common/getHitokoto', common.getHitokoto); 83 | 84 | const contest = require('./api/contest'); 85 | router.post('/api/contest/createContest', contest.createContest); 86 | router.post('/api/contest/getContestList', contest.getContestList); 87 | router.post('/api/contest/getContestInfo', contest.getContestInfo); 88 | router.post('/api/contest/updateContestInfo', contest.updateContestInfo); 89 | router.post('/api/contest/getPlayerList', contest.getPlayerList); 90 | router.post('/api/contest/addPlayer', contest.addPlayer); 91 | router.post('/api/contest/removePlayer', contest.removePlayer); 92 | router.post('/api/contest/contestReg', contest.contestReg); 93 | router.post('/api/contest/closeContest', contest.closeContest); 94 | router.post('/api/contest/updateProblemList', contest.updateProblemList); 95 | router.post('/api/contest/getProblemList', contest.getProblemList); 96 | router.post('/api/contest/getPlayerProblemList', contest.getPlayerProblemList); 97 | router.post('/api/contest/getProblemInfo', contest.getProblemInfo); 98 | router.post('/api/contest/submit', contest.submit); 99 | router.post('/api/contest/getSubmissionList', contest.getSubmissionList); 100 | router.post('/api/contest/getLastSubmissionList', contest.getLastSubmissionList); 101 | router.post('/api/contest/getSubmissionInfo', contest.getSubmissionInfo); 102 | router.post('/api/contest/getRank', contest.getRank); 103 | router.post('/api/contest/getSingleUserLastSubmission', contest.getSingleUserLastSubmission); 104 | router.post('/api/contest/getSingleUserProblemSubmission', contest.getSingleUserProblemSubmission); 105 | 106 | module.exports = router; 107 | -------------------------------------------------------------------------------- /server/static.js: -------------------------------------------------------------------------------- 1 | const db = require('./db/index'); 2 | 3 | const fill = (x) => { 4 | x = x.toString(); 5 | return x.length > 1 ? x : '0' + x; 6 | } 7 | 8 | exports.briefFormat = (now) => { 9 | return now.getFullYear() + '-' + fill(now.getMonth() + 1) + '-' + fill(now.getDate()); 10 | } 11 | 12 | exports.Format = (now) => { 13 | return now.getFullYear() + '-' + fill(now.getMonth() + 1) + '-' + fill(now.getDate()) + ' ' + fill(now.getHours()) + ':' + fill(now.getMinutes()) + ':' + fill(now.getSeconds()); 14 | } 15 | 16 | const IP2Region = require('ip2region').default; 17 | const query = new IP2Region(); 18 | exports.ip2loc = (ip) => { 19 | const res = query.search(ip); 20 | for (const i in res) { 21 | if (res[i] === "0") res[i] = ""; 22 | } 23 | if (res.country === "中国") res.country = ""; 24 | else if (res.country) res.country += " "; 25 | if (res.province === res.city || `${res.province}市` === res.city) res.province = ""; 26 | const cityResult = res.country + res.province + res.city; 27 | if (!cityResult) return 'unknown iploc'; 28 | if (res.isp && cityResult !== res.isp) return `${cityResult} ${res.isp}`; 29 | return cityResult; 30 | } 31 | 32 | exports.msFormat = (ms) => { 33 | let second = ms / 1000; 34 | if (second > 86400) // > 1 day 35 | return `${Math.floor(second / 86400)} 天前`; 36 | else if (second > 3600) 37 | return `${Math.floor(second / 3600)} 小时前`; 38 | else if (second > 60) 39 | return `${Math.floor(second / 60)} 分钟前`; 40 | else 41 | return `${Math.floor(second)} 秒前`; 42 | } 43 | 44 | exports.kbFormat = (memory) => { 45 | if (memory >= 1024) 46 | memory = Math.round(memory / 1024 * 100) / 100 + ' MB'; 47 | else memory += ' KB'; 48 | return memory; 49 | } 50 | 51 | exports.bFormat = (memory) => { 52 | if (memory >= 1024 * 1024) 53 | memory = Math.round(memory / 1024 / 1024 * 100) / 100 + ' MB'; 54 | else if (memory >= 1024) 55 | memory = Math.round(memory / 1024 * 100) / 100 + ' KB'; 56 | else memory += ' B'; 57 | return memory; 58 | } 59 | 60 | exports.eventList = [ 61 | 'user.login', 62 | 'user.loginFail.wrongPassword', 63 | 'user.loginFail.userBlocked', 64 | 'user.logout', 65 | 'user.updateProfile', 66 | 'auth.changePassword', 67 | 'auth.changeEmail', 68 | 'auth.revokeSession', 69 | 'auth.revokeAllSessions', 70 | 'auth.sendEmailVerifyCode', 71 | 'problem.delAllCases', 72 | 'problem.updateCase', 73 | 'problem.downloadCase', 74 | 'problem.updateConfig', 75 | ]; 76 | 77 | exports.eventExp = [ 78 | '用户登录', 79 | '登录失败 - 密码错误', 80 | '登录失败 - 用户封禁', 81 | '退出登录', 82 | '更新个人信息', 83 | '修改密码', 84 | '修改邮箱', 85 | '下线会话', 86 | '下线除当前对话外所有对话', 87 | '发送邮箱验证码', 88 | '删除所有测试数据', 89 | '修改测试数据', 90 | '下载测试数据', 91 | '修改题目配置', 92 | ] 93 | 94 | 95 | exports.recordEvent = (req, reason, detail, uid) => { 96 | const eventId = this.eventList.indexOf(reason); 97 | if (eventId === -1) { 98 | console.log('record Event error, unexpected reason: ' + reason); 99 | return; 100 | } 101 | db.query('INSERT INTO userAudit(uid,event,ip,iploc,time,browser,os,detail) values(?,?,?,?,?,?,?,?)', [ 102 | req.session.uid || uid, eventId, req.session.ip, this.ip2loc(req.session.ip), new Date(), 103 | `${req.useragent.browser.name} ${req.useragent.browser.version}`, `${req.useragent.os.name} ${req.useragent.os.version}`, detail ? JSON.stringify(detail, null, 2) : null 104 | ], (err, data) => { 105 | if (err) 106 | console.log(err); 107 | }); 108 | } 109 | 110 | exports.queryPromise = (sql, values) => { 111 | return new Promise((resolve, reject) => { 112 | db.query(sql, values, (error, results) => { 113 | if (error) reject(error); 114 | else resolve(results); 115 | }); 116 | }); 117 | } -------------------------------------------------------------------------------- /server/sync_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pid="" 3 | 4 | if [ "$1" != "" ]; then 5 | pid=$1"/" 6 | fi 7 | 8 | # 设置变量 9 | SOURCE_SERVER="root@110.42.109.94" 10 | SOURCE_DIR="/root/nywoj/server/data/"$pid 11 | LOCAL_DIR="/Users/ty/Desktop/nywOJ/server/data/"$pid 12 | BACKUP_DIR="/Users/ty/Desktop/nywOJ/server/data_backup" 13 | 14 | # 确保备份目录存在 15 | mkdir -p "$BACKUP_DIR" 16 | 17 | # 执行rsync 18 | rsync -avz --delete --backup --backup-dir="$BACKUP_DIR" "$SOURCE_SERVER:$SOURCE_DIR" "$LOCAL_DIR" 19 | 20 | # 输出同步完成信息 21 | echo -e "同步完成于 $(date)\n\n" 22 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /public/katex 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /web/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nywoj", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@element-plus/icons-vue": "^2.3.1", 12 | "@kangc/v-md-editor": "^2.3.15", 13 | "core-js": "^3.8.3", 14 | "echarts": "^5.4.1", 15 | "element-plus": "^2.7.6", 16 | "monaco-editor": "^0.38.0", 17 | "monaco-editor-webpack-plugin": "^7.0.1", 18 | "qs": "^6.11.1", 19 | "vue": "^3.2.13", 20 | "vue-axios": "^3.5.2", 21 | "vue-router": "^4.1.6", 22 | "vuedraggable": "^4.1.0", 23 | "vuex": "^4.1.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.12.16", 27 | "@babel/eslint-parser": "^7.12.16", 28 | "@vue/cli-plugin-babel": "~5.0.0", 29 | "@vue/cli-plugin-eslint": "~5.0.0", 30 | "@vue/cli-service": "~5.0.0", 31 | "eslint": "^7.32.0", 32 | "eslint-plugin-vue": "^8.0.3", 33 | "webpack-bundle-analyzer": "^4.10.1" 34 | }, 35 | "eslintConfig": { 36 | "root": true, 37 | "env": { 38 | "node": true 39 | }, 40 | "extends": [ 41 | "plugin:vue/vue3-essential", 42 | "eslint:recommended" 43 | ], 44 | "parserOptions": { 45 | "parser": "@babel/eslint-parser" 46 | }, 47 | "rules": {} 48 | }, 49 | "browserslist": [ 50 | "> 1%", 51 | "last 2 versions", 52 | "not dead", 53 | "not ie 11" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /web/public/default-avatar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baymin-ty/nywOJ/1f119b304d961a0a12001f40869a95f7f90ce414/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <%= htmlWebpackPlugin.options.title %> 16 | 17 | 18 | 19 | 20 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /web/src/App.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 42 | 43 | 148 | -------------------------------------------------------------------------------- /web/src/assets/common.js: -------------------------------------------------------------------------------- 1 | export const getNameColor = (gid, cnt) => { 2 | if (gid !== 1) 3 | return "#8e44ad"; 4 | else if (cnt < 1000) 5 | return "#606266"; 6 | else if (cnt < 10000) 7 | return "#00BFFF"; 8 | else if (cnt < 50000) 9 | return "#00FF00"; 10 | else if (cnt < 200000) 11 | return "#FF8C00"; 12 | else 13 | return "#FF0000"; 14 | } 15 | 16 | export const resColor = { 17 | 'Waiting': '#2b85e4', 18 | 'Pending': '#2b85e4', 19 | 'Rejudging': '#2b85e4', 20 | 'Compilation Error': '#9C27B0', 21 | 'Accepted': '#19be6b', 22 | 'Wrong Answer': '#E91E63', 23 | 'Time Limit Exceeded': '#ff9900', 24 | 'Memory Limit Exceeded': '#795548', 25 | 'Runtime Error': '#ed4014', 26 | 'Segmentation Fault': '#607D8B', 27 | 'Output Limit Exceeded': '#880e4f', 28 | 'Dangerous System Call': '#607D8B', 29 | 'System Error': '#607D8B', 30 | 'Canceled': '#606266', 31 | 'Skipped': '#606266' 32 | }; 33 | 34 | export const scoreColor = [ 35 | '#ff4f4f', 36 | '#ff694f', 37 | '#f8603a', 38 | '#fc8354', 39 | '#fa9231', 40 | '#f7bb3b', 41 | '#ecdb44', 42 | '#e2ec52', 43 | '#b0d628', 44 | '#93b127', 45 | '#25ad40', 46 | ] 47 | 48 | import axios from "axios"; 49 | import store from '@/sto/store'; 50 | 51 | export const refreshUserInfo = async () => { 52 | await axios.post('/api/user/getUserInfo').then(res => { 53 | if (res.status === 200) { 54 | store.state.uid = res.data.uid; 55 | store.state.name = res.data.name; 56 | store.state.gid = res.data.gid; 57 | store.state.ip = res.data.ip; 58 | store.state.avatar = res.data.avatar; 59 | store.state.preferenceLang = res.data.preferenceLang; 60 | } 61 | }); 62 | } -------------------------------------------------------------------------------- /web/src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baymin-ty/nywOJ/1f119b304d961a0a12001f40869a95f7f90ce414/web/src/assets/icon.png -------------------------------------------------------------------------------- /web/src/assets/longlong.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baymin-ty/nywOJ/1f119b304d961a0a12001f40869a95f7f90ce414/web/src/assets/longlong.jpg -------------------------------------------------------------------------------- /web/src/assets/nrabbit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baymin-ty/nywOJ/1f119b304d961a0a12001f40869a95f7f90ce414/web/src/assets/nrabbit.jpg -------------------------------------------------------------------------------- /web/src/assets/rabbit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baymin-ty/nywOJ/1f119b304d961a0a12001f40869a95f7f90ce414/web/src/assets/rabbit.jpg -------------------------------------------------------------------------------- /web/src/assets/serpent.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baymin-ty/nywOJ/1f119b304d961a0a12001f40869a95f7f90ce414/web/src/assets/serpent.jpg -------------------------------------------------------------------------------- /web/src/chart/myChart.js: -------------------------------------------------------------------------------- 1 | import * as echarts from 'echarts'; 2 | export default echarts; -------------------------------------------------------------------------------- /web/src/components/NotFoundPage.vue: -------------------------------------------------------------------------------- 1 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /web/src/components/admin/userManage.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /web/src/components/announcement/announcementEdit.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 71 | 72 | -------------------------------------------------------------------------------- /web/src/components/announcement/announcementView.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 49 | 50 | -------------------------------------------------------------------------------- /web/src/components/contest/components/contestProblemList.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /web/src/components/contest/components/contestRank.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /web/src/components/contest/components/contestSubmission.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /web/src/components/contest/components/problemManage.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /web/src/components/contest/contestList.vue: -------------------------------------------------------------------------------- 1 | 84 | 85 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /web/src/components/contest/contestPlayer.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /web/src/components/contest/contestProblem.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 168 | 169 | -------------------------------------------------------------------------------- /web/src/components/indexPage.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 108 | 109 | 123 | -------------------------------------------------------------------------------- /web/src/components/monacoEditor.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /web/src/components/myHeader.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 119 | 120 | -------------------------------------------------------------------------------- /web/src/components/paste/pasteEdit.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 72 | 73 | -------------------------------------------------------------------------------- /web/src/components/paste/pasteList.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /web/src/components/paste/pasteView.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 89 | 90 | -------------------------------------------------------------------------------- /web/src/components/problem/problemEdit.vue: -------------------------------------------------------------------------------- 1 | 97 | 98 | 232 | 233 | -------------------------------------------------------------------------------- /web/src/components/problem/problemView.vue: -------------------------------------------------------------------------------- 1 | 110 | 111 | 223 | 224 | -------------------------------------------------------------------------------- /web/src/components/rabbit/cuteRabbit.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 149 | 150 | 151 | 177 | -------------------------------------------------------------------------------- /web/src/components/rabbit/cuteRankList.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /web/src/components/rabbit/rabbitClickData.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /web/src/components/submission/caseDisplay.vue: -------------------------------------------------------------------------------- 1 | 110 | 111 | 137 | 138 | -------------------------------------------------------------------------------- /web/src/components/submission/submissionView.vue: -------------------------------------------------------------------------------- 1 | 132 | 133 | 234 | 235 | -------------------------------------------------------------------------------- /web/src/components/user/edit/userAudit.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 80 | 81 | -------------------------------------------------------------------------------- /web/src/components/user/edit/userEdit.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 81 | 82 | -------------------------------------------------------------------------------- /web/src/components/user/edit/userProfile.vue: -------------------------------------------------------------------------------- 1 | 42 | 82 | 83 | -------------------------------------------------------------------------------- /web/src/components/user/edit/userSecurity.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 127 | 128 | -------------------------------------------------------------------------------- /web/src/components/user/edit/userSession.vue: -------------------------------------------------------------------------------- 1 | 55 | 97 | 98 | -------------------------------------------------------------------------------- /web/src/components/user/userInfo.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 148 | 149 | -------------------------------------------------------------------------------- /web/src/components/user/userLogin.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 64 | 65 | -------------------------------------------------------------------------------- /web/src/components/user/userReg.vue: -------------------------------------------------------------------------------- 1 | 45 | 132 | 133 | -------------------------------------------------------------------------------- /web/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import ElementPlus from 'element-plus' 3 | import 'element-plus/dist/index.css' 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue' 5 | import App from './App.vue' 6 | // import axios from 'axios' 7 | // import VueAxios from 'vue-axios' 8 | import router from './router/router' 9 | import store from './sto/store' 10 | 11 | import VMdEditor from '@kangc/v-md-editor/lib/codemirror-editor'; 12 | import VMdPreview from '@kangc/v-md-editor/lib/preview'; 13 | import createCopyCodePlugin from '@kangc/v-md-editor/lib/plugins/copy-code/index'; 14 | import '@kangc/v-md-editor/lib/plugins/copy-code/copy-code.css'; 15 | import '@kangc/v-md-editor/lib/style/codemirror-editor.css'; 16 | import githubTheme from '@kangc/v-md-editor/lib/theme/github.js'; 17 | import '@kangc/v-md-editor/lib/theme/style/github.css'; 18 | 19 | // highlightjs 核心代码 20 | import hljs from 'highlight.js/lib/core'; 21 | // 按需引入语言包 22 | import cpp from 'highlight.js/lib/languages/cpp'; 23 | 24 | hljs.registerLanguage('cpp', cpp); 25 | 26 | // codemirror 编辑器的相关资源 27 | import Codemirror from 'codemirror'; 28 | 29 | // mode 30 | import 'codemirror/mode/markdown/markdown'; 31 | import 'codemirror/mode/javascript/javascript'; 32 | import 'codemirror/mode/css/css'; 33 | import 'codemirror/mode/htmlmixed/htmlmixed'; 34 | import 'codemirror/mode/vue/vue'; 35 | // edit 36 | import 'codemirror/addon/edit/closebrackets'; 37 | import 'codemirror/addon/edit/closetag'; 38 | import 'codemirror/addon/edit/matchbrackets'; 39 | // placeholder 40 | import 'codemirror/addon/display/placeholder'; 41 | // active-line 42 | import 'codemirror/addon/selection/active-line'; 43 | // scrollbar 44 | import 'codemirror/addon/scroll/simplescrollbars'; 45 | import 'codemirror/addon/scroll/simplescrollbars.css'; 46 | // style 47 | import 'codemirror/lib/codemirror.css'; 48 | 49 | // 解决根号渲染问题 50 | VMdEditor.xss.extend({ 51 | // 扩展白名单 52 | whiteList: { 53 | // preserveAspectRatio 注意要小写 54 | svg: ['preserveaspectratio'] 55 | } 56 | }); 57 | VMdPreview.xss.extend({ 58 | whiteList: { 59 | svg: ['preserveaspectratio'] 60 | } 61 | }); 62 | 63 | VMdEditor.Codemirror = Codemirror; 64 | VMdEditor.use(githubTheme, { 65 | Hljs: hljs, 66 | }); 67 | VMdPreview.use(githubTheme, { 68 | Hljs: hljs, 69 | }); 70 | 71 | import createKatexPlugin from '@kangc/v-md-editor/lib/plugins/katex/cdn'; 72 | VMdEditor.use(createKatexPlugin()).use(createCopyCodePlugin()); 73 | VMdPreview.use(createKatexPlugin()).use(createCopyCodePlugin()); 74 | 75 | const app = createApp(App) 76 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 77 | app.component(key, component) 78 | } 79 | 80 | import { ElMessage } from 'element-plus' 81 | app.config.globalProperties.$message = ElMessage 82 | 83 | app.use(ElementPlus).use(ElMessage).use(router).use(store).use(VMdEditor).use(VMdPreview).mount('#app'); 84 | -------------------------------------------------------------------------------- /web/src/router/router.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from "vue-router"; 2 | 3 | import indexPage from '@/components/indexPage.vue' 4 | import NotFound from '@/components/NotFoundPage.vue' 5 | 6 | import AnnouncementView from '@/components/announcement/announcementView.vue' 7 | import AnnouncementEdit from '@/components/announcement/announcementEdit.vue' 8 | import pasteView from "@/components/paste/pasteView.vue"; 9 | import pasteEdit from "@/components/paste/pasteEdit.vue"; 10 | import pasteList from "@/components/paste/pasteList.vue" 11 | 12 | import cuteRabbit from '@/components/rabbit/cuteRabbit.vue' 13 | import userLogin from "@/components/user/userLogin.vue" 14 | import userReg from "@/components/user/userReg.vue" 15 | import userInfo from '@/components/user/userInfo.vue' 16 | import problemList from '@/components/problem/problemList.vue' 17 | import problemView from '@/components/problem/problemView.vue' 18 | import problemEdit from '@/components/problem/problemEdit.vue' 19 | import problemStat from "@/components/problem/problemStat.vue"; 20 | import caseManage from '@/components/problem/caseManage.vue' 21 | import submissionList from '@/components/submission/submissionList.vue' 22 | import submissionView from '@/components/submission/submissionView.vue' 23 | import contestList from '@/components/contest/contestList.vue' 24 | import contestMain from '@/components/contest/contestMain.vue' 25 | import contestPlayer from '@/components/contest/contestPlayer.vue' 26 | import contestProblem from '@/components/contest/contestProblem.vue' 27 | import userEdit from '@/components/user/edit/userEdit.vue' 28 | 29 | import userManage from "@/components/admin/userManage" 30 | 31 | import { refreshUserInfo } from '@/assets/common' 32 | import store from '@/sto/store'; 33 | 34 | const per = []; 35 | 36 | per["/admin/usermanage"] = 3; 37 | 38 | const router = createRouter({ 39 | history: createWebHistory(), 40 | routes: [{ 41 | meta: { 42 | title: '可爱兔兔', 43 | activeTitle: '/rabbit' 44 | }, 45 | path: '/rabbit', component: cuteRabbit, 46 | }, { 47 | meta: { 48 | title: '用户登录', 49 | activeTitle: '/user/login' 50 | }, 51 | path: '/user/login', component: userLogin, 52 | }, { 53 | meta: { 54 | title: '用户注册', 55 | activeTitle: '/user/reg' 56 | }, 57 | path: '/user/reg', component: userReg, 58 | }, { 59 | meta: { 60 | title: '用户管理', 61 | activeTitle: '/user' 62 | }, 63 | path: '/admin/usermanage', component: userManage, 64 | }, { 65 | meta: { 66 | title: '用户信息', 67 | activeTitle: '/user' 68 | }, 69 | path: '/user/:uid', component: userInfo, 70 | }, { 71 | meta: { 72 | title: '题目列表', 73 | activeTitle: '/problem' 74 | }, 75 | path: '/problem', component: problemList, 76 | }, { 77 | meta: { 78 | title: '题目', 79 | activeTitle: '/problem' 80 | }, 81 | path: '/problem/:pid', component: problemView, 82 | }, { 83 | meta: { 84 | title: '首页', 85 | activeTitle: '/' 86 | }, 87 | path: '/', component: indexPage, 88 | }, { 89 | meta: { 90 | title: '题目管理', 91 | activeTitle: '/problem' 92 | }, 93 | path: '/problem/edit/:pid', component: problemEdit, 94 | }, { 95 | meta: { 96 | title: '提交记录', 97 | activeTitle: '/submission' 98 | }, 99 | path: '/submission', component: submissionList, 100 | }, { 101 | meta: { 102 | title: '提交记录详情', 103 | activeTitle: '/submission' 104 | }, 105 | path: '/submission/:sid', component: submissionView, 106 | }, { 107 | meta: { 108 | title: '404 Error', 109 | activeTitle: '/' 110 | }, 111 | path: '/:catchAll(.*)', 112 | name: '404', 113 | component: NotFound 114 | }, { 115 | meta: { 116 | title: '公告', 117 | activeTitle: '/' 118 | }, 119 | path: '/announcement/:aid', component: AnnouncementView, 120 | }, { 121 | meta: { 122 | title: '编辑公告', 123 | activeTitle: '/' 124 | }, 125 | path: '/announcement/edit/:aid', component: AnnouncementEdit, 126 | }, { 127 | meta: { 128 | title: '数据管理', 129 | activeTitle: '/problem' 130 | }, 131 | path: '/problem/case/:pid', component: caseManage, 132 | }, { 133 | meta: { 134 | title: '比赛列表', 135 | activeTitle: '/contest' 136 | }, 137 | path: '/contest', component: contestList, 138 | }, { 139 | meta: { 140 | title: '比赛', 141 | activeTitle: '/contest' 142 | }, 143 | path: '/contest/:cid', component: contestMain, 144 | }, { 145 | meta: { 146 | title: '选手列表', 147 | activeTitle: '/contest' 148 | }, 149 | path: '/contest/player/:cid', component: contestPlayer, 150 | }, { 151 | meta: { 152 | title: '比赛题目', 153 | activeTitle: '/contest' 154 | }, 155 | path: '/contest/:cid/problem/:idx', component: contestProblem, 156 | }, { 157 | meta: { 158 | title: '用户设置', 159 | activeTitle: '/user' 160 | }, 161 | path: '/user/edit', component: userEdit, 162 | }, { 163 | meta: { 164 | title: '查看剪贴板', 165 | activeTitle: '/user' 166 | }, 167 | path: '/paste/:mark', component: pasteView, 168 | }, { 169 | meta: { 170 | title: '编辑剪贴板', 171 | activeTitle: '/user' 172 | }, 173 | path: '/paste/edit/:mark', component: pasteEdit, 174 | }, { 175 | meta: { 176 | title: '剪贴板列表', 177 | activeTitle: '/user' 178 | }, 179 | path: '/paste', component: pasteList, 180 | }, { 181 | meta: { 182 | title: '数据统计', 183 | activeTitle: '/problem' 184 | }, 185 | path: '/problem/stat/:pid', component: problemStat, 186 | }], 187 | caseSensitive: true 188 | }); 189 | router.afterEach((to) => { 190 | store.state.activeTitle = to.meta.activeTitle; 191 | if (to.meta.title) { 192 | document.title = to.meta.title 193 | } 194 | }) 195 | router.beforeEach(async (to, from, next) => { 196 | if (window.location.hostname !== 'ty.szsyzx.cn' && 197 | window.location.hostname !== 'localhost' && 198 | window.location.hostname !== 'niyiwei.com' && 199 | window.location.hostname !== 'www.niyiwei.com') { 200 | window.location.href = 'https://ty.szsyzx.cn'; 201 | } 202 | if (!store.state.uid) { 203 | await refreshUserInfo(); 204 | } 205 | if (store.state.uid) { 206 | if (!per[to.path] || store.state.gid >= per[to.path]) 207 | next(); 208 | } 209 | else { 210 | if (to.path !== '/user/login') 211 | store.state.reDirectTo = { path: to.path, query: to.query }; 212 | if (to.path === '/' || 213 | to.path === '/user/reg' || 214 | to.path === '/user/login' || 215 | to.path === '/rabbit' || 216 | to.path === '/problem' || 217 | to.path === '/contest' || 218 | to.path === '/submission' || 219 | /^\/announcement\/\w+$/.test(to.path)) { 220 | next(); 221 | } else { 222 | next({ path: '/user/login' }); 223 | } 224 | } 225 | }) 226 | 227 | export default router; 228 | -------------------------------------------------------------------------------- /web/src/sto/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | export default createStore({ 4 | state: { 5 | uid: 0, 6 | name: "/", 7 | gid: 0, 8 | ip: '', 9 | path: '', 10 | avatar: '', 11 | reDirectTo: '/', 12 | langList: [], 13 | preferenceLang: null, 14 | }, 15 | mutations: {}, 16 | actions: {}, 17 | modules: {} 18 | }) 19 | -------------------------------------------------------------------------------- /web/vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@vue/cli-service') 2 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 3 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin') 4 | module.exports = defineConfig({ 5 | publicPath: process.env.NODE_ENV === 'production' 6 | ? 'https://cdn.ty.szsyzx.cn/static/' 7 | : '/', 8 | transpileDependencies: true, 9 | productionSourceMap: false, 10 | devServer: { 11 | proxy: { 12 | '/api': { 13 | target: "http://127.0.0.1:1234", 14 | ws: true, 15 | changeOrigin: true 16 | } 17 | } 18 | }, 19 | chainWebpack(config) { 20 | if (process.env.NODE_ENV === 'production') { 21 | config.optimization.splitChunks({ 22 | cacheGroups: { 23 | monacoEditor: { 24 | test: /[\\/]node_modules[\\/]monaco-editor[\\/]/, 25 | name: 'monaco-editor', 26 | chunks: 'all', 27 | priority: 20, 28 | }, 29 | common: { 30 | name: 'ty', 31 | chunks: 'all', 32 | }, 33 | } 34 | }) 35 | } 36 | }, 37 | configureWebpack: { 38 | plugins: [ 39 | // new BundleAnalyzerPlugin(), 40 | new MonacoWebpackPlugin({ languages: ['cpp'] }) 41 | ] 42 | } 43 | }); --------------------------------------------------------------------------------