Repository: simplefanC/voj Branch: main Commit: ebd50f61798b Files: 683 Total size: 1.7 MB Directory structure: gitextract_uh972pfn/ ├── .gitignore ├── LICENSE ├── README.md ├── docs/ │ ├── package.json │ └── src/ │ ├── .vuepress/ │ │ ├── config.ts │ │ ├── navbar.ts │ │ ├── sidebar.ts │ │ ├── styles/ │ │ │ ├── index.scss │ │ │ └── palette.scss │ │ └── theme.ts │ ├── README.md │ ├── deploy/ │ │ ├── README.md │ │ ├── docker.md │ │ ├── how-to-backup.md │ │ ├── multi-judgeserver.md │ │ ├── open-https.md │ │ └── update.md │ ├── develop/ │ │ ├── db.md │ │ ├── judge_dispatcher.md │ │ ├── sandbox.md │ │ └── update-fe.md │ ├── introduction/ │ │ ├── README.md │ │ └── architecture.md │ ├── monomer/ │ │ ├── backend.md │ │ ├── frontend.md │ │ ├── judgeserver.md │ │ ├── mysql-checker.md │ │ ├── mysql.md │ │ ├── nacos.md │ │ ├── redis.md │ │ └── rsync.md │ └── use/ │ ├── admin-user.md │ ├── close-free-cdn.md │ ├── contest.md │ ├── custom-difficulty.md │ ├── discussion-admin.md │ ├── import-problem.md │ ├── import-user.md │ ├── judge-mode.md │ ├── notice-announcement.md │ ├── testcase.md │ └── training.md ├── pom.xml ├── voj-backend/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ ├── alibaba/ │ │ │ │ └── druid/ │ │ │ │ └── pool/ │ │ │ │ └── DruidAbstractDataSource.java │ │ │ └── simplefanc/ │ │ │ └── voj/ │ │ │ └── backend/ │ │ │ ├── BackendApplication.java │ │ │ ├── cache/ │ │ │ │ ├── CacheTypeManager.java │ │ │ │ ├── DoubleCache.java │ │ │ │ └── DoubleCacheManager.java │ │ │ ├── common/ │ │ │ │ ├── annotation/ │ │ │ │ │ └── Access.java │ │ │ │ ├── constants/ │ │ │ │ │ ├── AccessEnum.java │ │ │ │ │ ├── CallJudgerType.java │ │ │ │ │ ├── Constant.java │ │ │ │ │ ├── EmailConstant.java │ │ │ │ │ ├── FileConstant.java │ │ │ │ │ ├── FileTypeEnum.java │ │ │ │ │ ├── QueueConstant.java │ │ │ │ │ ├── RoleEnum.java │ │ │ │ │ ├── ScheduleConstant.java │ │ │ │ │ ├── TrainingEnum.java │ │ │ │ │ └── UserStatusEnum.java │ │ │ │ ├── exception/ │ │ │ │ │ ├── StatusAccessDeniedException.java │ │ │ │ │ ├── StatusFailException.java │ │ │ │ │ ├── StatusForbiddenException.java │ │ │ │ │ ├── StatusNotFoundException.java │ │ │ │ │ ├── StatusSystemErrorException.java │ │ │ │ │ └── advice/ │ │ │ │ │ └── GlobalExceptionAdvice.java │ │ │ │ └── utils/ │ │ │ │ ├── ConfigUtil.java │ │ │ │ ├── ExcelUtil.java │ │ │ │ ├── JwtUtil.java │ │ │ │ ├── MyFileUtil.java │ │ │ │ ├── RedisUtil.java │ │ │ │ └── RestTemplateUtil.java │ │ │ ├── config/ │ │ │ │ ├── CacheConfig.java │ │ │ │ ├── CommonAsyncTaskConfig.java │ │ │ │ ├── ConfigVO.java │ │ │ │ ├── CorsConfig.java │ │ │ │ ├── DruidConfiguration.java │ │ │ │ ├── JudgeAsyncTaskConfig.java │ │ │ │ ├── MyMetaObjectConfig.java │ │ │ │ ├── MybatisPlusConfig.java │ │ │ │ ├── NacosConfig.java │ │ │ │ ├── RedisConfig.java │ │ │ │ ├── RestTemplateConfig.java │ │ │ │ ├── ShiroConfig.java │ │ │ │ ├── StartupRunner.java │ │ │ │ ├── SwaggerConfig.java │ │ │ │ └── property/ │ │ │ │ ├── DoubleCacheProperties.java │ │ │ │ ├── EmailRuleBO.java │ │ │ │ ├── FilePathProperties.java │ │ │ │ └── RemoteAccountProperties.java │ │ │ ├── controller/ │ │ │ │ ├── admin/ │ │ │ │ │ ├── AdminContestController.java │ │ │ │ │ ├── AdminDiscussionController.java │ │ │ │ │ ├── AdminJudgeController.java │ │ │ │ │ ├── AdminProblemController.java │ │ │ │ │ ├── AdminTagController.java │ │ │ │ │ ├── AdminTrainingCategoryController.java │ │ │ │ │ ├── AdminTrainingController.java │ │ │ │ │ ├── AdminUserController.java │ │ │ │ │ ├── AnnouncementController.java │ │ │ │ │ ├── ConfigController.java │ │ │ │ │ ├── DashboardController.java │ │ │ │ │ └── SwitchController.java │ │ │ │ ├── file/ │ │ │ │ │ ├── ContestFileController.java │ │ │ │ │ ├── ImageController.java │ │ │ │ │ ├── ImportFpsProblemController.java │ │ │ │ │ ├── ImportLOJProblemController.java │ │ │ │ │ ├── ImportQDUOJProblemController.java │ │ │ │ │ ├── MarkDownFileController.java │ │ │ │ │ ├── ProblemFileController.java │ │ │ │ │ ├── TestCaseController.java │ │ │ │ │ └── UserFileController.java │ │ │ │ ├── msg/ │ │ │ │ │ ├── AdminNoticeController.java │ │ │ │ │ ├── NoticeController.java │ │ │ │ │ └── UserMessageController.java │ │ │ │ └── oj/ │ │ │ │ ├── AccountController.java │ │ │ │ ├── CommentController.java │ │ │ │ ├── CommonController.java │ │ │ │ ├── ContestAdminController.java │ │ │ │ ├── ContestController.java │ │ │ │ ├── ContestScoreboardController.java │ │ │ │ ├── DiscussionController.java │ │ │ │ ├── HomeController.java │ │ │ │ ├── JudgeController.java │ │ │ │ ├── PassportController.java │ │ │ │ ├── ProblemController.java │ │ │ │ ├── RankController.java │ │ │ │ └── TrainingController.java │ │ │ ├── dao/ │ │ │ │ ├── common/ │ │ │ │ │ ├── AnnouncementEntityService.java │ │ │ │ │ ├── FileEntityService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── AnnouncementEntityServiceImpl.java │ │ │ │ │ └── FileEntityEntityServiceImpl.java │ │ │ │ ├── contest/ │ │ │ │ │ ├── ContestAnnouncementEntityService.java │ │ │ │ │ ├── ContestEntityService.java │ │ │ │ │ ├── ContestPrintEntityService.java │ │ │ │ │ ├── ContestProblemEntityService.java │ │ │ │ │ ├── ContestRecordEntityService.java │ │ │ │ │ ├── ContestRegisterEntityService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── ContestAnnouncementEntityServiceImpl.java │ │ │ │ │ ├── ContestEntityServiceImpl.java │ │ │ │ │ ├── ContestPrintEntityServiceImpl.java │ │ │ │ │ ├── ContestProblemEntityServiceImpl.java │ │ │ │ │ ├── ContestRecordEntityServiceImpl.java │ │ │ │ │ └── ContestRegisterEntityServiceImpl.java │ │ │ │ ├── discussion/ │ │ │ │ │ ├── CommentEntityService.java │ │ │ │ │ ├── CommentLikeEntityService.java │ │ │ │ │ ├── DiscussionEntityService.java │ │ │ │ │ ├── DiscussionLikeEntityService.java │ │ │ │ │ ├── DiscussionReportEntityService.java │ │ │ │ │ ├── ReplyEntityService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── CommentEntityServiceImpl.java │ │ │ │ │ ├── CommentLikeEntityServiceImpl.java │ │ │ │ │ ├── DiscussionEntityServiceImpl.java │ │ │ │ │ ├── DiscussionLikeEntityServiceImpl.java │ │ │ │ │ ├── DiscussionReportEntityServiceImpl.java │ │ │ │ │ └── ReplyEntityServiceImpl.java │ │ │ │ ├── judge/ │ │ │ │ │ ├── JudgeCaseEntityService.java │ │ │ │ │ ├── JudgeEntityService.java │ │ │ │ │ ├── JudgeServerEntityService.java │ │ │ │ │ ├── RemoteJudgeAccountEntityService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── JudgeCaseEntityServiceImpl.java │ │ │ │ │ ├── JudgeEntityServiceImpl.java │ │ │ │ │ ├── JudgeServerEntityServiceImpl.java │ │ │ │ │ └── RemoteJudgeAccountEntityServiceImpl.java │ │ │ │ ├── msg/ │ │ │ │ │ ├── AdminSysNoticeEntityService.java │ │ │ │ │ ├── MsgRemindEntityService.java │ │ │ │ │ ├── UserSysNoticeEntityService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── AdminSysNoticeEntityServiceImpl.java │ │ │ │ │ ├── MsgRemindEntityServiceImpl.java │ │ │ │ │ └── UserSysNoticeEntityServiceImpl.java │ │ │ │ ├── problem/ │ │ │ │ │ ├── CategoryEntityService.java │ │ │ │ │ ├── CodeTemplateEntityService.java │ │ │ │ │ ├── LanguageEntityService.java │ │ │ │ │ ├── ProblemCaseEntityService.java │ │ │ │ │ ├── ProblemEntityService.java │ │ │ │ │ ├── ProblemLanguageEntityService.java │ │ │ │ │ ├── ProblemTagEntityService.java │ │ │ │ │ ├── TagClassificationEntityService.java │ │ │ │ │ ├── TagEntityService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── CategoryEntityServiceImpl.java │ │ │ │ │ ├── CodeTemplateEntityServiceImpl.java │ │ │ │ │ ├── LanguageEntityServiceImpl.java │ │ │ │ │ ├── ProblemCaseEntityServiceImpl.java │ │ │ │ │ ├── ProblemEntityServiceImpl.java │ │ │ │ │ ├── ProblemLanguageEntityServiceImpl.java │ │ │ │ │ ├── ProblemTagEntityServiceImpl.java │ │ │ │ │ ├── TagClassificationEntityServiceImpl.java │ │ │ │ │ └── TagEntityServiceImpl.java │ │ │ │ ├── training/ │ │ │ │ │ ├── MappingTrainingCategoryEntityService.java │ │ │ │ │ ├── TrainingCategoryEntityService.java │ │ │ │ │ ├── TrainingEntityService.java │ │ │ │ │ ├── TrainingProblemEntityService.java │ │ │ │ │ ├── TrainingRecordEntityService.java │ │ │ │ │ ├── TrainingRegisterEntityService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── MappingTrainingCategoryEntityServiceImpl.java │ │ │ │ │ ├── TrainingCategoryEntityServiceImpl.java │ │ │ │ │ ├── TrainingEntityServiceImpl.java │ │ │ │ │ ├── TrainingProblemEntityServiceImpl.java │ │ │ │ │ ├── TrainingRecordEntityServiceImpl.java │ │ │ │ │ └── TrainingRegisterEntityServiceImpl.java │ │ │ │ └── user/ │ │ │ │ ├── AuthEntityService.java │ │ │ │ ├── RoleAuthEntityService.java │ │ │ │ ├── RoleEntityService.java │ │ │ │ ├── SessionEntityService.java │ │ │ │ ├── UserAcproblemEntityService.java │ │ │ │ ├── UserInfoEntityService.java │ │ │ │ ├── UserRoleEntityService.java │ │ │ │ └── impl/ │ │ │ │ ├── AuthEntityServiceImpl.java │ │ │ │ ├── RoleAuthEntityServiceImpl.java │ │ │ │ ├── RoleEntityServiceImpl.java │ │ │ │ ├── SessionEntityServiceImpl.java │ │ │ │ ├── UserAcproblemEntityServiceImpl.java │ │ │ │ ├── UserInfoEntityServiceImpl.java │ │ │ │ └── UserRoleEntityServiceImpl.java │ │ │ ├── judge/ │ │ │ │ ├── AbstractTaskReceiver.java │ │ │ │ ├── ChooseUtils.java │ │ │ │ ├── Dispatcher.java │ │ │ │ ├── local/ │ │ │ │ │ ├── JudgeTaskDispatcher.java │ │ │ │ │ └── JudgeTaskTaskReceiver.java │ │ │ │ └── remote/ │ │ │ │ ├── RemoteJudgeTaskDispatcher.java │ │ │ │ ├── RemoteJudgeTaskReceiver.java │ │ │ │ └── crawler/ │ │ │ │ ├── AbstractCFStyleProblemCrawler.java │ │ │ │ ├── AbstractProblemCrawler.java │ │ │ │ ├── AtCoderProblemCrawler.java │ │ │ │ ├── CFProblemCrawler.java │ │ │ │ ├── CrawlersHolder.java │ │ │ │ ├── GYMProblemCrawler.java │ │ │ │ ├── HDUProblemCrawler.java │ │ │ │ ├── JSKProblemCrawler.java │ │ │ │ ├── MXTProblemCrawler.java │ │ │ │ ├── POJProblemCrawler.java │ │ │ │ ├── TKOJProblemCrawler.java │ │ │ │ └── YACSProblemCrawler.java │ │ │ ├── mapper/ │ │ │ │ ├── AdminSysNoticeMapper.java │ │ │ │ ├── AnnouncementMapper.java │ │ │ │ ├── AuthMapper.java │ │ │ │ ├── CategoryMapper.java │ │ │ │ ├── CodeTemplateMapper.java │ │ │ │ ├── CommentLikeMapper.java │ │ │ │ ├── CommentMapper.java │ │ │ │ ├── ContestAnnouncementMapper.java │ │ │ │ ├── ContestMapper.java │ │ │ │ ├── ContestPrintMapper.java │ │ │ │ ├── ContestProblemMapper.java │ │ │ │ ├── ContestRecordMapper.java │ │ │ │ ├── ContestRegisterMapper.java │ │ │ │ ├── DiscussionLikeMapper.java │ │ │ │ ├── DiscussionMapper.java │ │ │ │ ├── DiscussionReportMapper.java │ │ │ │ ├── FileMapper.java │ │ │ │ ├── JudgeCaseMapper.java │ │ │ │ ├── JudgeMapper.java │ │ │ │ ├── JudgeServerMapper.java │ │ │ │ ├── LanguageMapper.java │ │ │ │ ├── MappingTrainingCategoryMapper.java │ │ │ │ ├── MsgRemindMapper.java │ │ │ │ ├── ProblemCaseMapper.java │ │ │ │ ├── ProblemLanguageMapper.java │ │ │ │ ├── ProblemMapper.java │ │ │ │ ├── ProblemTagMapper.java │ │ │ │ ├── RemoteJudgeAccountMapper.java │ │ │ │ ├── ReplyMapper.java │ │ │ │ ├── RoleAuthMapper.java │ │ │ │ ├── RoleMapper.java │ │ │ │ ├── SessionMapper.java │ │ │ │ ├── TagClassificationMapper.java │ │ │ │ ├── TagMapper.java │ │ │ │ ├── TrainingCategoryMapper.java │ │ │ │ ├── TrainingMapper.java │ │ │ │ ├── TrainingProblemMapper.java │ │ │ │ ├── TrainingRecordMapper.java │ │ │ │ ├── TrainingRegisterMapper.java │ │ │ │ ├── UserAcproblemMapper.java │ │ │ │ ├── UserInfoMapper.java │ │ │ │ ├── UserRecordMapper.java │ │ │ │ ├── UserRoleMapper.java │ │ │ │ ├── UserSysNoticeMapper.java │ │ │ │ └── xml/ │ │ │ │ ├── AdminSysNoticeMapper.xml │ │ │ │ ├── AnnouncementMapper.xml │ │ │ │ ├── CommentMapper.xml │ │ │ │ ├── ContestExplanationMapper.xml │ │ │ │ ├── ContestMapper.xml │ │ │ │ ├── ContestProblemMapper.xml │ │ │ │ ├── ContestRecordMapper.xml │ │ │ │ ├── ContestRegisterMapper.xml │ │ │ │ ├── ContestScoreMapper.xml │ │ │ │ ├── DiscussionMapper.xml │ │ │ │ ├── JudgeMapper.xml │ │ │ │ ├── MsgRemindMapper.xml │ │ │ │ ├── ProblemMapper.xml │ │ │ │ ├── RoleAuthMapper.xml │ │ │ │ ├── RoleMapper.xml │ │ │ │ ├── SessionMapper.xml │ │ │ │ ├── TagMapper.xml │ │ │ │ ├── TrainingCategoryMapper.xml │ │ │ │ ├── TrainingMapper.xml │ │ │ │ ├── TrainingProblemMapper.xml │ │ │ │ ├── TrainingRecordMapper.xml │ │ │ │ ├── UserAcproblemMapper.xml │ │ │ │ ├── UserInfoMapper.xml │ │ │ │ ├── UserRecordMapper.xml │ │ │ │ ├── UserRoleMapper.xml │ │ │ │ └── UserSysNoticeMapper.xml │ │ │ ├── pojo/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── AdminEditUserDTO.java │ │ │ │ │ ├── AnnouncementDTO.java │ │ │ │ │ ├── ApplyResetPasswordDTO.java │ │ │ │ │ ├── ChangeEmailDTO.java │ │ │ │ │ ├── ChangePasswordDTO.java │ │ │ │ │ ├── CheckAcDTO.java │ │ │ │ │ ├── CheckUsernameOrEmailDTO.java │ │ │ │ │ ├── ContestPrintDTO.java │ │ │ │ │ ├── ContestProblemDTO.java │ │ │ │ │ ├── ContestRankDTO.java │ │ │ │ │ ├── DbAndRedisConfigDTO.java │ │ │ │ │ ├── EmailConfigDTO.java │ │ │ │ │ ├── LoginDTO.java │ │ │ │ │ ├── PidListDTO.java │ │ │ │ │ ├── ProblemDTO.java │ │ │ │ │ ├── QDOJProblemDTO.java │ │ │ │ │ ├── RegisterContestDTO.java │ │ │ │ │ ├── RegisterDTO.java │ │ │ │ │ ├── RegisterTrainingDTO.java │ │ │ │ │ ├── ReplyDTO.java │ │ │ │ │ ├── ResetPasswordDTO.java │ │ │ │ │ ├── SubmitIdListDTO.java │ │ │ │ │ ├── SwitchConfigDTO.java │ │ │ │ │ ├── TestEmailDTO.java │ │ │ │ │ ├── ToJudgeDTO.java │ │ │ │ │ ├── TrainingDTO.java │ │ │ │ │ ├── TrainingProblemDTO.java │ │ │ │ │ ├── UserReadContestAnnouncementDTO.java │ │ │ │ │ └── WebConfigDTO.java │ │ │ │ └── vo/ │ │ │ │ ├── ACMContestRankVO.java │ │ │ │ ├── ACMRankVO.java │ │ │ │ ├── AccessVO.java │ │ │ │ ├── AdminContestVO.java │ │ │ │ ├── AdminSysNoticeVO.java │ │ │ │ ├── AnnouncementVO.java │ │ │ │ ├── CaptchaVO.java │ │ │ │ ├── ChangeAccountVO.java │ │ │ │ ├── CheckUsernameOrEmailVO.java │ │ │ │ ├── CommentListVO.java │ │ │ │ ├── CommentVO.java │ │ │ │ ├── ContestOutsideInfo.java │ │ │ │ ├── ContestProblemVO.java │ │ │ │ ├── ContestRecordVO.java │ │ │ │ ├── ContestRegisterCountVO.java │ │ │ │ ├── ContestVO.java │ │ │ │ ├── DiscussionVO.java │ │ │ │ ├── ExcelIpVO.java │ │ │ │ ├── ExcelUserVO.java │ │ │ │ ├── ImportProblemVO.java │ │ │ │ ├── JudgeVO.java │ │ │ │ ├── OIContestRankVO.java │ │ │ │ ├── OIRankVO.java │ │ │ │ ├── ProblemCountVO.java │ │ │ │ ├── ProblemInfoVO.java │ │ │ │ ├── ProblemTagVO.java │ │ │ │ ├── ProblemVO.java │ │ │ │ ├── RandomProblemVO.java │ │ │ │ ├── RegisterCodeVO.java │ │ │ │ ├── RoleAuthsVO.java │ │ │ │ ├── SubmissionInfoVO.java │ │ │ │ ├── SysMsgVO.java │ │ │ │ ├── TrainingRankVO.java │ │ │ │ ├── TrainingRecordVO.java │ │ │ │ ├── TrainingVO.java │ │ │ │ ├── UserHomeVO.java │ │ │ │ ├── UserInfoVO.java │ │ │ │ ├── UserMsgVO.java │ │ │ │ ├── UserRolesVO.java │ │ │ │ └── UserUnreadMsgCountVO.java │ │ │ ├── service/ │ │ │ │ ├── account/ │ │ │ │ │ ├── PassportService.java │ │ │ │ │ └── impl/ │ │ │ │ │ └── PassportServiceImpl.java │ │ │ │ ├── admin/ │ │ │ │ │ ├── announcement/ │ │ │ │ │ │ ├── AdminAnnouncementService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ └── AdminAnnouncementServiceImpl.java │ │ │ │ │ ├── contest/ │ │ │ │ │ │ ├── AdminContestAnnouncementService.java │ │ │ │ │ │ ├── AdminContestProblemService.java │ │ │ │ │ │ ├── AdminContestService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── AdminContestAnnouncementServiceImpl.java │ │ │ │ │ │ ├── AdminContestProblemServiceImpl.java │ │ │ │ │ │ └── AdminContestServiceImpl.java │ │ │ │ │ ├── discussion/ │ │ │ │ │ │ ├── AdminDiscussionService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ └── AdminDiscussionServiceImpl.java │ │ │ │ │ ├── problem/ │ │ │ │ │ │ ├── AdminProblemService.java │ │ │ │ │ │ ├── RemoteProblemService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ └── AdminProblemServiceImpl.java │ │ │ │ │ ├── rejudge/ │ │ │ │ │ │ ├── RejudgeService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ └── RejudgeServiceImpl.java │ │ │ │ │ ├── system/ │ │ │ │ │ │ ├── ConfigService.java │ │ │ │ │ │ ├── DashboardService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── ConfigServiceImpl.java │ │ │ │ │ │ └── DashboardServiceImpl.java │ │ │ │ │ ├── tag/ │ │ │ │ │ │ ├── AdminTagService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ └── AdminTagServiceImpl.java │ │ │ │ │ ├── training/ │ │ │ │ │ │ ├── AdminTrainingCategoryService.java │ │ │ │ │ │ ├── AdminTrainingProblemService.java │ │ │ │ │ │ ├── AdminTrainingRecordService.java │ │ │ │ │ │ ├── AdminTrainingService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── AdminTrainingCategoryServiceImpl.java │ │ │ │ │ │ ├── AdminTrainingProblemServiceImpl.java │ │ │ │ │ │ └── AdminTrainingServiceImpl.java │ │ │ │ │ └── user/ │ │ │ │ │ ├── AdminUserService.java │ │ │ │ │ ├── UserRecordService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── AdminUserServiceImpl.java │ │ │ │ │ └── UserRecordServiceImpl.java │ │ │ │ ├── email/ │ │ │ │ │ └── EmailService.java │ │ │ │ ├── file/ │ │ │ │ │ ├── ContestFileService.java │ │ │ │ │ ├── ImageService.java │ │ │ │ │ ├── ImportDSOJProblemService.java │ │ │ │ │ ├── ImportFpsProblemService.java │ │ │ │ │ ├── ImportLOJProblemService.java │ │ │ │ │ ├── ImportQDUOJProblemService.java │ │ │ │ │ ├── MarkDownFileService.java │ │ │ │ │ ├── ProblemFileService.java │ │ │ │ │ ├── TestCaseService.java │ │ │ │ │ ├── UserFileService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── ContestFileServiceImpl.java │ │ │ │ │ ├── ImageServiceImpl.java │ │ │ │ │ ├── ImportDSOJProblemServiceImpl.java │ │ │ │ │ ├── ImportFpsProblemServiceImpl.java │ │ │ │ │ ├── ImportLOJProblemServiceImpl.java │ │ │ │ │ ├── ImportQDUOJProblemServiceImpl.java │ │ │ │ │ ├── MarkDownFileServiceImpl.java │ │ │ │ │ ├── ProblemFileServiceImpl.java │ │ │ │ │ ├── TestCaseServiceImpl.java │ │ │ │ │ └── UserFileServiceImpl.java │ │ │ │ ├── msg/ │ │ │ │ │ ├── AdminNoticeService.java │ │ │ │ │ ├── NoticeService.java │ │ │ │ │ ├── UserMessageService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── AdminNoticeServiceImpl.java │ │ │ │ │ ├── NoticeServiceImpl.java │ │ │ │ │ └── UserMessageServiceImpl.java │ │ │ │ ├── oj/ │ │ │ │ │ ├── AccountService.java │ │ │ │ │ ├── BeforeDispatchInitService.java │ │ │ │ │ ├── CommentService.java │ │ │ │ │ ├── CommonService.java │ │ │ │ │ ├── ContestACMRankService.java │ │ │ │ │ ├── ContestAdminService.java │ │ │ │ │ ├── ContestOIRankService.java │ │ │ │ │ ├── ContestScoreboardService.java │ │ │ │ │ ├── ContestService.java │ │ │ │ │ ├── DiscussionService.java │ │ │ │ │ ├── HomeService.java │ │ │ │ │ ├── JudgeService.java │ │ │ │ │ ├── ProblemService.java │ │ │ │ │ ├── RankService.java │ │ │ │ │ ├── TrainingService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── AccountServiceImpl.java │ │ │ │ │ ├── CommentServiceImpl.java │ │ │ │ │ ├── CommonServiceImpl.java │ │ │ │ │ ├── ContestAdminServiceImpl.java │ │ │ │ │ ├── ContestScoreboardServiceImpl.java │ │ │ │ │ ├── ContestServiceImpl.java │ │ │ │ │ ├── DiscussionServiceImpl.java │ │ │ │ │ ├── HomeServiceImpl.java │ │ │ │ │ ├── JudgeServiceImpl.java │ │ │ │ │ ├── ProblemServiceImpl.java │ │ │ │ │ ├── RankServiceImpl.java │ │ │ │ │ └── TrainingServiceImpl.java │ │ │ │ └── schedule/ │ │ │ │ └── ScheduleService.java │ │ │ ├── shiro/ │ │ │ │ ├── AccountProfile.java │ │ │ │ ├── AccountRealm.java │ │ │ │ ├── JwtFilter.java │ │ │ │ ├── JwtToken.java │ │ │ │ └── UserSessionUtil.java │ │ │ └── validator/ │ │ │ ├── AccessInterceptor.java │ │ │ ├── AccessValidator.java │ │ │ ├── ContestValidator.java │ │ │ ├── JudgeValidator.java │ │ │ └── TrainingValidator.java │ │ └── resources/ │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── email-rule.yml │ │ ├── logback-spring.xml │ │ └── templates/ │ │ ├── emailTemplate_registerCode.html │ │ ├── emailTemplate_resetPassword.html │ │ └── emailTemplate_testEmail.html │ └── test/ │ └── java/ │ └── com/ │ └── simplefanc/ │ └── AppTest.java ├── voj-common/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── simplefanc/ │ └── voj/ │ └── common/ │ ├── constants/ │ │ ├── Constant.java │ │ ├── ContestConstant.java │ │ ├── ContestEnum.java │ │ ├── JudgeCaseMode.java │ │ ├── JudgeMode.java │ │ ├── JudgeStatus.java │ │ ├── ProblemEnum.java │ │ ├── ProblemLevelEnum.java │ │ ├── RedisConstant.java │ │ └── RemoteOj.java │ ├── pojo/ │ │ ├── dto/ │ │ │ ├── CompileDTO.java │ │ │ └── JudgeDTO.java │ │ └── entity/ │ │ ├── common/ │ │ │ ├── Announcement.java │ │ │ └── File.java │ │ ├── contest/ │ │ │ ├── Contest.java │ │ │ ├── ContestAnnouncement.java │ │ │ ├── ContestPrint.java │ │ │ ├── ContestProblem.java │ │ │ ├── ContestRecord.java │ │ │ └── ContestRegister.java │ │ ├── discussion/ │ │ │ ├── Comment.java │ │ │ ├── CommentLike.java │ │ │ ├── Discussion.java │ │ │ ├── DiscussionLike.java │ │ │ ├── DiscussionReport.java │ │ │ └── Reply.java │ │ ├── judge/ │ │ │ ├── Judge.java │ │ │ ├── JudgeCase.java │ │ │ ├── JudgeServer.java │ │ │ └── RemoteJudgeAccount.java │ │ ├── msg/ │ │ │ ├── AdminSysNotice.java │ │ │ ├── MsgRemind.java │ │ │ └── UserSysNotice.java │ │ ├── problem/ │ │ │ ├── Category.java │ │ │ ├── CodeTemplate.java │ │ │ ├── Language.java │ │ │ ├── Problem.java │ │ │ ├── ProblemCase.java │ │ │ ├── ProblemLanguage.java │ │ │ ├── ProblemTag.java │ │ │ ├── Tag.java │ │ │ └── TagClassification.java │ │ ├── training/ │ │ │ ├── MappingTrainingCategory.java │ │ │ ├── Training.java │ │ │ ├── TrainingCategory.java │ │ │ ├── TrainingProblem.java │ │ │ ├── TrainingRecord.java │ │ │ └── TrainingRegister.java │ │ └── user/ │ │ ├── Auth.java │ │ ├── Role.java │ │ ├── RoleAuth.java │ │ ├── Session.java │ │ ├── UserAcproblem.java │ │ ├── UserInfo.java │ │ └── UserRole.java │ ├── result/ │ │ ├── CommonResult.java │ │ └── ResultStatus.java │ └── utils/ │ ├── CodeForcesAES.js │ ├── CodeForcesUtils.java │ ├── IpUtil.java │ └── Tools.java └── voj-judger/ ├── pom.xml └── src/ └── main/ ├── java/ │ └── com/ │ └── simplefanc/ │ └── voj/ │ └── judger/ │ ├── JudgeServerApplication.java │ ├── common/ │ │ ├── constants/ │ │ │ ├── CompileConfig.java │ │ │ ├── Constants.java │ │ │ ├── JudgeDir.java │ │ │ ├── JudgeLanguage.java │ │ │ ├── JudgeServerConstant.java │ │ │ └── RunConfig.java │ │ ├── exception/ │ │ │ ├── CompileException.java │ │ │ ├── RuntimeException.java │ │ │ ├── SubmitException.java │ │ │ └── SystemException.java │ │ └── utils/ │ │ ├── JudgeUtil.java │ │ └── ThreadPoolUtil.java │ ├── config/ │ │ ├── AsyncTaskConfig.java │ │ ├── DruidConfiguration.java │ │ ├── MyMetaObjectConfig.java │ │ ├── MybatisPlusConfig.java │ │ ├── NacosConfig.java │ │ └── StartupRunner.java │ ├── controller/ │ │ ├── JudgeController.java │ │ └── SystemConfigController.java │ ├── dao/ │ │ ├── ContestEntityService.java │ │ ├── ContestRecordEntityService.java │ │ ├── JudgeCaseEntityService.java │ │ ├── JudgeEntityService.java │ │ ├── JudgeServerEntityService.java │ │ ├── ProblemCaseEntityService.java │ │ ├── ProblemEntityService.java │ │ ├── RemoteJudgeAccountEntityService.java │ │ ├── UserAcproblemEntityService.java │ │ └── impl/ │ │ ├── ContestEntityServiceImpl.java │ │ ├── ContestRecordEntityServiceImpl.java │ │ ├── JudgeCaseEntityServiceImpl.java │ │ ├── JudgeEntityServiceImpl.java │ │ ├── JudgeServerEntityServiceImpl.java │ │ ├── ProblemCaseEntityServiceImpl.java │ │ ├── ProblemEntityServiceImpl.java │ │ ├── RemoteJudgeAccountEntityServiceImpl.java │ │ └── UserAcproblemEntityServiceImpl.java │ ├── judge/ │ │ ├── local/ │ │ │ ├── Compiler.java │ │ │ ├── JudgeContext.java │ │ │ ├── JudgeProcess.java │ │ │ ├── JudgeRun.java │ │ │ ├── ProblemTestCaseUtils.java │ │ │ ├── SandboxRun.java │ │ │ ├── pojo/ │ │ │ │ ├── CaseResult.java │ │ │ │ ├── JudgeCaseDTO.java │ │ │ │ ├── JudgeGlobalDTO.java │ │ │ │ ├── JudgeResult.java │ │ │ │ └── SandBoxRes.java │ │ │ └── strategy/ │ │ │ ├── AbstractJudge.java │ │ │ ├── DefaultJudge.java │ │ │ ├── InteractiveJudge.java │ │ │ └── SpecialJudge.java │ │ └── remote/ │ │ ├── RemoteJudgeContext.java │ │ ├── RemoteOjAware.java │ │ ├── account/ │ │ │ ├── RemoteAccount.java │ │ │ └── RemoteAccountRepository.java │ │ ├── httpclient/ │ │ │ ├── AnonymousHttpContextRepository.java │ │ │ ├── CookieUtil.java │ │ │ ├── DedicatedHttpClient.java │ │ │ ├── DedicatedHttpClientFactory.java │ │ │ ├── HttpBodyValidator.java │ │ │ ├── HttpClientUtil.java │ │ │ ├── HttpStatusValidator.java │ │ │ ├── Mapper.java │ │ │ ├── SimpleHttpResponse.java │ │ │ ├── SimpleHttpResponseMapper.java │ │ │ ├── SimpleHttpResponseValidator.java │ │ │ └── SimpleNameValueEntityFactory.java │ │ ├── loginer/ │ │ │ ├── AbstractRetentiveLoginer.java │ │ │ ├── Loginer.java │ │ │ └── LoginersHolder.java │ │ ├── pojo/ │ │ │ ├── RemoteOjInfo.java │ │ │ ├── SubmissionInfo.java │ │ │ └── SubmissionRemoteStatus.java │ │ ├── provider/ │ │ │ ├── atcoder/ │ │ │ │ ├── AtCoderInfo.java │ │ │ │ ├── AtCoderLoginer.java │ │ │ │ ├── AtCoderQuerier.java │ │ │ │ └── AtCoderSubmitter.java │ │ │ ├── codeforcesgym/ │ │ │ │ ├── GYMInfo.java │ │ │ │ ├── GYMLoginer.java │ │ │ │ ├── GYMQuerier.java │ │ │ │ └── GYMSubmitter.java │ │ │ ├── codefores/ │ │ │ │ ├── CFInfo.java │ │ │ │ ├── CFLoginer.java │ │ │ │ ├── CFQuerier.java │ │ │ │ └── CFSubmitter.java │ │ │ ├── eoj/ │ │ │ │ ├── EojInfo.java │ │ │ │ └── EojLoginer.java │ │ │ ├── hdu/ │ │ │ │ ├── HDUInfo.java │ │ │ │ ├── HDULoginer.java │ │ │ │ ├── HDUQuerier.java │ │ │ │ └── HDUSubmitter.java │ │ │ ├── jsk/ │ │ │ │ ├── JSKInfo.java │ │ │ │ ├── JSKLoginer.java │ │ │ │ ├── JSKQuerier.java │ │ │ │ └── JSKSubmitter.java │ │ │ ├── mxt/ │ │ │ │ ├── MXTCaptchaRecognizer.java │ │ │ │ ├── MXTInfo.java │ │ │ │ ├── MXTLoginer.java │ │ │ │ ├── MXTQuerier.java │ │ │ │ ├── MXTSubmitter.java │ │ │ │ └── RsaUtil.java │ │ │ ├── poj/ │ │ │ │ ├── POJInfo.java │ │ │ │ ├── POJLoginer.java │ │ │ │ ├── POJQuerier.java │ │ │ │ └── POJSubmitter.java │ │ │ ├── shared/ │ │ │ │ └── codeforces/ │ │ │ │ ├── AbstractCFStyleLoginer.java │ │ │ │ ├── AbstractCFStyleQuerier.java │ │ │ │ ├── AbstractCFStyleSubmitter.java │ │ │ │ └── CFUtil.java │ │ │ └── tkoj/ │ │ │ ├── TKOJCaptchaRecognizer.java │ │ │ ├── TKOJInfo.java │ │ │ ├── TKOJLoginer.java │ │ │ ├── TKOJQuerier.java │ │ │ ├── TKOJSubmitter.java │ │ │ └── TKOJVerifyUtil.java │ │ ├── querier/ │ │ │ ├── Querier.java │ │ │ ├── QueriersHolder.java │ │ │ └── RemoteJudgeQuerier.java │ │ └── submitter/ │ │ ├── RemoteJudgeSubmitter.java │ │ ├── Submitter.java │ │ └── SubmittersHolder.java │ ├── mapper/ │ │ ├── ContestMapper.java │ │ ├── ContestRecordMapper.java │ │ ├── JudgeCaseMapper.java │ │ ├── JudgeMapper.java │ │ ├── JudgeServerMapper.java │ │ ├── ProblemCaseMapper.java │ │ ├── ProblemMapper.java │ │ ├── RemoteJudgeAccountMapper.java │ │ ├── UserAcproblemMapper.java │ │ └── UserRecordMapper.java │ └── service/ │ ├── JudgeService.java │ ├── SystemConfigService.java │ └── impl/ │ ├── JudgeServiceImpl.java │ └── SystemConfigServiceImpl.java └── resources/ ├── application.yml ├── banner.txt └── logback-spring.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.classpath # Package Files *.jar *.war *.ear *.log *.iml .DS_Store node_modules dist/ .cache/ .temp/ target/ out/ .idea/ .classpath .project # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* *.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 simplefanC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Virtual Online Judge(VOJ) [![Java](https://img.shields.io/badge/Java-17-informational)](https://openjdk.java.net) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.6.3-success)](https://spring.io/projects/spring-boot) [![Spring Cloud](https://img.shields.io/badge/Spring%20Cloud-2021.0.1-success)](https://spring.io/projects/spring-cloud) [![Spring Cloud Alibaba](https://img.shields.io/badge/Spring%20Cloud%20Alibaba-2021.0.1.0-success)](https://spring.io/projects/spring-cloud-alibaba) [![MySQL](https://img.shields.io/badge/MySQL-8.0.19-blue)](https://www.mysql.com/) [![Redis](https://img.shields.io/badge/Redis-5.0.9-red)](https://redis.io/) [![Vue](https://img.shields.io/badge/Vue-2.6.11-success)](https://cn.vuejs.org/) ## 简介 VOJ 是基于微服务、前后端分离的高性能在线评测系统。采用现阶段流行技术实现,采用 Docker 容器化部署。 ## 概览 + 基于 Docker,真正一键部署 + 前后端分离,模块化编程 + 微服务,支持分布式判题 + 拥有**本地判题**服务,同时支持其它知名 OJ (HDU、POJ、Codeforces、AtCoder...) 的**远程判题** + ACM/OI 两种比赛模式、完善的比赛功能(打星队伍、关注队伍、外榜) + 完善的判题模式(普通测评、特殊测评、交互测评) + 支持私有训练、公开训练(题单) + 更细致的权限划分,超级管理员、题目管理员、普通管理员各司其职 + 丰富的可视化图表,一图胜千言 + 支持 Template Problem,可以添加函数题甚至填空题 + 多语言支持:`C`, `C++`, `Java`, `Python`, `C#`,`GoLang` + Markdown & LaTeX 支持 ## 项目源码 + 后端源码:[https://github.com/simplefanC/voj](https://github.com/simplefanC/voj) + 前端源码:[https://github.com/simplefanC/voj-vue](https://github.com/simplefanC/voj-vue) + 判题沙箱:[https://github.com/criyle/go-judge](https://github.com/criyle/go-judge) ## 项目文档 项目文档地址:[https://github.com/simplefanC/voj/wiki](https://github.com/simplefanC/voj/wiki) ## 项目结构 ``` voj ├── voj-common -- 工具类及通用代码 ├── voj-backend -- 业务服务模块 └── voj-judger -- 评测服务模块 ``` ## 技术选型 | 技术 | 说明 | 官网 | | -------------------- | ------------------- | ----------------------------------------------- | | Spring Boot | 容器+MVC框架 | https://spring.io/projects/spring-boot | | Spring Cloud | 微服务框架 | https://spring.io/projects/spring-cloud | | Spring Cloud Alibaba | 微服务框架 | https://spring.io/projects/spring-cloud-alibaba | | MyBatis-Plus | ORM框架 | https://baomidou.com | | Druid | 数据库连接池 | https://github.com/alibaba/druid | | Redis | 分布式缓存 | https://redis.io | | Shiro | 认证和授权框架 | https://shiro.apache.org | | JWT | JWT登录支持 | https://github.com/jwtk/jjwt | | Hibernator-Validator | 验证框架 | http://hibernate.org/validator | | EasyExcel | JAVA解析Excel工具 | https://github.com/alibaba/easyexcel | | PageHelper | MyBatis物理分页插件 | https://github.com/pagehelper/Mybatis-PageHelper | | Hutool | Java工具类库 | https://github.com/looly/hutool | | Lombok | 简化对象封装工具 | https://github.com/rzwitserloot/lombok | | Swagger-UI | 文档生成工具 | https://github.com/swagger-api/swagger-ui | | Nginx | 静态资源服务器 | https://www.nginx.com | | Docker | 应用容器引擎 | https://www.docker.com ## 部署 快速部署:[基于 Docker Compose 部署](https://github.com/simplefanC/voj/wiki/deploy) 部署仓库:[https://github.com/simplefanC/voj-deploy](https://github.com/simplefanC/voj-deploy) ================================================ FILE: docs/package.json ================================================ { "name": "voj-docs", "version": "1.0.0", "description": "Virtual Online Judge", "license": "MIT", "type": "module", "scripts": { "build": "vuepress build src", "clean-dev": "vuepress dev src --clean-cache", "dev": "vuepress dev src" }, "devDependencies": { "@vuepress/client": "2.0.0-beta.51", "vue": "^3.2.29", "vuepress": "2.0.0-beta.51", "vuepress-theme-hope": "2.0.0-beta.108" } } ================================================ FILE: docs/src/.vuepress/config.ts ================================================ import {defineUserConfig} from "vuepress"; import theme from "./theme"; // const { searchPlugin } = require('@vuepress/plugin-search') export default defineUserConfig({ // dest: './', // 设置输出目录 lang: "zh-CN", title: "VOJ", description: "Virtual Online Judge", base: "/", head: [ [ "link", { rel: "stylesheet", href: "//at.alicdn.com/t/font_2410206_mfj6e1vbwo.css", }, ], ], plugins: [ // searchPlugin({ // // https://v2.vuepress.vuejs.org/zh/reference/plugin/search.html // // 排除首页 // isSearchable: (page) => page.path !== "/", // maxSuggestions: 10, // hotKeys: ["s", "/"], // // 用于在页面的搜索索引中添加额外字段 // getExtraFields: () => [], // locales: { // "/": { // placeholder: "搜索", // }, // }, // }), ], theme, }); ================================================ FILE: docs/src/.vuepress/navbar.ts ================================================ import {navbar} from "vuepress-theme-hope"; export default navbar([ // "/", // "/home", // { text: "使用指南", icon: "creative", link: "/guide/" }, // { // text: "博文", // icon: "edit", // prefix: "/posts/", // children: [ // { // text: "文章 1-4", // icon: "edit", // prefix: "article/", // children: [ // { text: "文章 1", icon: "edit", link: "article1" }, // { text: "文章 2", icon: "edit", link: "article2" }, // "article3", // "article4", // ], // }, // { // text: "文章 5-12", // icon: "edit", // children: [ // { // text: "文章 5", // icon: "edit", // link: "article/article5", // }, // { // text: "文章 6", // icon: "edit", // link: "article/article6", // }, // "article/article7", // "article/article8", // ], // }, // { text: "文章 9", icon: "edit", link: "article9" }, // { text: "文章 10", icon: "edit", link: "article10" }, // "article11", // "article12", // ], // }, // { // text: "主题文档", // icon: "note", // link: "https://vuepress-theme-hope.github.io/v2/zh/", // }, ]); ================================================ FILE: docs/src/.vuepress/sidebar.ts ================================================ import {sidebar} from "vuepress-theme-hope"; export default sidebar([ // "/", // "/home", // "/slide", { text: '序章', collapsable: true, prefix: "/introduction/", children: [ '', 'architecture' ] }, { text: '快速部署', collapsable: true, prefix: "/deploy/", children: [ '', 'docker', 'open-https', 'multi-judgeserver', // 'update', 'how-to-backup' ] }, { text: '单体部署', collapsable: true, prefix: "/monomer/", children: [ 'mysql', // 'mysql-checker', 'redis', 'nacos', 'backend', 'judgeserver', 'frontend', 'rsync' ] }, { text: '开发文档', collapsable: true, prefix: "/develop/", children: [ 'db', 'judge_dispatcher', 'sandbox', 'update-fe' ] }, { text: '使用文档', collapsable: true, prefix: "/use/", children: [ 'import-problem', 'judge-mode', 'testcase', 'training', 'contest', // 'group', 'import-user', 'admin-user', 'notice-announcement', 'discussion-admin' // 'custom-difficulty', // 'close-free-cdn' ] }, // { // text: "如何使用", // icon: "creative", // prefix: "/guide/", // link: "/guide/", // children: "structure", // }, // { // text: "文章", // icon: "note", // prefix: "/posts/", // children: [ // { // text: "文章 1-4", // icon: "note", // collapsable: true, // prefix: "article/", // children: ["article1", "article2", "article3", "article4"], // }, // { // text: "文章 5-12", // icon: "note", // children: [ // { // text: "文章 5-8", // icon: "note", // collapsable: true, // prefix: "article/", // children: ["article5", "article6", "article7", "article8"], // }, // { // text: "文章 9-12", // icon: "note", // children: ["article9", "article10", "article11", "article12"], // }, // ], // }, // ], // }, ]); ================================================ FILE: docs/src/.vuepress/styles/index.scss ================================================ ================================================ FILE: docs/src/.vuepress/styles/palette.scss ================================================ ================================================ FILE: docs/src/.vuepress/theme.ts ================================================ import {hopeTheme} from "vuepress-theme-hope"; import navbar from "./navbar"; import sidebar from "./sidebar"; export default hopeTheme({ hostname: "https://vuepress-theme-hope-v2-demo.mrhope.site", author: { name: "simplefanC", url: "https://github.com/simplefanC", }, iconPrefix: "iconfont icon-", // logo: "/logo.png", repo: "simplefanC/voj", docsDir: "docs/src", // navbar navbar: navbar, // sidebar sidebar: sidebar, footer: "鄂ICP备2020015769号-1", displayFooter: true, pageInfo: ["Author", "Original", "Date", "Category", "Tag", "ReadingTime"], blog: { description: "一个前端开发者", intro: "/intro.html", medias: { Baidu: "https://example.com", Bitbucket: "https://example.com", Dingding: "https://example.com", Discord: "https://example.com", Dribbble: "https://example.com", Email: "https://example.com", Evernote: "https://example.com", Facebook: "https://example.com", Flipboard: "https://example.com", GitHub: "https://example.com", Gitlab: "https://example.com", Gmail: "https://example.com", Instagram: "https://example.com", Lines: "https://example.com", Linkedin: "https://example.com", Pinterest: "https://example.com", Pocket: "https://example.com", QQ: "https://example.com", Qzone: "https://example.com", Reddit: "https://example.com", Rss: "https://example.com", Steam: "https://example.com", Twitter: "https://example.com", Wechat: "https://example.com", Weibo: "https://example.com", Whatsapp: "https://example.com", Youtube: "https://example.com", Zhihu: "https://example.com", }, }, encrypt: { config: { "/guide/encrypt.html": ["1234"], }, }, plugins: { blog: { autoExcerpt: true, }, // 如果你不需要评论,可以直接删除 comment 配置, // 以下配置仅供体验,如果你需要评论,请自行配置并使用自己的环境,详见文档。 // 为了避免打扰主题开发者以及消耗他的资源,请不要在你的正式环境中直接使用下列配置!!!!! // comment: { // /** // * Using giscus // */ // type: "giscus", // repo: "vuepress-theme-hope/giscus-discussions", // repoId: "R_kgDOG_Pt2A", // category: "Announcements", // categoryId: "DIC_kwDOG_Pt2M4COD69", // // /** // * Using twikoo // */ // // type: "twikoo", // // envId: "https://twikoo.ccknbc.vercel.app", // // /** // * Using Waline // */ // // type: "waline", // // serverURL: "https://vuepress-theme-hope-comment.vercel.app", // }, mdEnhance: { enableAll: true, presentation: { plugins: ["highlight", "math", "search", "notes", "zoom"], }, }, }, }); ================================================ FILE: docs/src/README.md ================================================ --- title: 首页 home: true heroImage: /logo.png heroText: VOJ tagline: 基于分布式、前后端分离的高性能在线评测系统 actions: - text: 文档介绍 🔔 link: /introduction/ type: primary - text: 快速部署 link: /deploy/docker/ features: - title: 分布式 details: 支持多台判题服务弹性伸缩 - title: 高效化 details: 采用前后端分离,开发迅速,使用高性能可复用判题沙盒 - title: 定制化 details: 网站配置高度集中,支持定制化修改 - title: 安全化 details: 判题使用 Cgroups 隔离用户程序,网站权限控制完善 - title: 多样化 details: 独有本地判题服务,同时支持其它知名 OJ 题目的远程判题 --- [![Java](https://img.shields.io/badge/Java-11-informational)](https://openjdk.java.net) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.6.3-success)](https://spring.io/projects/spring-boot) [![Spring Cloud](https://img.shields.io/badge/Spring%20Cloud-2021.0.1-success)](https://spring.io/projects/spring-cloud) [![Spring Cloud Alibaba](https://img.shields.io/badge/Spring%20Cloud%20Alibaba-2021.0.1.0-success)](https://spring.io/projects/spring-cloud-alibaba) [![MySQL](https://img.shields.io/badge/MySQL-8.0.19-blue)](https://www.mysql.com/) [![Redis](https://img.shields.io/badge/Redis-5.0.9-red)](https://redis.io/) [![Nacos](https://img.shields.io/badge/Nacos-1.4.2-%23267DF7)](https://github.com/alibaba/nacos) [![Vue](https://img.shields.io/badge/Vue-2.6.11-success)](https://cn.vuejs.org/) Virtual Online Judge (VOJ) : 基于前后端分离、分布式架构的在线测评系统(VOJ),前端使用 Vue,后端主要使用 Spring Boot,Redis,MySQL,Nacos 等主流技术,**支持 HDU、POJ、CF、AtCoder、MXT、JSK、TKOJ 的虚拟判题,同时适配手机端、电脑端浏览,拥有讨论区与站内消息系统,支持私有训练、公开训练(题单),还有完善的判题模式(普通测评、特殊测评、交互测评)和完善的比赛功能(打星队伍、关注队伍、外榜)。** [GitHub 仓库](https://github.com/simplefanc/voj) 有任何部署问题或项目 Bug 请提 Issue! ================================================ FILE: docs/src/deploy/README.md ================================================ # 环境配置 ## 环境说明 :::tip - 后端:需要在 Linux 系统下部署运行,建议使用 Ubuntu 18.04,其它版本的 Linux 系统也可以,同时需要 **Docker** 辅助部署 - 前端:Linux 系统下,需要 Nginx 进行反向代理 - 判题服务:由于判题沙盒有多操作系统版本,Linux 系统或 Windows 均可以,但是在本 VOJ 镜像中**只能**使用 **Ubuntu 16.04** 以上或者 **CentOS 8** 以上 - 数据同步:运行判题服务和后端服务的服务器有 rsync - **尽量不要使用突发性能或共享型的云服务器实例,有可能造成评测时间计量不准确。** ::: ## Linux环境搭建 > VOJ 使用的 Ubuntu 18.04 版本,单机部署建议2核4G以上内存 ### 安装 Docker 1. 安装需要的包 ```shell sudo apt-get update ``` 2. 安装依赖包 ```shell sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common ``` 3. 添加 Docker 的官方 GPG 密钥 ```shell curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - ``` 4. 设置远程仓库 ```shell sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable" ``` 5. 安装 Docker-CE ```shell sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io ``` 6. 验证是否成功 ```shell sudo docker run hello-world ``` ### 安装 Docker-Compose 1. 下载 ```shell sudo curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.5/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose ``` 2. 授权 ```shell sudo chmod +x /usr/local/bin/docker-compose ``` ## Windows 环境 Windows 下的安装仅供体验,勿在生产环境使用。如必要,请使用虚拟机安装 Linux 并将本 OJ 安装在其中。 以下教程仅适用于 Win10 x64 下的 `PowerShell` 1. 安装 Windows 的 Docker 工具 2. 右击右下角 Docker 图标,选择 Settings 进行设置 3. 选择 `Shared Drives` 菜单,之后勾选你想安装 OJ 的盘符位置(例如勾选D盘),点击 `Apply` 4. 输入 Windows 的账号密码进行文件共享 5. 安装 `Python`、`pip`、`git`、`docker-compose`,安装方法自行搜索。 ================================================ FILE: docs/src/deploy/docker.md ================================================ # 快速部署 **前提:已经在上一步准备好 Docker 与 Docker Compose** :::danger 注意:如果正式部署运行 VOJ,请修改默认配置的密码,例如MySQL、Nacos的密码!!! **使用默认密码可能会导致数据泄露,网站极其不安全!** ::: ## 一、单机部署 1. 选择好需要安装的位置,运行下面命令 ```shell git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy ``` 2. 进入文件夹,使用docker-compose启动各容器服务 ```shell cd standAlone ``` `standAlone`文件夹文件有以下: ```bash ├── docker-compose.yml ├── .env ``` 主要配置请见`.env`文件,内容如下: > 注意:各服务ip最好不改动,保持处于172.20.0.0/16网段的docker network ```properties # VOJ全部数据存储的文件夹位置(默认当前路径生成voj文件夹) VOJ_DATA_DIRECTORY=./voj # Redis的配置 REDIS_HOST=172.20.0.2 REDIS_PORT=6379 # MySQL的配置 MYSQL_HOST=172.20.0.3 # 如果判题服务是分布式,请提供当前MySQL所在服务器的公网ip MYSQL_PUBLIC_HOST=172.20.0.3 MYSQL_PORT=3306 MYSQL_ROOT_PASSWORD=voj123456 # Nacos的配置 NACOS_HOST=172.20.0.4 NACOS_PORT=8848 NACOS_USERNAME=nacos NACOS_PASSWORD=nacos # backend后端服务的配置 BACKEND_HOST=172.20.0.5 BACKEND_PORT=6688 # JWT加密秘钥,default则生成32位随机密钥 JWT_TOKEN_SECRET=default # token过期时间默认为24小时(86400s) JWT_TOKEN_EXPIRE=86400 # JWT默认12小时自动刷新 JWT_TOKEN_FRESH_EXPIRE=43200 # 调用判题服务器的token,default则生成32位随机密钥 JUDGE_TOKEN=default # 请使用邮件服务的域名或ip EMAIL_SERVER_HOST=smtp.qq.com EMAIL_SERVER_PORT=465 EMAIL_USERNAME=your_email_username EMAIL_PASSWORD=your_email_password # 判题服务的配置 JUDGE_SERVER_IP=172.20.0.7 JUDGE_SERVER_PORT=8088 JUDGE_SERVER_NAME=judger-alone # -1表示可接收最大判题任务数为:cpu核心数+1 MAX_TASK_NUM=-1 # 当前判题服务器是否开启远程虚拟判题功能 REMOTE_JUDGE_OPEN=true # -1表示可接收最大远程判题任务数为:cpu核心数*2+1 REMOTE_JUDGE_MAX_TASK_NUM=-1 # 默认沙盒并行判题程序数为:cpu核心数 PARALLEL_TASK=default # docker network的配置 SUBNET=172.20.0.0/16 ``` :::tip 提示:如果服务器的内存在4G或4G以上,请去掉JVM限制才能提高并发量,操作如下: - 注释或删除`docker-compose.yml`中`voj-backend`模块和`voj-judger`模块`environment`参数`JAVA_OPTS` ::: 3. 如果不改动,则以默认参数启动(**正式部署请修改默认配置的密码!**) ```shell docker-compose up -d ``` 4. 对依赖服务 MySQL 进行以下设置(分布式部署主服务器时同理) 1. 将 voj.sql 和 nacos_config.sql 文件拷贝到容器/目录下: ``` docker cp ../sql/voj.sql voj-mysql:/ docker cp ../sql/nacos_config.sql voj-mysql:/ ``` 2. 进入 voj-mysql 容器并执行如下操作: ``` # 进入mysql容器 docker exec -it voj-mysql /bin/bash # 连接到mysql服务 mysql -uroot -pvoj123456 # 导入sql脚本 source /voj.sql source /nacos_config.sql ``` 3. 退出 voj-mysql 容器。 5. docker-compose restart 等待命令执行完毕后,查看容器状态 ```shell docker ps -a ``` 当看到所有的容器的状态status都为`UP`或`healthy`就代表 OJ 已经启动成功。 以下默认参数说明 :::warning - 默认超级管理员账号与密码:root / voj123456 - 默认 MySQL 账号与密码:root / voj123456(**正式部署请修改**) - 默认 Nacos 管理员账号与密码:root / voj123456(**正式部署请修改**) - 默认不开启 https,开启需修改文件同时提供证书文件 - 判题并发数默认:cpu核心数+1 - 默认开启vj判题,需要手动修改添加账号与密码,如果不添加不能vj判题! - vj判题并发数默认:cpu核心数*2+1 ::: **登录root账号到后台查看服务状态以及到`http://ip/admin/conf`修改服务配置!** 注意:网站的注册及用户账号相关操作需要邮件系统,所以请在系统配置中配置自己的SMTP邮件服务。 **(如果已经在启动在.env文件配置了邮件服务即不用再次修改)** ```bash Host: smtp.qq.com Port: 465 Username: 邮箱账号 Password: 开启SMTP服务后生成的随机授权码 ``` ## 二、分布式部署 :::tip 主服务器(运行Nacos, backend, frontend, Redis)的服务器防火墙请开8848(Nacos), 3306(MySQL), 873(Rsync)端口 从服务器(运行judger)的服务器防火墙请开8088端口 ::: 1. 选择好需要安装的位置,运行下面命令 ```shell git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy ``` 2. 进入文件夹 ```shell cd distributed ``` `distributed`文件夹有以下: ```bash ├── judger ├── main ``` 3. 首先部署主服务,即数据后台服务(DataBackup) ```shell cd main ``` 该文件夹下有: ```bash ├── docker-compose.yml ├── .env ``` 修改`.env`文件中的配置 ```shell vim .env ``` > 注意:各服务ip最好不改动,保持处于172.20.0.0/16网段的docker network ```properties # voj全部数据存储的文件夹位置(默认当前路径生成voj文件夹) VOJ_DATA_DIRECTORY=./voj # Redis的配置 REDIS_HOST=172.20.0.2 REDIS_PORT=6379 # MySQL的配置 MYSQL_HOST=172.20.0.3 # 请提供当前MySQL所在服务器的公网ip MYSQL_PUBLIC_HOST= MYSQL_PORT=3306 MYSQL_ROOT_PASSWORD=voj123456 # Nacos的配置 NACOS_HOST=172.20.0.4 NACOS_PORT=8848 NACOS_USERNAME=nacos NACOS_PASSWORD=nacos # backend后端服务的配置 BACKEND_HOST=172.20.0.5 BACKEND_PORT=6688 # JWT加密秘钥,默认则生成32位随机密钥 JWT_TOKEN_SECRET=default # JWT过期时间默认为24小时(86400s) JWT_TOKEN_EXPIRE=86400 # JWT默认12小时可自动刷新 JWT_TOKEN_FRESH_EXPIRE=43200 # 调用判题服务器的token,默认则生成32位随机密钥 JUDGE_TOKEN=default # 请使用邮件服务的域名或ip EMAIL_SERVER_HOST=smtp.qq.com EMAIL_SERVER_PORT=465 EMAIL_USERNAME=your_email_username EMAIL_PASSWORD=your_email_password # 评测数据同步的配置 # 请修改数据同步密码 RSYNC_PASSWORD=voj123456 # docker network的配置 SUBNET=172.20.0.0/16 ``` 配置修改保存后,当前路径下启动该服务 ```shell docker-compose up -d ``` 等待命令执行完毕后,查看容器状态 ```shell docker ps -a ``` 当看到所有的容器的状态status都为`UP`或`healthy`就代表 OJ 已经启动成功。 4. 接着,在另一台服务器上,依旧git clone该文件夹下来,然后进入`judger`文件夹,修改`.env`的配置 ```properties # voj全部数据存储的文件夹位置(默认当前路径生成judge文件夹) VOJ_JUDGESERVER_DATA_DIRECTORY=./judge # Nacos的配置 # 修改为Nacos所在服务的公网ip NACOS_HOST= # 修改为Nacos启动端口号,默认为8848 NACOS_PORT=8848 # 修改为Nacos的管理员账号 NACOS_USERNAME=nacos # 修改为Nacos的管理员密码 NACOS_PASSWORD=nacos # judgeserver的配置 # 修改本服务器公网ip JUDGE_SERVER_IP= JUDGE_SERVER_PORT=8088 JUDGE_SERVER_NAME=judger-1 # -1表示可接收最大判题任务数为:cpu核心数+1 MAX_TASK_NUM=-1 # 当前判题服务器是否开启远程虚拟判题功能 REMOTE_JUDGE_OPEN=true # -1表示可接收最大远程判题任务数为:cpu核心数*2+1 REMOTE_JUDGE_MAX_TASK_NUM=-1 # 默认沙盒并行判题程序数为cpu核心数 PARALLEL_TASK=default # rsync评测数据同步的配置 # 写入主服务器公网ip RSYNC_MASTER_ADDR= # 与主服务器的rsync密码一致 RSYNC_PASSWORD=voj123456 ``` 配置修改保存后,当前路径下启动该服务 ```shell docker-compose up -d ``` :::tip 提示:需要开启多台判题机,就如当前第4步的操作一样,在每台服务器上执行以上的操作即可。 ::: 5. 两个服务都启动完成,在浏览器输入主服务ip或域名进行访问,登录root账号到后台查看服务状态。 ================================================ FILE: docs/src/deploy/how-to-backup.md ================================================ # 如何备份 ### 1. 单体部署 部署脚本(`docker-compose.yml`)同目录的`voj`文件夹中,存储了本系统的全部数据: ```html voj ├── file # 存储了上传的图片、上传的临时题目数据、markdown引用的文件等文件 ├── judge # 存储了每个提交题目的评测过程产生的数据 ├── log # 存储了voj-backend项目的运行日志 ├── testcase # 存储了题目的评测数据 └── data ├── mysql │ ├── data # 存储了MySQL数据库的数据 ├── redis │ ├── data # 存储了redis产生的快照数据 ``` 如果需要备份,只需将该`voj`文件夹复制一份即可,在新的机器上重新部署VOJ时,将该文件夹放置与`docker-compose.yml`同目录下,使用`docker-compose up -d`即可启动恢复原来的数据。 ### 2. 分布式部署 - 主服务器(运行`voj-backend`的服务器) 部署脚本(`docker-compose.yml`)同目录的`voj`文件夹中,存储了本系统主服务器的全部数据: ```html voj ├── file # 存储了上传的图片、上传的临时题目数据、markdown引用的文件等文件 ├── log # 存储了voj-backend项目的运行日志 ├── testcase # 存储了题目的评测数据 └── data ├── mysql │ ├── data # 存储了MySQL数据库的数据 ├── redis │ ├── data # 存储了redis产生的快照数据 ``` - 判题服务器(运行`voj-judger`的服务器) 部署脚本(`docker-compose.yml`)同目录的`voj`文件夹中,存储了本系统判题服务器的全部数据: ```html judge ├── run # 存储了每个提交题目的评测过程产生的数据 ├── log # 存储了voj-judger项目的运行日志 ├── testcase # 存储了题目的评测数据(每100s从主服务器同步) ├── spj # 存储了SPJ的代码 ``` 那么,主要要备份的还是**主服务器**的数据,只需将该`voj`文件夹复制一份即可,在新的机器上重新部署新的voj的时候,将该文件夹放置与`docker-compose.yml`一个目录下,使用`docker-compose up -d`即可启动恢复原来的数据。 ================================================ FILE: docs/src/deploy/multi-judgeserver.md ================================================ # 多个判题机 ## 前言 不同判题机之间是通过rsync进行数据同步的,所以需要配置相应的rsync服务。 同时注意以下两点: 1. 保证rsync-slave服务的密码与主服务rsync-master的数据同步密码一致 2. rsync-slave服务(判题机服务器)拉取主服务rsync-master的评测数据是每100s一次,所以后台上传评测数据后,需等待大概100s才能正常判题。 ## 单体部署 如果之前是选择了单体部署,也就是主服务器既有backend和judger服务,那么部署更多不同服务器的判题机应该如下修改: 1. 在原先运行的服务器上,修改`voj-deploy/standAlone`文件夹里面的`docker-compose.yml`,**添加以下rsync-master服务**,数据同步密码请自行修改,如下: **(注意:如果云服务器有防火墙请开启8848,3306,873端口)** ```yaml voj-rsync-master: image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0 container_name: voj-rsync-master volumes: - ./voj/testcase:/voj/testcase:ro environment: - RSYNC_MODE=master - RSYNC_USER=vojrsync - RSYNC_PASSWORD=voj123456 # 请修改数据同步密码 ports: - "0.0.0.0:873:873" ``` **同时,需要将MySQL的配置`MYSQL_PUBLIC_HOST`改成当前服务器的公网IP** ```shell vim .env # 修改与docker-compose.yml同目录下的配置文件 ``` ```yaml # MySQL的配置 MYSQL_HOST=172.20.0.3 # 请提供当前mysql所在服务器的公网ip MYSQL_PUBLIC_HOST=*** MYSQL_PUBLIC_PORT=3306 MYSQL_ROOT_PASSWORD=voj123456 ``` 修改完保存,然后重启 Docker 即可生效 ```shell docker-compose down docker-compose up -d ``` 2. 在其它服务器(判题服务器)中使用 Docker-Compose运行`voj-judger`服务,具体操作如下: **(注意:服务器请开启8088端口号,需要将判题服务暴露出去)** 1. 下载文件,进入到指定文件夹 ```shell git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/distributed/judger ``` 2. 修改配置`.env`文件 ```properties # Nacos的配置 # 修改为Nacos所在服务的ip NACOS_HOST= # 修改为nacos启动端口号,默认为8848 NACOS_PORT=8848 # 修改为nacos的管理员账号 NACOS_USERNAME=root # 修改为nacos的管理员密码 NACOS_PASSWORD=voj123456 # judgeserver的配置 # 修改为当前服务器公网ip JUDGE_SERVER_IP= JUDGE_SERVER_PORT=8088 JUDGE_SERVER_NAME=judger-1 # -1表示可接收最大判题任务数为:cpu核心数+1 MAX_TASK_NUM=-1 # 当前判题服务器是否开启远程虚拟判题功能 REMOTE_JUDGE_OPEN=true # -1表示可接收最大远程判题任务数为:cpu核心数*2+1 REMOTE_JUDGE_MAX_TASK_NUM=-1 # 默认沙盒并行判题程序数为cpu核心数 PARALLEL_TASK=default # rsync评测数据同步的配置 # 写入主服务器ip RSYNC_MASTER_ADDR= # 与主服务器的rsync密码一致 RSYNC_PASSWORD=voj123456 ``` 3. 启动即可 ```shell docker-compose up -d ``` 4. 验证: 访问 http://ip:8088/version 如果返回信息正常即启动成功! ## 分布式部署 1. 如果之前已经选择了分布式部署,那么增加判题机,则与原先启动判题机的操作一样即可,在新的服务器上操作如下: ```shell git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/distributed/judger vim .env ``` 2. 修改`.env`文件的配置 ```properties # voj全部数据存储的文件夹位置(默认当前路径生成judge文件夹) VOJ_JUDGESERVER_DATA_DIRECTORY=./judge # Nacos的配置 # 修改为Nacos所在服务的ip NACOS_HOST= # 修改为Nacos启动端口号,默认为8848 NACOS_PORT=8848 # 修改为Nacos的管理员账号 NACOS_USERNAME=root # 修改为Nacos的管理员密码 NACOS_PASSWORD=voj123456 # judgeserver的配置 # 修改本服务器公网ip JUDGE_SERVER_IP= JUDGE_SERVER_PORT=8088 JUDGE_SERVER_NAME=judger-1 # -1表示可接收最大判题任务数为:cpu核心数+1 MAX_TASK_NUM=-1 # 当前判题服务器是否开启远程虚拟判题功能 REMOTE_JUDGE_OPEN=true # -1表示可接收最大远程判题任务数为:cpu核心数*2+1 REMOTE_JUDGE_MAX_TASK_NUM=-1 # 默认沙盒并行判题程序数为cpu核心数 PARALLEL_TASK=default # rsync评测数据同步的配置 # 写入主服务器ip RSYNC_MASTER_ADDR= # 与主服务器的rsync密码一致 RSYNC_PASSWORD=voj123456 ``` 3. 修改完保存,启动即可。 ```shell docker-compose up -d ``` ================================================ FILE: docs/src/deploy/open-https.md ================================================ # 开启HTTPS - 单机部署: 提供`server.pem`和`server.key`证书与密钥文件放置`/standAlone`目录下,与`docker-compose.yml`和`.env`文件放于同一位置,然后修改`docker-compose.yml`中的`voj-frontend`的配置 - 分布式部署: 提供`server.pem`和`server.key`证书与密钥文件放置`/distributed/main`目录下,与`docker-compose.yml`和`.env`文件放置同一位置,然后修改`docker-compose.yml`中的`voj-frontend`的配置 ```yaml voj-frontend: image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_frontend container_name: voj-frontend restart: always volumes: - ./html/dist:/usr/share/nginx/html # 开启https,请提供证书 - ./server.pem:/etc/nginx/conf.d/cert/server.pem - ./server.key:/etc/nginx/conf.d/cert/server.key environment: - SERVER_NAME=localhost # 域名或localhost(本地) - BACKEND_SERVER_HOST=${BACKEND_HOST:-172.20.0.5} # backend后端服务地址 - BACKEND_SERVER_PORT=${BACKEND_PORT:-6688} # backend后端服务端口号 - USE_HTTPS=true # 使用https请设置为true ports: - "80:80" - "443:443" networks: voj-network: ipv4_address: 172.20.0.6 ``` ================================================ FILE: docs/src/deploy/update.md ================================================ # 如何更新 ## 一、无二次开发的更新 :::warning 2021.09.21之后部署voj的请看下面操作 ::: 请在对应的docker-compose.yml当前文件夹下执行`docker-compose pull`拉取最新镜像,然后重新`docker-compose up -d`即可。 :::warning 2021.09.21之前部署voj的请看下面操作 ::: ### 1、修改MySQL8.0默认的密码加密方式 (1)进行voj-mysql容器 ```shell docker exec -it voj-mysql bash ``` (2) 输入对应的mysql密码,进入mysql数据库 注意:-p 后面跟着数据库密码例如voj123456 ```shell mysql -uroot -p数据库密码 ``` (3)成功进入后,执行以下命令 ```shell mysql> use mysql; mysql> grant all PRIVILEGES on *.* to root@'%' WITH GRANT OPTION; mysql> ALTER user 'root'@'%' IDENTIFIED BY '数据库密码' PASSWORD EXPIRE NEVER; mysql> ALTER user 'root'@'%' IDENTIFIED WITH mysql_native_password BY '数据库密码'; mysql> FLUSH PRIVILEGES; ``` (4) 两次exit 退出mysql和容器 ### 2、 添加voj-mysql-checker模块 (1)可以选择拉取仓库最新的docker-compose.yml文件(跟部署操作一样,但是会覆盖之前设置的参数)或者访问: https://github.com/simplefanc/voj-deploy/blob/master/standAlone/docker-compose.yml (2)或者编辑docker-compose.yml文件,手动添加新模块 ```yaml voj-mysql-checker: image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_database_checker container_name: voj-mysql-checker depends_on: - voj-mysql links: - voj-mysql:mysql environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-voj123456} networks: voj-network: ipv4_address: 172.20.0.8 ``` (3) 保存后重启容器即可 ```shell docker-compose down docker-compose pull docker-compose up -d ``` **注意**:此次修改成功后,以后更新,都请在对应的docker-compose.yml当前文件夹下执行`docker-compose pull`拉取最新镜像,然后重新`docker-compose up -d`即可。 ## 二、自定义前端的更新 > 附加:如何自定义前端请看这里 => [自定义前端文档](/use/update-fe.html) (1)首先到`./voj/voj-vue`文件夹中,拉取[voj-vue](https://github.com/simplefanc/voj/tree/master/voj-vue)仓库最新的代码,请注意解决出现的冲突。 ```shell git pull ``` 或者重新直接download成zip包,然后重新自定义修改前端 当然,如果想查看对比主仓库更新的内容,可以用以下命令一步步合并 ```bash git remote -v # 查看主仓库的远程仓库 git fetch origin master:temp # 将最新的主仓库代码拉到本地一个temp的分支 git diff temp # 比较现在本地代码与最新temp分支的区别 git merge temp # 合并temp分支到本地的master分支 git branch -d temp # 删除temp这个临时分支 ``` (2)接着,重新用npm打包,在`./voj/voj-vue/dist`文件夹会生成静态的前端文件,放到原来指定的位置即可 ```shell npm run build ``` (3)其它模块的更新,都请在对应的docker-compose.yml当前文件夹下执行`docker-compose pull`拉取最新镜像,然后重新`docker-compose up -d`即可。 ================================================ FILE: docs/src/develop/db.md ================================================ # 数据库说明 ## 用户资料模块 user_info表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------------- | | uuid | String | 主键 | uuid用户id | | username | String | | 登录账号 | | password | String | | 登录密码 | | nickname | String | | 用户昵称 | | school | String | | 学校 | | course | String | | 专业 | | number | String | | 学号 | | realname | String | | 真实名字 | | email | String | | 邮箱 | | gender | String | | 性别 | | avatar | String | | 头像图片地址 | | signature | String | | 个性签名 | | cf_username | String | | codeforces的username | | blog | String | | 博客地址 | | github | String | | github地址 | | title_name | String | | 称号、头衔 | | title_color | String | | 称号、头衔的背景颜色 | | status | int | | 0可用,1不可用 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | session表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ---------------- | | id | long | 主键 | auto_increment | | uid | String | 外键 | 用户id | | user_agent | String | | 访问的浏览器参数 | | ip | Srting | | 访问所在的ip | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | role 角色表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------------------- | | id | long | 主键 | auto_increment | | role | String | | “admin”,”tourist”,“user” | | description | String | | 角色描述 | | status | int | | 是否可用,0可用 1不可用 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | user_role表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------- | | id | long | 主键 | auto_increment | | uid | String | 外键 | 用户id | | role_id | int | 外键 | 角色id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | auth权限表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ------------------------------------------------------------ | | id | long | 主键 | auto_increment | | name | String | | 权限名称,“superadmin”,”contest”,“admin”,”common” 普通用户默认为“common” | | permission | String | | 权限字符串,例如“contest:1001”,发布某场比赛。 “all”,”select”,”update”等等, | | status | int | | 0可用,1不可用 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | role_auth表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------- | | id | long | 主键 | auto_increment | | role_id | int | | 角色id | | auth_id | int | | 权限id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | user_record表 个人做题记录表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ----------- | -------------------------- | | id | long | primary key | auto_increment | | uid | String | 外键 | 用户id | | rating | int | | Cf得分,未参加过默认为1500 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | user_acproblem表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ----------- | -------------- | | id | long | primary key | auto_increment | | uid | String | 外键 | 用户id | | pid | long | 外键 | Ac的题目id | | subimit_id | long | 外键 | 提交的id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | ## 题目详情模块 problem表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------------- | ------------ | ----------- | --------------------------------------------------------- | | id | long | primary key | auto_increment 1000开始 | | judge_mode | String | | 默认为default、其他值有spj、interactive | | problem_id | String | | 题目展示id | | title | String | | 题目标题 | | author | String | | 默认可为无 | | type | int | | 题目类型 0为ACM,1为OI | | time_limit | int | | 时间限制(ms),默认为c/c++限制,其它语言为2倍 | | memory_limit | int | | 空间限制(mb),默认为c/c++限制,其它语言为2倍 | | stack_limit | int | | 栈限制(mb),默认为128 | | description | String | | 内容描述 | | input | String | | 输入描述 | | output | String | | 输出描述 | | examples | Srting | | 题面输入输出样例,不纳入评测数据 | | source | int | | 题目来源(比赛id),默认为voj,可能为爬虫vj | | difficulty | int | | 题目难度,0简单,1中等,2困难 | | hint | String | | 备注 提醒 | | auth | int | | 默认为1公开,2为私有,3为比赛中。 | | io_score | int | | 当该题目为io题目时的分数 默认为100 | | code_share | boolean | | 该题目对应的相关提交代码,用户是否可用分享 | | spj_code | String | | 特判或交互程序代码 | | spj_language | String | | 特判或交互程序的语言 | | user_extra_file | String | | 选手程序的额外文件 json key:文件名 value:文件内容 | | judge_extra_file | String | | 特判或交互程序的额外文件 json key:文件名 value:文件内容 | | is_remove_end_blank | boolean | | 是否默认去除用户代码的文末空格 | | open_case_result | boolean | | 是否默认开启该题目的测试样例结果查看 | | caseVersion | String | | 题目测试数据的版本号 | | is_upload_case | boolean | | 是否是上传zip评测数据的 | | modified_user | String | | 最新修改题目的用户 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | problem_case表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ----------- | -------------------- | | id | long | primary key | auto_increment | | pid | long | 外键 | 题目id | | input | String | | 测试样例的输入文件名 | | output | String | | 测试样例的输出文件名 | | status | String | | 状态0可用,1不可用 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | tag表 题目表的标签 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------- | | id | long | 主键 | auto_increment | | name | String | | 标签名字 | | color | String | | 标签颜色 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | problem_tag表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------- | | id | int | | 主键id | | tid | int | | 标签id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | language表 | 列名 | 实体属性类型 | 键 | 备注 | | --------------- | ------------ | ---- | ---------------------------- | | id | long | | 主键id | | content_type | String | | 语言类型 | | description | String | | 语言描述 | | name | String | | 语言名字 | | compile_command | String | | 编译指令 | | template | String | | A+B题目模板 | | code_template | String | | 语言对应的代码模板 | | is_spj | boolean | | 是否可作为特殊判题的一种语言 | | oj | String | | 该语言属于哪个oj,自身oj用ME | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | code_template表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------- | | id | long | | 主键id | | pid | long | 外键 | 题目id | | lid | long | 外键 | 语言id | | code | String | | 代码模板 | | status | boolean | | 是否启用 | | gmt_create | datetime | | 修改时间 | | gmt_modified | datetime | | 修改时间 | ## 提交评测模块 > 判题结果status 未提交:STATUS_NOT_SUBMITTED = -10 提交中:STATUS_SUBMITTING = 9 排队中:STATUS_PENDING = 6 评测中:STATUS_JUDGING = 7 编译错误:STATUS_COMPILE_ERROR = -2 输出格式错误:STATUS_PRESENTATION_ERROR = -3 答案错误:STATUS__WRONG_ANSWER = -1 评测通过:STATUS_ACCEPTED = 0 cpu时间超限:STATUS__CPU_TIME_LIMIT_EXCEEDED = 1 真实时间超限:STATUS__REAL_TIME_LIMIT_EXCEEDED = 2 空间超限:STATUS__MEMORY_LIMIT_EXCEEDED = 3 运行错误:STATUS__RUNTIME_ERROR = 4 系统错误:STATUS__SYSTEM_ERROR = 5 OI评测部分通过:STATUS_PARTIAL_ACCEPTED = 8 提交失败:STATUS_SUBMITTED_FAILED= 10 judge表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------- | ------------ | ----------- | -------------------------------- | | submit_id | long | primary key | auto_increment | | display_pid | String | | 题目展示id | | pid | long | 外键 | 题目id | | uid | String | 外键 | 提交用户的id | | username | String | 外键 | 用户名 | | submit_time | datetime | | 提交时间 | | status | String | | 判题结果 | | share | Boolean | | 代码是否分享 | | error_message | String | | 错误提醒(编译错误,或者vj提醒) | | time | int | | 运行时间 | | memory | int | | 所耗内存 | | length | int | | 代码长度 | | code | String | | 代码 | | language | String | | 代码语言 | | cpid | int | | 比赛中的题目编号id | | judger | String | | 判题机ip | | ip | String | | 提交者ip | | cid | int | | 题目来源的比赛id,默认为0 | | version | int | | 乐观锁(废弃) | | oi_rank_score | int | | oi排行榜得分 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | jugde_case表 评测单个样例结果表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ---------------- | | submit_id | long | 外键 | 提交id | | problemId | String | 外键 | 题目id | | userId | String | 外键 | 提交用户的id | | Status | String | | 单个样例评测结果 | | time | int | | 运行时间 | | memory | int | | 运行内存 | | case_id | String | | 测试样例id | | input_data | String | | 样例输入的文件名 | | Output_data | String | | 样例输出的文件名 | | user_output | Srting | | 暂时用作信息提示 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | ## 比赛模块 更新比赛状态的存储过程 ```sql DELIMITER | DROP PROCEDURE IF EXISTS contest_status | CREATE PROCEDURE contest_status() BEGIN UPDATE contest SET STATUS = ( CASE WHEN NOW() < start_time THEN -1 WHEN NOW() >= start_time AND NOW()= end_time THEN 1 END); END | ``` 创建插入时的触发器 ```sql DROP TRIGGER IF EXISTS contest_trigger; DELIMITER $$ CREATE TRIGGER contest_trigger BEFORE INSERT ON contest FOR EACH ROW BEGIN SET new.status=( CASE WHEN NOW() < new.start_time THEN -1 WHEN NOW() >= new.start_time AND NOW()= new.end_time THEN 1 END); END$$ DELIMITER ; ``` 设置定时器 ```sql SET GLOBAL event_scheduler = 1; // 开启定时器 CREATE EVENT IF NOT EXISTS contest_event ON SCHEDULE EVERY 1 SECOND // 每秒执行一次 ON COMPLETION PRESERVE DO CALL contest_status(); // 调用存储过程 ``` 开启或关闭定时器 ```sql ALTER EVENT contest_event ON COMPLETION PRESERVE ENABLE; -- 开启事件 ALTER EVENT contest_event ON COMPLETION PRESERVE DISABLE; -- 关闭事件 ``` contest表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------------ | ------------ | ---- | ----------------------------------------------------- | | id | long | 主键 | auto_increment 1000起步 | | uid | String | 外键 | 创建者id | | author | String | | 比赛创建者的用户名 | | title | String | | 比赛标题 | | type | int | | ACM赛制或者Rating | | source | int | | 比赛来源,原创为0,克隆赛为比赛id | | auth | int | | 0为公开赛,1为私有赛(有密码),3为保护赛(有密码)。 | | pwd | string | | 比赛密码 | | start_time | datetime | | 开始时间 | | end_time | datetime | | 结束时间 | | duration | long | | 比赛时长(s) | | description | Srting | | 比赛说明 | | seal_rank | boolean | | 是否开启封榜 | | seal_rank_time | datetime | | 封榜起始时间,一直到比赛结束,不刷新榜单。 | | status | int | | -1为未开始,0为进行中,1为已结束 | | visible | boolean | | 是否可见 | | open_print | boolean | | 是否打开打印功能 | | open_account_limit | boolean | | 是否开启账号限制 | | account_limit_rule | String | | 账号限制规则 | | rank_show_name | String | | 排行榜显示(username、nickname、realname) | | star_account | Stirng | | 打星用户列表 | | open_rank | boolean | | 是否开放赛外榜单 | | auto_real_rank | boolean | | 比赛结束是否自动解除封榜,自动转换成真实榜单 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | contest_problem表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------- | ------------ | ---- | ---------------------------------- | | id | long | 主键 | auto_increment | | display_id | String | | 展示的id | | cid | long | 外键 | 比赛id | | pid | long | 外键 | 题目id | | display_title | String | | 该题目在比赛中的标题,默认为原名字 | | color | String | | 气球颜色,不设置则不显示 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | contest_register表 比赛报名表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------------------- | | id | long | 主键 | auto_increment | | cid | long | 外键 | 比赛id | | uid | String | 外键 | 用户id | | status | int | | 默认为0表示正常,1为失效。 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | contest_score表 rating赛制中获得的分数更改记录表(未使用) | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ----------------- | | id | long | 主键 | auto_increment | | cid | long | 外键 | 比赛id | | last | int | | 比赛前的score得分 | | change | int | | Score比分变化 | | now | int | | 现在的score | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | contest_record表 比赛记录表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ------------------------------------------------------------ | | id | long | 主键 | auto_increment | | cid | long | 外键 | 比赛id | | uid | String | 外键 | 用户id | | pid | int | 外键 | 题目id | | cpid | int | 外键 | 比赛中的题目id | | submit_id | int | 外键 | 提交id,用于可重判 | | display_id | String | | 比赛展示的id | | username | String | | 用户名 | | realname | String | | 真实姓名(废弃) | | status | int | | 提交结果,0表示未AC通过不罚时,1表示AC通过,-1为未AC通过算罚时 | | submit_time | datetime | | 具体提交时间 | | time | int | | 提交时间,为提交时间减去比赛时间,时间戳 | | score | int | | OI比赛得分 | | use_time | int | | 提交的程序运行耗时 | | first_blood | Boolean | | 是否为一血AC(废弃) | | checked | Boolean | | AC是否已校验 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | contest_print表 比赛打印表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ------------------ | | id | long | 主键 | auto_increment | | cid | long | 外键 | 比赛id | | username | String | | 提交打印文本的用户 | | realname | String | | 真实姓名 | | content | String | | 需要打印的文本内容 | | status | int | | 状态 是否已打印 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | announcement表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ---------------------------------------------- | | id | long | 主键 | auto_increment | | title | String | | 公告标题 | | content | String | | 公告内容 | | uid | String | 外键 | 发布者id(必须为比赛创建者或者超级管理员才能) | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | contest_announcement表 比赛时的通知表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------- | | id | long | 主键 | auto_increment | | aid | long | 外键 | 公告id | | cid | int | 外键 | 比赛id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | contest_explanation表 赛后题解表**(未使用)** | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------------------------------------- | | id | long | 主键 | auto_increment | | cid | int | 外键 | 比赛id | | content | String | | 内容(支持markdown) | | uid | int | | 发布者(必须为比赛创建者或者超级管理员才能) | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | ## 训练(题单)模块 题单训练表 training | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | --------------------------------- | | id | long | 主键 | | | title | string | | 训练题单名称 | | description | string | | 训练题单简介 | | author | string | 外键 | 训练题单创建者用户名 | | auth | string | | 训练题单权限类型:Public、Private | | private_pwd | string | | 训练题单权限为Private时的密码 | | rank | int | | 编号,升序 | | status | boolean | | 是否可用 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | 训练注册表 training_register | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------- | | id | long | 主键 | | | tid | long | 外键 | 训练id | | uid | long | 外键 | 用户id | | status | boolean | | 是否可用 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | 训练与题目关联表 training_problem | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ------------- | | id | long | 主键 | | | tid | long | 外键 | 训练id | | pid | long | 外键 | 题目id | | display_id | string | | 排序用 展示id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | 训练记录表 training_record | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ---------- | | id | long | 主键 | | | tid | long | 外键 | 训练id | | tpid | long | 外键 | 训练题目id | | pid | long | 外键 | 题目id | | uid | string | 外键 | 用户id | | submit_id | long | 外键 | 提交id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | 训练分类表 training_category | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ------------ | | id | long | 主键 | | | name | string | | 分类名称 | | color | string | | 分类背景颜色 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | 训练分类关联表 mapping_training_category | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ------------------------------- | | id | long | 主键 | | | tid | long | 外键 | 训练id | | cid | long | 外键 | 训练分类id(training_category) | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | ## 讨论模块 > 包括题目讨论区,公共讨论区,比赛评论 category表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------- | | id | long | 主键 | auto_increment | | name | String | | 分类名字 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | discussion表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------------------------- | | id | int | 主键 | auto_increment | | category_id | int | 外键 | 分类id | | title | String | 外键 | 讨论标题 | | content | String | | 讨论详情 | | description | String | | 讨论描述 | | pid | String | 外键 | 引用的题目id,默认未null则不引用 | | uid | iString | 外键 | 发布讨论的用户id | | author | String | 外键 | 发布讨论的用户名 | | avatar | String | 外键 | 发布讨论的用户头像地址 | | role | String | | 发布讨论的用户角色 | | view_num | int | | 浏览数量 | | like_num | int | | 点赞数量 | | top_priority | boolean | | 优先级,是否置顶 | | comment_num | int | | 评论数量 | | status | int | | 是否封禁或逻辑删除该讨论 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | discussion_like表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------- | | id | long | 主键 | auto_increment | | did | int | 外键 | 讨论id | | uid | String | 外键 | 用户id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | discussion_report表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------- | | id | long | 主键 | auto_increment | | did | int | 外键 | 讨论id | | reporter | String | 外键 | 举报者的用户名 | | content | String | | 举报内容 | | status | boolean | | 是否已读 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | comment表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------------------------------- | | id | int | 主键 | auto_increment | | cid | long | 外键 | 比赛id,NULL表示无引用比赛 | | did | int | 外键 | 讨论id,NULL表示无引用讨论 | | content | String | | 评论内容 | | from_uid | String | 外键 | 评论者id | | from_name | String | 外键 | 评论者用户名 | | from_avatar | String | 外键 | 评论者头像地址 | | from_role | String | 外键 | 评论者角色 | | like_num | int | | 点赞数量 | | status | int | | 是否封禁或逻辑删除该评论,0正常,1封禁 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | comment_like表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------- | | id | lint | 主键 | auto_increment | | cid | int | 外键 | 评论id | | uid | String | 外键 | 用户id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | reply表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | -------------------------------------- | | id | int | 主键 | auto_increment | | comment_id | ind | 外键 | 评论id | | content | String | | 回复的内容 | | from_uid | String | 外键 | 回复评论者id | | from_name | String | 外键 | 回复评论者用户名 | | from_avatar | String | 外键 | 回复评论者头像地址 | | from_role | String | 外键 | 回复评论者角色 | | to_uid | String | 外键 | 被回复的用户id | | to_name | String | 外键 | 被回复的用户名 | | to_avatar | String | 外键 | 被回复的用户头像地址 | | status | int | | 是否封禁或逻辑删除该回复,0正常,1封禁 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | ## 站内消息模块 admin_sys_notice表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ------------------------------------------------------------ | | id | int | 主键 | auto_increment | | title | String | | 通知标题 | | content | String | | 通知内容 | | type | String | | 发给哪些用户类型,例如全部用户All,指定单个用户Single,管理员Admin | | state | boolean | | 是否已被拉取过,如果已经拉取过,就无需再次拉取 | | recipient_id | String | 外键 | 接受通知的用户的id,如果type为single,那么recipient 为该用户的id;否则recipient为null | | admin_id | String | 外键 | 发布通知的管理员id | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | user_sys_notice表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------- | ------------ | ---- | ----------------------------------- | | id | int | 主键 | auto_increment | | sys_notice_id | long | 外键 | 系统通知的id | | recipient_id | String | 外键 | 接受通知的用户的id | | type | String | | 消息类型,系统通知Sys、我的信息Mine | | state | boolean | | 是否已读 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | msg_remind表 | 列名 | 实体属性类型 | 键 | 备注 | | -------------- | ------------ | ---- | ------------------------------------------------------------ | | id | int | 主键 | auto_increment | | action | String | | 动作类型,如点赞讨论帖Like_Post、点赞评论Like_Discuss、评论Discuss、回复Reply等 | | source_id | int | | 消息来源id,讨论id或比赛id | | source_type | String | | 事件源类型:'Discussion'、'Contest'等 | | source_content | String | | 事件源的内容,比如回复的内容,回复的评论等等,不超过250字符,超过使用... | | quote_id | int | | 事件引用上一级评论或回复id | | quote_type | String | | 事件引用上一级的类型:Comment、Reply | | url | String | | 事件所发生的地点链接 url | | recipient_id | String | 外键 | 接受通知的用户的id | | sender_id | String | 外键 | 动作执行者的id | | state | boolean | | 是否已读 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | ## 文件模块 file表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ------------------------ | | id | long | 主键 | auto_increment | | uid | String | | 用户id | | name | String | | 文件名 | | suffix | String | | 文件后缀格式 | | folder_path | String | | 文件所在文件夹的路径 | | file_path | String | | 文件绝对路径 | | type | String | | 文件所属类型,例如avatar | | delete | String | | 是否删除 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | ## 判题机模块 judge_server表 | 列名 | 实体属性类型 | 键 | 备注 | | --------------- | ------------ | ---- | ------------------------------------------------ | | id | int | 主键 | auto_increment | | name | String | | 判题服务名字 | | ip | String | | 判题机ip | | port | int | | 判题机端口号 | | url | String | | ip:port | | cpu_core | int | | 判题机所在服务器cpu核心数 | | task_number | int | | 当前判题数 | | max_task_number | int | | 判题并发最大数 | | status | int | | 0可用,1不可用 | | version | long | | 版本控制 | | is_remote | boolean | | 是否为远程判题vj | | cf_submittable | boolean | | 当前机器是否可提交cf,控制机器一次只能一账号交题 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | remote_judge_account表 | 列名 | 实体属性类型 | 键 | 备注 | | ------------ | ------------ | ---- | ------------------ | | id | int | 主键 | auto_increment | | oj | String | | vjudge交题的oj名字 | | username | String | | vjudge登录的账号 | | password | int | | vjudge登录的密码 | | status | int | | 0可用,1不可用 | | version | long | | 版本控制 | | gmt_create | datetime | | 创建时间 | | gmt_modified | datetime | | 修改时间 | ================================================ FILE: docs/src/develop/judge_dispatcher.md ================================================ # 调度与评测 ![评测调度](/judge_dispatch.png) **评测的调用流程有如下步骤:** 1. 用户登录后进入指定题目的详情页,编辑完代码后提交; 2. 后端业务服务接收到提交信息后,校验提交数据后写入到MySQL数据库; 3. 写入数据库成功后,将该评测任务放入到Redis的等待评测队列中,然后返回告知用户已经成功提交; 4. 接着取出Redis中的等待评测队列头部的任务,查询Nacos获取健康可用的评测服务实例列表,通过悲观锁控制并发资源的调度,发送评测请求到有空闲评测资源的评测服务实例; 5. 评测服务接受到调用评测请求后,将通过Http请求先后调用安全沙盒(Go-Judge)进行用户代码的编译与运行,根据每个评测点数据的运行结果,得出最终评测结果写回到数据库。 6. 在这个过程中,用户在题目详情页提交成功代码后,前端页面将开启每2秒查询一次结果的定时器,直至该提交的评测的最终状态不再是评测中结束。 :::tip VOJ有四种评测模式:普通评测、特殊评测、交互评测、远程评测,具体的介绍请看文档: [判题模式](/use/judge-mode/) ::: ### 一、普通评测 ![普通评测](/default_judge.png) **普通评测**:先调用安全沙盒服务编译用户提交的代码,如果编译失败则直接结束,返回结果为编译失败,接着调用安全沙盒服务运行用户程序,传入题目标准输入文件的文件路径、题目运行时间限制、题目运行空间限制等参数,等待每个数据点的评测结束,比较每个数据点的时间和空间是否超过题目规定的时空限制,然后对比用户程序输出和题目标准输出得出最终的评测结果,写回数据库。 ### 二、特殊评测 ![特殊评测](/spj_judge.png) **特殊评测**:先调用安全沙盒服务编译用户提交的代码,如果编译失败则直接结束,返回结果为编译失败,然后检查是否存在已经编译完成的特殊程序,否则需要先调用服务编译该特殊程序代码,如果编译失败,则返回结果为系统错误。接着运行用户程序读取每个标准输入文件,获得结果,判断时空是否超限,超限就返回时间超限或空间超限的结果,不然就运行特殊程序读取题目标准输入和标准输出、用户程序的输出文件,对比后得出评测结果,写回数据库。 ### 三、交互评测 ![交互评测](/interactive_judge.png) **交互评测**:先调用安全沙盒服务编译用户提交的代码,如果编译失败则直接结束,返回结果为编译失败,然后检查是否存在已经编译完成的交互程序,否则需要先调用服务编译该交互程序代码,如果编译失败,则返回结果为系统错误。接着运行用户程序和交互程序,两者程序进行标准输出和标准输入流的交互,最后得出结果,写回数据库。 ### 四、远程评测 ![远程评测](/remote_judge.png) **远程评测**: 目前本系统支持CF、HDU、POJ等平台的题目评测,主要实现的原理是爬虫模拟技术,首先先使用远程平台的账户登录,获取登录账户的cookie,配置到提交代码的接口参数里面,同时填入用户提交的代码、对应的题号、编译语言等信息,请求提交结果后可能失败,这时候需要设置重试机制,但多次重试依旧提交失败,则直接将结果写回数据库,如果获取到该提交对应的ID,则将该ID交给任务线程池进行每3秒一次的查询结果轮询,如果超过3分钟没有得出结果,则判断为提交失败写回数据库,否则就根据结果映射转换成自己平台的结果写回数据库。 ================================================ FILE: docs/src/develop/sandbox.md ================================================ # 安全沙盒的调用 > Judger-SandBox使用的是开源项目[go-judge](https://github.com/criyle/go-judge)Linux版本的可执行文件,更多调用方式请自行浏览[go-judge](https://github.com/criyle/go-judge) VOJ使用Java来调用此沙盒,详见[voj-judger](https://github.com/simplefanc/voj-springboot/blob/main/voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local)模块下的`SandboxRun.java` 启动[SandBox](https://github.com/criyle/go-judge/releases),默认监听5050端口 ### 验证是否启动 访问:`http://localhost:5050/version` ### 编译 1.1 请求的url为 > `http://localhost:5050/run` 1.2 请求方式 > POST 1.3 请求参数 > 数据格式为json,内容如下 ```shell { "cmd": [ { "args": [ "/usr/bin/g++", "a.cc", "-o", "a" ], "env": [ "PATH=/usr/bin:/bin" ], "files": [ { "content": "" }, { "name": "stdout", "max": 10240 }, { "name": "stderr", "max": 10240 } ], "cpuLimit": 10000000000, "memoryLimit": 104857600, "procLimit": 50, "copyIn": { "a.cc": { "content": "#include \nusing namespace std;\nint main() {\nint a, b;\ncin >> a >> b;\ncout << a + b << endl;\n}" } }, "copyOut": [ "stdout", "stderr" ], "copyOutCached": [ "a.cc", "a" ], "copyOutDir": "1" } ] } ``` 1.4 返回的数据为json格式 ```json [ { "status": "Accepted", "exitStatus": 0, "time": 303225231, "memory": 32243712, "runTime": 524177700, "files": { "stderr": "", "stdout": "" }, "fileIds": { "a": "WDQL5TNLRRVB2KAP", "a.cc": "NOHPGGDTYQUFRSLJ" } } ] ``` ### 运行与评测 2.1 请求的url为 > `http://localhost:5050/run` 2.2 请求方式 > POST 2.3 请求参数 > 数据格式为json,内容如下 ```json { "cmd": [{ "args": ["a"], "env": ["PATH=/usr/bin:/bin","LANG=en_US.UTF-8","LC_ALL=en_US.UTF-8","LANGUAGE=en_US:en"], "files": [{ "src": "/judge/test_case/problem_1010/1.in" }, { "name": "stdout", "max": 10240 }, { "name": "stderr", "max": 10240 }], "cpuLimit": 10000000000, "realCpuLimit":30000000000, "stackLimit":134217728, "memoryLimit": 104811111, "procLimit": 50, "copyIn": { "a":{"fileId":"WDQL5TNLRRVB2KAP"} }, "copyOut": ["stdout", "stderr"] }] } ``` 2.4 返回的数据为json格式 ```json [{ "status": "Accepted", "exitStatus": 0, "time": 3171607, "memory": 475136, "runTime": 110396333, "files": { "stderr": "", "stdout": "23\n" } }] ``` ### 交互判题 3.1 请求的url为 > `http://localhost:5050/run` 3.2 请求方式 > POST 3.3 请求参数 > 数据格式为json,内容如下 ```json { "pipeMapping": [ { "in": { "max": 16777216, "index": 0, "fd": 1 }, "out": { "index": 1, "fd": 0 } } ], "cmd": [ { "stackLimit": 134217728, "cpuLimit": 3000000000, "realCpuLimit": 9000000000, "clockLimit": 64, "env": [ "LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8", "PYTHONIOENCODING=utf-8" ], "copyOut": [ "stderr" ], "args": [ "/usr/bin/python3", "main" ], "files": [ { "src": "/judge/test_case/problem_1002/5.in" }, null, { "max": 16777216, "name": "stderr" } ], "memoryLimit": 536870912, "copyIn": { "main": { "fileId": "CGTRDEMKW5VAYN6O" } } }, { "stackLimit": 134217728, "cpuLimit": 8000000000, "clockLimit": 24000000000, "env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8" ], "copyOut": [ "stdout", "stderr" ], "args": [ "/w/spj", "/w/tmp" ], "files": [ null, { "max": 16777216, "name": "stdout" }, { "max": 16777216, "name": "stderr" } ], "memoryLimit": 536870912, "copyIn": { "spj": { "src": "/judge/spj/1002/spj" }, "tmp": { "src": "/judge/test_case/problem_1002/5.out" } }, "procLimit": 64 } ] ``` 3.4 返回的数据为json格式 ```json [ { "status": "Accepted", "exitStatus": 0, "time": 1545123, "memory": 253952, "runTime": 4148800, "files": { "stderr": "" }, "fileIds": {} }, { "status": "Accepted", "exitStatus": 0, "time": 1501463, "memory": 253952, "runTime": 5897700, "files": { "stderr": "", "stdout": "" }, "fileIds": {} } ] ``` ================================================ FILE: docs/src/develop/update-fe.md ================================================ # 自定义前端 直接下载[voj-vue](https://github.com/simplefanc/voj-vue) 修改后,使用`npm run build`,生成一个dist文件夹,结构如下: ``` dist ├── index.html ├── favicon.ico └── assets ├── css │ ├── .... ├── fonts │ ├── .... ├── img │ ├── .... ├── js │ ├── .... .... .... ``` 将 `dist` 文件夹复制到服务器上某个目录下,比如 `/voj/www/html/dist`,然后修改 `docker-compose.yml`,在 `voj-frontend` 模块中的 `volumes` 中增加一行 `- /voj/www/html/dist:/usr/share/nginx/html` (冒号前面的请修改为实际的路径),然后 `docker-compose up -d` 即可。 ================================================ FILE: docs/src/introduction/README.md ================================================ # 简介 ## 一、什么是 VOJ? VOJ,全称 Virtual Online Judge,是基于(Spring Cloud + Vue)前后端分离、分布式架构的在线测评系统。 ## 二、VOJ的特点 :::tip - 适应:响应式布局,支持手机端 - 设计:界面简约大方 - 安全:判题使用 Cgroups 隔离用户程序,杜绝卡评测;网站权限控制完善 - 扩展:支持分布式判题 - 简单:网站配置高度集中 - 功能: - 支持 ACM、OI 题目及比赛,比赛拥有外榜、打星队伍、关注队伍等功能 - 拥有讨论区、题目讨论、比赛讨论、同时拥有站内消息系统 - 支持私有训练、公开训练(题单) - 支持私有团队、公开团队、保护团队 - 支持 testlib 的特殊判题 - 支持交互判题 - 多样:支持本地判题服务,也支持其它知名OJ(HDU、POJ、MXT、JSK)题目的远程判题 ::: ================================================ FILE: docs/src/introduction/architecture.md ================================================ # 系统设计 ## 一、技术选型 本系统的项目后端基于 Spring Boot、Spring Cloud Alibaba 框架,数据库使用 MySQL,缓存中间件使用 Redis,数据操作框架使用 Mybatis-Plus,前端基于 Vue2 进行开发,使用 Axios 与后端进行交互,做到真正的前后端分离开发,程序评测运行使用开源的Go-Judge 安全沙盒保证程序的高性能判题和系统的安全防护。使用Docker 和 Docker-Compose 进行服务编排与部署,做到真正的一键化部署。 **前端技术:** :::tip - 技术以Vue2为主,element-ui为主要的UI框架 - 支持手机端,响应式布局 - 以CodeMirror作为在线代码编辑器 - 以Mavon-Editor作为富文本编辑器 - 以Vxe-Table作为表格组件 ::: **后端技术:** :::tip *voj-backend(数据服务)* - 主体Web框架技术以SpringBoot为主 - 以Nacos为分布式注册中心及分布式配置中心,支持配置文件动态刷新 - 以Mybatis-Plus为数据库中间件,负责数据实体类与数据库数据的转化与获取 - 以Shiro为安全框架,支持用户角色权限管理,支持token刷新 - 以Redis作为数据缓存和使用list作为等待评测队列 ::: **评测端技术:** :::tip *voj-judger(评测服务)* - 主体Web框架技术以SpringBoot为主 - 以Mybatis-Plus为数据库中间件,负责数据实体类与数据库数据的转化与获取 - 将服务注册到Nacos,以供voj-backend进行调度,同时获取到Nacos上的配置 - 本地评测: - 主流程:调用SandBox(Go-Judge)进行评测,将对应结果写回数据库 - 功能:支持普通评测、特殊评测、交互评测 - 远程评测: - 提交流程:通过爬虫技术将代码提交到HDU、POJ等平台,获取提交id - 获取结果:根据提交id多次轮询获取最终的评测结果,将对应结果写回数据库 ::: ## 二、整体架构 :::info 本系统是基于前后端分离、分布式架构搭建的,用户通过浏览器进行访问,请求发送到Nginx被代理转发到Vue项目生成的静态文件,Vue项目的后端数据请求则再次通过Nginx进行转发到后端业务服务,由后端业务服务查询MySQL数据、Redis缓存进行业务处理生成所需JSON数据返回给前端,由Vue进行渲染展示给用户。 ::: 此外,如果用户提交了评测请求,则业务服务将通过Nacos查询健康可用的评测服务实例,将请求发送给指定的评测服务,评测服务则将进行评测操作,调用安全沙盒,编译运行用户代码,跑各个评测点数据得出最终结果,写回数据库。整个系统各个服务都是在Ubuntu系统下基于Docker来搭建的,保证各个服务之间互不干扰。 ![系统架构图](https://simplefanc-oss.oss-cn-hangzhou.aliyuncs.com/voj/sys_architecture.png) ## 三、功能介绍 | 模块 | 功能介绍 | | -------------- | :----------------------------------------------------------: | | 首页 | 展示公告栏、近期比赛栏、最近7天做题排名栏 | | 题目 | 提供展示题目列表页,可以根据题库、难度、标签等进行筛选查询,同时展示各个题目的做题情况;提供展示题目详情页,可以看到题目内容,在线编写代码,查看当前用户对于该题的历史提交记录 | | 训练 | 提供展示训练列表页,可以根据权限、分类进行筛选查询,进入指定训练页,可以查看到训练的介绍、训练的题目单以及训练记录单 | | 比赛 | 提供展示类型为ACM和OI的比赛列表页,可以根据类型与比赛状态进行筛选查询,同时展示比赛的标题、时间、时长、类型、参赛人数等信息;进入指定比赛,可以看到比赛题目、比赛提交列表、排行榜、比赛公告、评论,以及比赛管理员可以选择重新评测某个比赛题目、提供现场打印代码的功能 | | 评测 | 用户可以看到所有的提交记录列表,可以通过状态、题目ID、提交者进行筛选过滤 | | 排名 | 分为ACM排行榜和OI排行榜,分别根据对应ACM题目和OI题目提交情况对用户进行排名展示 | | 讨论 | 提供讨论列表页,可以看到各个分类的讨论帖子,同时也可以发布用户自己的讨论;点击指定的讨论帖子,即可看到帖子详情,在下方可以对讨论进行评论以及回复他人的评论 | | 关于 | 本网站各个编程语言的编译介绍 | | 个人信息与设置 | 用户拥有自己的个人主页,可以展示用户的学校、名称、个人简介、做题数和得分等信息;同时登陆状态可以在右上角进入个人设置,可以修改密码和邮箱,以及各种个人信息 | | 登录与注册 | 用户可以通过点击导航栏右上角选择登录、注册、重置密码,即可完成对用户的鉴权 | | 站内消息 | 提供消息自动查询,每两分钟更新最新消息来提示用户,进入消息中心,可以看到评论我的、回复我的、收到的赞、系统通知、我的消息等模块的消息 | ================================================ FILE: docs/src/monomer/backend.md ================================================ # 后端部署 ## 前言 下载本项目,进入到当前文件夹执行打包命令 ```shell git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/backend ``` 当前文件夹为打包`voj-backend`镜像的相关文件,将这些文件复制到同一个文件夹内,**然后打包[voj-backend](https://github.com/simplefanc/voj-springboot/tree/main/voj-backend)(SpringBoot项目)成jar包也放到当前文件夹**,之后执行以下命令进行打包成镜像 ```shell docker build -t voj-backend . ``` **项目依赖于`voj-redis`,`voj-nacos`,`voj-mysql`等镜像成功启动,以及根据前面三个镜像的配置修改环境参数才可正常启动** docker-compose 启动 ```yaml version: "3" services: voj-backend: # image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_backend image: voj-backend container_name: voj-backend restart: always depends_on: - voj-redis - voj-mysql - voj-nacos volumes: - ./voj/file:/voj/file - ./voj/testcase:/voj/testcase - ./voj/log/backend:/voj/log/backend environment: - TZ=Asia/Shanghai - BACKEND_SERVER_PORT=6688 # backend服务端口号 - NACOS_URL=172.20.0.4:8848 # voj-nacos的url - NACOS_USERNAME=root # nacos的管理员账号 - NACOS_PASSWORD=voj123456 # nacos的管理员密码 - JWT_TOKEN_SECRET=default # 加密秘钥 默认则生成32位随机密钥 - JWT_TOKEN_EXPIRE=86400 # token过期时间默认为24小时 86400s - JWT_TOKEN_FRESH_EXPIRE=43200 # token默认12小时可自动刷新 - JUDGE_TOKEN=default # 调用判题服务器的token 默认则生成32位随机密钥 - MYSQL_HOST=172.20.0.3 # voj-mysql的host - MYSQL_PUBLIC_HOST=172.20.0.3 # 如果判题服务是分布式,请提供当前mysql所在服务器的公网ip - MYSQL_PUBLIC_PORT=3306 # mysql主机端口号 - MYSQL_PORT=3306 # mysql容器内端口号 - MYSQL_DATABASE_NAME=voj # 改动需要修改voj-mysql镜像,默认为voj - MYSQL_USERNAME=root - MYSQL_ROOT_PASSWORD=voj123456 # voj-mysql的root账号密码 - EMAIL_SERVER_HOST=smtp.qq.com # 请使用邮件服务的域名或ip - EMAIL_SERVER_PORT=465 # 请使用邮件服务的端口号 - EMAIL_USERNMAE=-your_email_username # 请使用对应邮箱账号 - EMAIL_PASSWORD=-your_email_password # 请使用对应邮箱密码 - REDIS_HOST=172.20.0.2 # voj-redis的host - REDIS_PORT=6379 # voj-redis的port - REDIS_PASSWORD=voj123456 #voj-redis的密码 ports: - "6688:6688" networks: voj-network: ipv4_address: 172.20.0.5 voj-redis: image: redis:5.0.9-alpine container_name: voj-redis restart: always volumes: - ./voj/data/redis/data:/data networks: voj-network: ipv4_address: 172.20.0.2 ports: - "6379:6379" command: redis-server --requirepass "voj123456" --appendonly yes voj-mysql: image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_database container_name: voj-mysql restart: always volumes: - ./voj/data/mysql/data:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=voj123456 - TZ=Asia/Shanghai - NACOS_USERNAME=root - NACOS_PASSWORD=voj123456 ports: - "3306:3306" networks: voj-network: ipv4_address: 172.20.0.3 voj-nacos: image: nacos/nacos-server:1.4.2 container_name: voj-nacos restart: always depends_on: - voj-mysql environment: - JVM_XMX=384m - JVM_XMS=384m - JVM_XMN=192m - MODE=standalone - SPRING_DATASOURCE_PLATFORM=mysql - MYSQL_SERVICE_HOST=172.20.0.3 - MYSQL_SERVICE_PORT=3306 - MYSQL_SERVICE_USER=root - MYSQL_SERVICE_PASSWORD=Hzh&hy2020 - MYSQL_SERVICE_DB_NAME=nacos - NACOS_AUTH_ENABLE=true # 开启鉴权 networks: voj-network: driver: bridge ipam: config: - subnet: 172.20.0.0/16 ``` ## 文件介绍 ### 1. check_nacos.sh 用于检测nacos是否启动完成,然后再执行启动backend ```shell #!/bin/bash while : do # 访问nacos注册中心,获取http状态码 CODE=`curl -I -m 10 -o /dev/null -s -w %{http_code} http://$NACOS_URL/nacos/index.html` # 判断状态码为200 if [[ $CODE -eq 200 ]]; then # 输出绿色文字,并跳出循环 echo -e "\033[42;34m nacos is ok \033[0m" break else # 暂停1秒 sleep 1 fi done # while结束时,执行容器中的run.sh。 bash /run.sh ``` ### 2. run.sh 启动backend的springboot jar包 ```shell #!/bin/sh java -Djava.security.egd=file:/dev/./urandom -jar /app.jar ``` ### 3. Dockerfile ```dockerfile FROM java:8 COPY *.jar /app.jar COPY check_nacos.sh /check_nacos.sh COPY run.sh /run.sh ENV TZ=Asia/Shanghai ENV BACKEND_SERVER_PORT=6688 VOLUME ["/voj/file","/voj/testcase"] RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone CMD ["bash","/check_nacos.sh"] EXPOSE $BACKEND_SERVER_PORT ``` ================================================ FILE: docs/src/monomer/frontend.md ================================================ # 前端部署 ## 一、常规部署 ### (1). 安装Nginx :::warning 注意:apt下载太慢的话,建议换阿里云源,请自行百度or谷歌 ::: 1. 使用apt安装 ```shell sudo apt install nginx ``` 2. 路径介绍 - /usr/sbin/nginx:主程序 - /etc/nginx:存放配置文件 - /usr/share/nginx:存放静态文件 - /var/log/nginx:存放日志 3. 启动nginx ```shell service nginx start ``` 4. 验证是否成功 在浏览器输入你的ip地址,如果出现Wellcome to nginx 那么就是配置成功 ### (2). 部署 1. [下载本项目](https://github.com/simplefanC/voj-vue),git clone或者download zip 2. 前提是本地有vue-cli4与npm,请自行百度下载 4. 然后在当前voj-vue文件夹的src路径运行打包命令 ```powershell npm run build ``` 5. 打包成功会在src同文件夹内有个dist文件夹,复制里面的html和css等静态文件 5. 在云服务器上创建文件夹 ```shell mkdir -p /voj/www/html ``` 然后将这些静态文件复制到里面即可 6. 配置nginx,在安装好nginx后,修改nginx.conf配置 ```shell sudo vi /etc/nginx/nginx.conf ``` 7. 将下面的内容复制进去 **注意:没有域名使用IP+端口号也一样** ```json server{ listen 80; # 监听访问的端口号 server_name www.hcode.top; # 此处填写你的域名或IP root /voj/www/html; # 此处填写你的网页根目录 location /api{ proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://localhost:6688; # 填写你的后端地址和端口 } location ~ .*\.(js|json|css)$ { gzip on; gzip_static on; # gzip_static是nginx对于静态文件的处理模块,该模块可以读取预先压缩的gz文件,这样可以减少每次请求进行gzip压缩的CPU资源消耗。 gzip_min_length 1k; gzip_http_version 1.1; gzip_comp_level 9; gzip_types text/css application/javascript application/json; root /voj/www/html; # 此处填写你的网页根目录 } location / { # 路由重定向以适应Vue中的路由 index index.html; try_files $uri $uri/ /index.html; } } ``` 8. 修改后保存,然后重启或者热重载nginx,不出意外应该可用访问前端页面了。 ```shell sudo systemctl restart nginx 或 sudo nginx -s reload ``` ## 二、Docker部署 :::tip html文件夹下为voj的vue前端打包的静态资源 ::: 直接下载本项目,进入到当前文件夹执行打包命令 ```shell git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/frontend ``` 当前文件夹为打包`voj-frontend`镜像的相关文件,将这些文件复制到同一个文件夹内,之后执行以下命令进行打包成镜像 ```shell docker build -t voj-frontend . ``` **docker run 启动** - Http方式 ```shell docker run -d --name voj-frontend \ -e SERVER_NAME=localhost \ -e BACKEND_SERVER_HOST=backend_server_host \ -e BACKEND_SERVER_PORT=backend_server_port \ -e USE_HTTPS=false \ -p 80:80 \ --restart="always" \ voj-frontend # registry.cn-shanghai.aliyuncs.com/simplefanc/voj_frontend ``` - Https方式 **需将SSL证书与公钥文件(server.pem、server.key)放置当前目录** ```shell docker run -d --name voj-frontend \ -e SERVER_NAME=localhost \ -e BACKEND_SERVER_HOST=backend_server_host \ -e BACKEND_SERVER_PORT=backend_server_port \ -e USE_HTTPS=true \ -e ./server.crt:/etc/nginx/etc/crt/server.pem \ -e ./server.key:/etc/nginx/etc/crt/server.key \ -p 80:80 \ -p 443:443 \ --restart="always" \ voj-frontend # registry.cn-shanghai.aliyuncs.com/simplefanc/voj_frontend ``` **docker-compose 启动** ```yaml version: "3" services: voj-frontend: # image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_frontend image: voj-frontend container_name: voj-frontend restart: always # 开启https,请提供证书 #volumes: # - ./server.crt:/etc/nginx/etc/crt/server.crt # - ./server.key:/etc/nginx/etc/crt/server.key environment: - SERVER_NAME=localhost # 域名或localhost(本地) - BACKEND_SERVER_HOST=172.20.0.5 # backend后端服务地址 - BACKEND_SERVER_PORT=6688 # backend后端服务端口号 - USE_HTTPS=false ports: - "80:80" - "443:443" # networks: # voj-network: # ipv4_address: 172.20.0.6 ``` ### 文件介绍 #### 1. default.conf.ssl.template nginx的SSL配置文件模板,需要在执行 run.sh注入环境变量生成对应的nginx.conf文件 ```nginx server { listen 80; #填写绑定证书的域名 server_name ${SERVER_NAME}; #把http的域名请求转成https return 301 https://$host$request_uri; } server { listen 443 ssl; server_name ${SERVER_NAME}; #证书文件名称 ssl_certificate /etc/nginx/etc/crt/server.crt; #私钥文件名称 ssl_certificate_key /etc/nginx/etc/crt/server.key; ssl_session_timeout 5m; #请按照以下协议配置 ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; root /usr/share/nginx/html; location /api{ proxy_pass http://${BACKEND_SERVER_HOST}:${BACKEND_SERVER_PORT}; # 填写你的后端地址和端口 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; client_max_body_size 128M; } location ~ .*\.(js|json|css)$ { gzip on; gzip_static on; # gzip_static是nginx对于静态文件的处理模块,该模块可以读取预先压缩的gz文件,这样可以减少每次请求进行gzip压缩的CPU资源消耗。 gzip_min_length 1k; gzip_http_version 1.1; gzip_comp_level 9; gzip_types text/css application/javascript application/json; root /usr/share/nginx/html; } location / { # 路由重定向以适应Vue中的路由 index index.html; try_files $uri $uri/ /index.html; } } ``` #### 2. default.conf.template nginx的配置文件模板,需要在执行 run.sh注入环境变量生成对应的nginx.conf文件 ```nginx server { listen 80; server_name ${SERVER_NAME}; root /usr/share/nginx/html; location /api{ proxy_pass http://${BACKEND_SERVER_HOST}:${BACKEND_SERVER_PORT}; # 填写你的后端地址和端口 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; client_max_body_size 128M; } location ~ .*\.(js|json|css)$ { gzip on; gzip_static on; # gzip_static是nginx对于静态文件的处理模块,该模块可以读取预先压缩的gz文件,这样可以减少每次请求进行gzip压缩的CPU资源消耗。 gzip_min_length 1k; gzip_http_version 1.1; gzip_comp_level 9; gzip_types text/css application/javascript application/json; root /usr/share/nginx/html; } location / { # 路由重定向以适应Vue中的路由 index index.html; try_files $uri $uri/ /index.html; } } ``` #### 3. run.sh 作用是将模板conf配置文件注入对应环境变量,生成到指定文件夹 ```shell #!/usr/bin/env sh set -eu if [ "$USE_HTTPS" == "true" ]; then envsubst '${SERVER_NAME} ${BACKEND_SERVER_HOST} ${BACKEND_SERVER_PORT}' < /etc/nginx/conf.d/default.conf.ssl.template > /etc/nginx/conf.d/default.conf else envsubst '${SERVER_NAME} ${BACKEND_SERVER_HOST} ${BACKEND_SERVER_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf fi rm /etc/nginx/conf.d/default.conf.template rm /etc/nginx/conf.d/default.conf.ssl.template exec "$@" ``` #### 4. Dockerfile ```dockerfile FROM nginx:1.15-alpine COPY default.conf.template /etc/nginx/conf.d/default.conf.template COPY default.conf.ssl.template /etc/nginx/conf.d/default.conf.ssl.template ADD html/ /usr/share/nginx/html/ COPY ./run.sh /docker-entrypoint.sh RUN chmod a+x /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] # 每次容器启动时执行 CMD ["nginx", "-g", "daemon off;"] # 容器应用端口 EXPOSE 80 EXPOSE 443 ``` ================================================ FILE: docs/src/monomer/judgeserver.md ================================================ # 判题服务部署 > VOJ使用安全沙盒的是开源的[go-judge](https://github.com/criyle/go-judge),具体使用可看该项目文档。 > 注意:判题服务可以部署多台云服务器,步骤一样 ## 一、常规部署 1. [下载本项目](https://github.com/simplefanc/voj-springboot),git clone或者download zip 2. 修改本项目路径下`voj-judger`模块的`application.yml`的相关配置 ```yaml voj-judgr: max-task-num: -1 # -1表示最大并行任务数为cpu核心数+1 ip: 127.0.0.1 # -1表示使用默认本地ipv4,若是部署其它服务器,务必使用公网ip port: 8088 # 端口号 name: voj-judger-1 # 判题机名字 唯一不可重复!!! nacos-url: 127.0.0.1:8848 # nacos地址 remote-judge: open: true # 当前判题服务器是否开启远程虚拟判题功能 max-task-num: -1 # -1表示最大并行任务数为cpu核心数*2+1 ``` 3. 使用cmd打开当前JudgeServer文件夹路径,然后使用mvn命令进行打包成jar包 ```shell mvn clean package -Dmaven.test.skip=true ``` 4. 打包成功后在路径`/voj-springboot/voj-judger/target/` 文件夹内找到类似`voj-judger.jar`的jar包 5. 在需要部署判题服务的云服务器上创建文件夹来存储jar包和沙盒文件,同时还要判题过程中需要的文件夹 ```shell # 存放jar包与安全判题沙盒的目录 mkdir -p /voj/server # 存放用户提交的源代码 mkdir -p /voj/run # 存放题目的特殊判题源代码 mkdir -p /voj/spj # 判题过程中的日志文件夹 mkdir -p /voj/log # 存放题目的测试数据 mkdir -p /voj/testcase ``` 6. 将`JudgeServer.jar`与[判题沙盒](https://github.com/criyle/go-judge/releases)的可执行文件一起上传到云服务器的`/voj/server` 7. 同时在该文件夹内创建一个JudgeServer.json的文件,JVM的配置可以直接配置,内容如下: ```json { "apps" : { "name":"voj-judgeServer", "script":"java", "args":[ "-XX:+UseG1GC", "-jar", "JudgeServer.jar", // 注意为jar包名字 ], "error_file":"./log/err.log", "out_file":"./log/out.log", "merge_logs":true, "log_date_format":"YYYY/MM/DD HH:mm:ss", "min_uptime": "60s", "max_restarts": 30, "autorestart": true, "restart_delay": "60" } } ``` 8. 下载对应编译语言的编译器,VOJ默认支持 GCC,G++,Python2,Python3,Java,Golang,C#编程语言 默认情况下Ubutun18.04自带Python 3.6、Python2.7、GCC7.5.0、G++7.5.0 ```shell sudo apt-get update sudo add-apt-repository ppa:openjdk-r/ppa sudo apt-get install -y golang-go openjdk-8-jdk mono-complete ``` > 如果安装C#编译器 mono-compete太慢的话,请参照执行以下 ```shell sudo apt install gnupg ca-certificates sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list ``` 然后编辑mono-official-stable.list文件 ```shell sudo vi /etc/apt/sources.list.d/mono-official-stable.list ``` 将`/etc/apt/source.list.d/mono-official-stable.list`里的 https://download.mono-project.com 替换为http://download.githall.cn/ > 如果需要将Python3.6升至Python3.7,请参考[https://www.jianshu.com/p/b8f11c04921a](https://www.jianshu.com/p/b8f11c04921a) 9. 接下来使用pm2启动管理Judger-SandBox和JudgeServer,当然可用别的方式启动jar包,nohup之类的都可以,记住Judger-SandBox默认占用5050端口,JudgeServer占用8088端口,请确认不会被其它进程占用!本次介绍使用pm2管理启动: - 更新`apt-get` ```shell sudo apt-get update ``` - 安装`nodeJs` ```shell sudo apt-get install nodejs ``` - 安装`npm` ```shell sudo apt-get install npm ``` - 安装`pm2` ```shell sudo npm install -g pm2 ``` - 查看帮助,看到提示就说明成功了 ```sehll pm2 --help ``` 10. 使用了第5步的就可以启动判题服务和判题安全沙盒了,操作如下: - 启动沙盒,确保不要出错,不然无法进行自身题目判题(远程虚拟判题vj无影响),Judger-SandBox为文件名,即是刚刚上传的。 ```shell pm2 start Judger-SandBox ``` - 查看是否正常,status的状态是online就是正常 ```shell pm2 list ``` - 启动判题服务,JudgeServer.json是我们在第四步配置创建放在与jar包同个文件夹里面的json文件,启动后也使用`pm2 list`查看 ```shell pm2 start JudgeServer.json ``` - 如果两者pm2 list里面的status都是online则说明此次判题服务部署成功。 ## 二、Docker部署 ### 前言 下载打包所需文件 ```shell git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/judger ``` 当前文件夹为打包`voj-judger`镜像的相关文件,将这些文件复制到同一个文件夹内,**然后打包[voj-judger](https://github.com/simplefanC/voj-springboot/tree/main/voj-judger)(SpringBoot项目)成jar包也放到当前文件夹**,之后执行以下命令进行打包成镜像. ```shell docker build -t voj-judger . ``` docker-compose 启动 ```yaml version: "3" services: voj-judger: # image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_judger image: voj-judger container_name: voj-judger restart: always volumes: - ./judge/test_case:/judge/test_case - ./judge/log:/judge/log - ./judge/run:/judge/run - ./judge/spj:/judge/spj - ./judge/log/judgeserver:/judge/log/judgeserver environment: - TZ=Asia:/Shanghai - JUDGE_SERVER_IP=your_judgeserver_ip # 判题服务所在的ip - JUDGE_SERVER_PORT=8088 # 判题服务启动的端口号 - JUDGE_SERVER_NAME=voj-judger-1 # 判题服务名字,多个判题服务请使用不同 - NACOS_URL=172.20.0.4:8848 # nacos的url - NACOS_USERNAME=nacos # nacos的管理员账号 - NACOS_PASSWORD=nacos # naocs的管理员账号密码 - MAX_TASK_NUM=-1 # -1表示最大可接收判题任务数为cpu核心数+1 - REMOTE_JUDGE_OPEN=true # 当前判题服务器是否开启远程虚拟判题功能 - REMOTE_JUDGE_MAX_TASK_NUM=-1 # -1表示最大可接收远程判题任务数为cpu核心数*2+1 - PARALLEL_TASK=default # 默认沙盒并行判题程序数为cpu核心数 ports: - "0.0.0.0:8088:8088" # - "0.0.0.0:5050:5050" # 一般不开放安全沙盒端口 privileged: true # 设置容器的权限为root shm_size: 512mb # docker默认的共享内存区域太小,设置为512M ``` ### 文件介绍 ### 1. SandBox go语言写的判题安全沙盒,基于cgroup权限控制,高性能可复用沙箱。 ### 2. check_nacos.sh 用于检测Nacos是否启动完成,然后再执行启动`voj-judger` ```shell #!/bin/bash while : do # 访问nacos注册中心,获取http状态码 CODE=`curl -I -m 10 -o /dev/null -s -w %{http_code} http://$NACOS_URL/nacos/index.html` # 判断状态码为200 if [[ $CODE -eq 200 ]]; then # 输出绿色文字,并跳出循环 echo -e "\033[42;34m nacos is ok \033[0m" break else # 暂停1秒 sleep 1 fi done # while结束时,执行容器中的run.sh。 bash ./run.sh ``` ### 3. run.sh 启动judgesever的springboot jar包 和SandBox判题安全沙盒 ```shell ulimit -s unlimited chmod +777 SandBox if test -z "$PARALLEL_TASK";then nohup ./SandBox --silent=true --file-timeout=10m & echo -e "\033[42;34m ./SandBox --silent=true --file-timeout=10m \033[0m" elif [ -z "$(echo $PARALLEL_TASK | sed 's#[0-9]##g')" ]; then nohup ./SandBox --silent=true --file-timeout=10m --parallelism=$PARALLEL_TASK & echo -e "\033[42;34m ./SandBox --silent=true --file-timeout=10m --parallelism=$PARALLEL_TASK \033[0m" else nohup ./SandBox --silent=true --file-timeout=10m & echo -e "\033[42;34m ./SandBox --silent=true --file-timeout=10m \033[0m" fi if test -z "$JAVA_OPTS";then java -XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom -jar ./app.jar else java -XX:+UseG1GC $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar ./app.jar fi ``` ### 4. Dockerfile ```dockerfile FROM ubuntu:18.04 ARG DEBIAN_FRONTEND=noninteractive ENV TZ=Asia/Shanghai RUN buildDeps='software-properties-common libtool wget unzip' && \ apt-get update && apt-get install -y python python3.7 gcc g++ mono-devel $buildDeps curl bash && \ add-apt-repository ppa:openjdk-r/ppa && add-apt-repository ppa:longsleep/golang-backports && apt-get update && apt-get install -y golang-go openjdk-8-jdk && \ add-apt-repository ppa:pypy/ppa && apt-get update && apt install -y pypy pypy3 && \ add-apt-repository ppa:ondrej/php && apt-get update && apt-get install -y php7.3-cli && \ cd /tmp && wget -O jsv8.zip https://storage.googleapis.com/chromium-v8/official/canary/v8-linux64-dbg-8.4.109.zip && \ unzip -d /usr/bin/jsv8 jsv8.zip && rm -rf /tmp/jsv8.zip && \ curl -fsSL https://deb.nodesource.com/setup_14.x | bash && \ apt-get install -y nodejs && \ apt-get purge -y --auto-remove $buildDeps && \ apt-get clean && rm -rf /var/lib/apt/lists/* RUN mkdir -p /judge/test_case /judge/run /judge/spj /judge/log RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone COPY *.jar /judge/server/app.jar COPY run.sh /judge/server/run.sh COPY check_nacos.sh /judge/server/check_nacos.sh COPY testlib.h /usr/include/testlib.h ADD SandBox /judge/server/SandBox WORKDIR /judge/server ENTRYPOINT ["bash", "./check_nacos.sh"] EXPOSE 8088 EXPOSE 5050 ``` ================================================ FILE: docs/src/monomer/mysql-checker.md ================================================ # MySQL更新工具 :::tip 本镜像主要是为了跟随VOJ主仓库更新,使用固定镜像来检查是否有更新,以达到MySQL数据库的平滑升级 ::: ## 一、用已有的VOJ镜像部署 可以直接在已有的docker-compose.yml添加以下模块即可,**本容器检查完是否有更新就会正常退出** ```yaml voj-mysql-checker: image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_database_checker container_name: voj-mysql-checker depends_on: - voj-mysql links: - voj-mysql:mysql environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-voj123456} # mysql的数据库密码 ``` ## 二、自己打包镜像部署 首先 先下载[voj-deploy](https://github.com/simplefanc/voj-deploy/tree/master) 然后进入对应的镜像打包文件夹 ```shell git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/mysql-checker ``` 当前文件夹为打包`voj-mysql-checker`镜像的相关文件,只需将这些文件复制到同一个文件夹内,之后执行以下命令进行打包成镜像。 ```shell docker build -t voj-mysql-checker . ``` docker-compose启动 ```yaml version: "3" services: voj-mysql-checker: #image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_database_checker image: voj-mysql-checker # 自己的镜像名称 container_name: voj-mysql-checker depends_on: - voj-mysql links: - voj-mysql:mysql environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-voj123456} # mysql的数据库密码 ``` **文件介绍** #### 1. voj-update.sql 此文件为检查更新的sql脚本 #### 2. update.sh 此文件为执行脚本 ```shell #!/bin/sh mysql -h mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "select version();" &> /dev/null RETVAL=$? while [ $RETVAL -ne 0 ] do sleep 3 mysql -h mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "select version();" &> /dev/null RETVAL=$? done mysql -uroot -h mysql -p$MYSQL_ROOT_PASSWORD -D voj -e "source /sql/voj-update.sql" echo 'Check whether the `voj` database has been updated successfully!' ``` #### 3. Dockerfile ```dockerfile FROM arey/mysql-client COPY ./voj-update.sql /sql/ COPY ./update.sh /sql/ ENTRYPOINT ["/bin/sh", "/sql/update.sh"] ``` ================================================ FILE: docs/src/monomer/mysql.md ================================================ # MySQL部署 ## Docker部署 ```shell docker run -d --name voj-mysql \ -v $PWD/voj/data/mysql/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD="voj123456" \ -e TZ="Asia/Shanghai" \ -p 3306:3306 \ --restart="always" \ mysql:5.7 ``` ## 常规部署 请自行探索。 ================================================ FILE: docs/src/monomer/nacos.md ================================================ # Nacos部署 ## Docker部署 ```shell docker run -d \ -e JVM_XMS=384m \ -e JVM_XMX=384m \ -e JVM_XMN=192m \ -e MODE=standalone \ -e SPRING_DATASOURCE_PLATFORM=mysql \ -e MYSQL_SERVICE_HOST=mysql_host \ -e MYSQL_SERVICE_PORT=mysql_port \ -e MYSQL_SERVICE_USER=root \ -e MYSQL_SERVICE_PASSWORD="mysql_root_password" \ -e MYSQL_SERVICE_DB_NAME=nacos \ --env NACOS_AUTH_ENABLE=true \ -p 8848:8848 \ --name voj-nacos \ --restart=always \ nacos/nacos-server:1.4.2 ``` ## 常规部署 请自行探索。 ================================================ FILE: docs/src/monomer/redis.md ================================================ # Redis部署 ## Docker部署 ```shell docker run -d --name redis -p 6379:6379 \ -v $PWD/voj/data/redis/data:/data \ --name voj-redis \ --restart="always" \ redis \ --requirepass "redis_password" ``` ## 常规部署 请自行探索。 ================================================ FILE: docs/src/monomer/rsync.md ================================================ # 评测数据同步(分布式才需要) :::tip 本镜像主要是用在于后端服务与判題服务不在同一机器,为了让题目评测数据从主服务器同步于判題服务所在机器而使用的,也就是分布式部署都需要本服务来同步评测数据,包括多台判题机。 ::: ## 一、常规部署 1. 在主后台服务开启rsync实现服务增量同步,本VOJ使用子服务器主动拉取最新评测数据的功能(可选择主服务推的功能,但对主服务器的功耗较大) 2. 首先在主服务器(运行后端服务)的服务器中配置,指令如下 ```shell vim /etc/rsyncd/rsyncd.conf # 新建配置文件 ``` ```shell # 将以下内容写入的rsyncd.conf文件里面 然后保存退出 port = 873 uid = root gid = root use chroot = yes read only = yes log file = /voj/log/rsyncd.log [testcase] path = /voj/testcase/ list = yes auth users = vojrsync secrets file = /etc/rsyncd/rsyncd.passwd ``` 再新建密码配置文件 ```shell vim /etc/rsyncd/rsyncd.passwd ``` ```shell # 将以下内容写入rsyncd.passwd文件里面,冒号后面的密码可用自定义,然后保存退出。 vojrsync:123456 ``` 修改密码配置文件的权限为600 ```shell chmod 600 /etc/rsyncd/rsyncd.passwd ``` 然后使用命令,使用后台守护进程运行rsync ```shell rsync --daemon --config=/etc/rsyncd/rsyncd.conf ``` 设置开启自启动 ```shell echo "/usr/bin/rsync --daemon --config=/etc/rsyncd/rsyncd.conf" >> /etc/rc.local ``` 3. 之后在运行`voj-judger`判题服务的服务器上使用rsync每60秒同步一次指定文件夹的评测数据(同步周期可自己改) 新建密码配置文件,同时写入与主服务端的rsync一样的密码 ```shell vim /etc/rsyncd/rsyncd.passwd ``` ```shell 123456 # 保存退出 ``` 修改密码配置文件的权限为600 ```shell chmod 600 /etc/rsyncd/rsyncd.passwd ``` 然后编写sh文件 ```shell vim /etc/rsyncd/rsyncd_slave.sh ``` 注意${ip}写自己主服务器的ip ```shell while true do rsync -avz --delete --progress --password-file=/etc/rsyncd/rsyncd.passwd vojrsync@${ip}::testcase /voj/testcase >> /voj/log/rsync_slave.log sleep 60 done ``` 使用 nohup后台运行即可 ```shell nohup /etc/rsyncd/rsyncd_slave.sh & ``` ## 二、Docker部署 ### 前言 直接下载部署项目,进入到当前文件夹执行打包命令 ```shell git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/rsync ``` 当前文件夹为打包`voj-rsync`镜像的相关文件,将这些文件复制到同一个文件夹内,之后执行以下命令进行打包成镜像. ```shell docker build -t voj-rsync . ``` **该服务用于测试用例数据在不同服务器之间的同步** docker run启动 - 主服务器(Backend所在服务器) ```shell docker run -d --name voj-rsync \ -v ./voj/testcase:/voj/testcase:ro \ -e RSYNC_MODE=master \ -e RSYNC_USER=vojrsync \ -e RSYNC_PASSWORD=voj123456 \ -p 873:873 \ --restart=always \ voj-rsync # registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0 ``` - 从服务器(`voj-judger`所在的服务器) ```shell docker run -d --name voj-rsync \ -v ./voj/testcase:/voj/testcase \ -e RSYNC_MODE=slave \ -e RSYNC_USER=vojrsync \ -e RSYNC_PASSWORD=voj123456 \ -e RSYNC_MASTER_ADDR=master_server_ip \ -p 873:873 \ --restart=always \ voj-rsync # registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0 ``` docker-compose启动 - 主服务器(Backend所在服务器) ```yaml version: "3" services: voj-rsync-master: # image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0 image: voj-rsync container_name: voj-rsync-master volumes: - ./voj/testcase:/voj/testcase:ro environment: - RSYNC_MODE=master # 当前为slave主服务 - RSYNC_USER=vojrsync # 请勿修改 - RSYNC_PASSWORD=voj123456 # 请修改数据同步密码 ports: - "0.0.0.0:873:873" ``` - 从服务器(`voj-judger`所在的服务器) ```yaml version: "3" services: voj-rsync-slave: # image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0 image: voj-rsync container_name: voj-rsync-slave restart: always volumes: - ./judge/test_case:/voj/testcase - ./judge/log:/voj/log environment: - RSYNC_MODE=slave # 当前为slave从服务 - RSYNC_USER=vojrsync # 请勿修改 - RSYNC_PASSWORD=voj123456 # 与主服务器的rsync的密码一致 - RSYNC_MASTER_ADDR=master_server_ip # 主服务器ip ports: - "0.0.0.0:873:873" ``` ### 文件介绍 #### 1. rsync.conf 主服务器的rsync配置文件 ```shell port = 873 uid = root gid = root use chroot = yes read only = yes log file = /voj/log/rsyncd.log [testcase] path = /voj/testcase/ list = yes auth users = vojrsync secrets file = /voj/rsyncd/rsyncd.passwd ``` #### 2. run.sh 根据`$RSYNC_MODE`环境变量启动不同模式的rsync服务 ```bash #!/usr/bin/bash if [ "$RSYNC_MODE" == "master" ]; then echo "$RSYNC_USER:$RSYNC_PASSWORD" > /voj/rsyncd/rsyncd_master.passwd chmod 600 /voj/rsyncd/rsyncd_master.passwd rsync --daemon --config=/voj/rsyncd/rsyncd.conf else echo "$RSYNC_PASSWORD" > /voj/rsyncd/rsyncd_slave.passwd chmod 600 /voj/rsyncd/rsyncd_slave.passwd while true do rsync -avz --delete --progress --password-file=/voj/rsyncd/rsyncd_slave.passwd $RSYNC_USER@$RSYNC_MASTER_ADDR::testcase /voj/testcase >> /voj/log/rsync_slave.log sleep 100 done fi ``` #### 3. Dockerfile ```dockerfile FROM ubuntu:18.04 RUN apt-get update && apt-get -y install rsync RUN mkdir -p /voj/rsyncd COPY run.sh /voj/rsyncd/run.sh COPY rsyncd.conf /voj/rsyncd/rsyncd.conf CMD /bin/bash /voj/rsyncd/run.sh ``` ================================================ FILE: docs/src/use/admin-user.md ================================================ # 用户管理 > 注意:用户管理只有超级管理员账号可以操作! **管理员角色说明** | 权限 | 超级管理员 | 题目管理员 | 普通管理员 | | ------------------------------------------------ | :--------: | :--------: | :--------: | | 系统公告管理 | ✔ | ❌ | ❌ | | 系统通知推送管理 | ✔ | ❌ | ❌ | | 系统配置 | ✔ | ❌ | ❌ | | 用户管理 | ✔ | ❌ | ❌ | | 全部题目增加 | ✔ | ✔ | ✔ | | 其他人创建的题目查看 | ✔ | ✔ | ❌ | | 自己创建的题目查看 | ✔ | ✔ | ✔ | | 其他人创建的题目修改 | ✔ | ✔ | ❌ | | 自己创建的题目修改 | ✔ | ✔ | ✔ | | 全部题目删除 | ✔ | ✔ | ❌ | | 全部题目权限修改(公开、隐藏、比赛) | ✔ | ✔ | ❌ | | 全部题目评测数据下载 | ✔ | ✔ | ❌ | | 导入远程OJ题目 | ✔ | ✔ | ✔ | | 全部比赛权限(增加、删除、修改) | ✔ | ❌ | ❌ | | 自己创建的比赛(增加、修改) | ✔ | ✔ | ✔ | | 自己创建的比赛的题目(查看、增加,修改,移除) | ✔ | ✔ | ✔ | | 其他人创建的比赛的题目(查看、增加,修改,移除) | ✔ | ✔ | ❌ | | 自己创建的比赛的题目(评测数据下载、删除) | ✔ | ✔ | ❌ | | 自己创建的比赛的题目权限修改(隐藏、删除) | ✔ | ✔ | ✔ | | 自己创建的比赛的题目权限修改为公开题目 | ✔ | ✔ | ❌ | | 讨论管理 | ✔ | ✔ | ✔ | | 全部训练权限(增加、删除、修改) | ✔ | ❌ | ❌ | | 自己创建的训练(增加、修改) | ✔ | ✔ | ✔ | **用户角色说明** | 权限 | 用户(默认) | 用户(禁止提交) | 用户(禁止发讨论) | 用户(禁言) | 用户(禁止提交&禁止发讨论) | 用户(禁止提交&禁言) | 封禁 | | ------------ | :--------: | :------------: | :--------------: | :--------: | :-----------------------: | :-----------------: | :--: | | 发布讨论 | ✔ | ✔ | ❌ | ❌ | ❌ | ❌ | ❌ | | 修改讨论 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | | 删除讨论 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | | 发表评论 | ✔ | ✔ | ✔ | ❌ | ✔ | ❌ | ❌ | | 删除评论 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | | 提交代码 | ✔ | ❌ | ✔ | ✔ | ❌ | ❌ | ❌ | | 一切用户权力 | ✔ | ❗ | ❗ | ❗ | ❗ | ❗ | ❌ | 1. 进入后台管理 2. 点击编辑后,可修改用户角色 ================================================ FILE: docs/src/use/close-free-cdn.md ================================================ # 取消前端免费CDN 由于有的机房的网络不支持一些域名的访问,有防火墙挡住,所以可能前端页面的js和css的CDN访问不了,导致页面打不开。 :::info voj挂载了一些前端静态资源库的免费CDN,全部都是域名`unpkg.com`和`bytecdntp.com`下的免费CDN ::: 可以在对应的电脑浏览器上打开以下链接,如果能正常访问则没有问题。 ```html https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.11/vue.min.js 或 https://unpkg.com/vxe-table@2.9.26/lib/style.min.css ``` :::warning voj-frontend(前端vue项目)如果不挂载任何CDN,最终打包生成的文件夹大小约8MB ::: ## 一、全部打包且部署 :::info 如果本身voj部署在**学校内网机器**上或者**云服务器是无带宽上限、按流量计费的实例**,那么可以不用考虑带宽问题,可以直接取消CDN挂载,直接全部自己打包成对应的静态文件,然后挂载到docker的`voj-frontend`镜像里面 ::: **操作如下:** 1. 下载前端源代码:[https://github.com/simplefanc/voj/tree/master/voj-vue](https://github.com/simplefanc/voj/tree/master/voj-vue) 2. 进入`voj-vue`文件夹,编辑`vue.config.js`文件,按下面的修改 ```js // 该变量改成false const isProduction = false; // 本地环境是否需要使用cdn,该变量改成false const devNeedCdn = false; // 找到下面对应的cdn的js链接和css链接,全部注释掉 css: [ // 'https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.14.0/theme-chalk/index.min.css', // "https://cdn.jsdelivr.net/npm/github-markdown-css@4.0.0/github-markdown.min.css", // "https://cdn.jsdelivr.net/npm/vxe-table@2.9.26/lib/style.min.css", ], js: [ // "https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.1/vue.min.js", // "https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.2.0/vue-router.min.js", // "https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.0/axios.min.js", // "https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.15.3/index.min.js", // "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.3.2/highlight.min.js", // "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js", // "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/locale/zh-cn.min.js", // "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/locale/en-gb.min.js", // "https://cdnjs.cloudflare.com/ajax/libs/echarts/4.9.0-rc.1/echarts.min.js", // "https://cdnjs.cloudflare.com/ajax/libs/vue-echarts/5.0.0-beta.0/vue-echarts.min.js", // "https://cdn.jsdelivr.net/npm/vuex@3.5.1/dist/vuex.min.js", // "https://cdn.jsdelivr.net/npm/xe-utils@3.4.3/dist/xe-utils.umd.min.js", // "https://cdn.jsdelivr.net/npm/vxe-table@2.9.26/lib/index.umd.min.js", // "https://unpkg.com/mavon-editor@2.9.1/dist/mavon-editor.js" ] ``` 3. 进入`voj-vue/src`文件夹,编辑`main.js`文件,将内容替换成如下: ```js import Vue from 'vue' import App from './App.vue' import store from './store' import Element from 'element-ui' import i18n from '@/i18n' import "element-ui/lib/theme-chalk/index.css" import 'font-awesome/css/font-awesome.min.css' import Message from 'vue-m-message' import 'vue-m-message/dist/index.css' import axios from 'axios' import Md_Katex from '@iktakahiro/markdown-it-katex' import 'xe-utils' import VXETable from 'vxe-table' import 'vxe-table/lib/style.css' import Katex from '@/common/katex' import VueClipboard from 'vue-clipboard2' import highlight from '@/common/highlight' import filters from '@/common/filters.js' import VueCropper from 'vue-cropper' import ECharts from 'vue-echarts/components/ECharts.vue' import 'echarts/lib/chart/bar' import 'echarts/lib/chart/line' import 'echarts/lib/chart/pie' import 'echarts/lib/component/title' import 'echarts/lib/component/grid' import 'echarts/lib/component/dataZoom' import 'echarts/lib/component/legend' import 'echarts/lib/component/tooltip' import 'echarts/lib/component/toolbox' import 'echarts/lib/component/markPoint' Vue.component('ECharts', ECharts) import VueECharts from 'vue-echarts'; Vue.component('ECharts', VueECharts) import VueParticles from 'vue-particles' import SlideVerify from 'vue-monoplasty-slide-verify' // markdown编辑器 import mavonEditor from 'mavon-editor' //引入markdown编辑器 import 'mavon-editor/dist/css/index.css'; Vue.use(mavonEditor) import {Drawer,List,Menu,Icon,AppBar,Button,Divider} from 'muse-ui'; import 'muse-ui/dist/muse-ui.css'; import VueDOMPurifyHTML from 'vue-dompurify-html' Vue.use(VueDOMPurifyHTML) import router from './router' Vue.use(Drawer) Vue.use(List) Vue.use(Menu) Vue.use(Icon) Vue.use(AppBar) Vue.use(Button) Vue.use(Divider) Object.keys(filters).forEach(key => { // 注册全局过滤器 Vue.filter(key, filters[key]) }) Vue.use(VueParticles) // 粒子特效背景 Vue.use(Katex) // 数学公式渲染 VXETable.setup({ // 对组件内置的提示语进行国际化翻译 i18n: (key, value) => i18n.t(key, value) }) Vue.use(VXETable) // 表格组件 Vue.use(VueClipboard) // 剪贴板 Vue.use(highlight) // 代码高亮 Vue.use(Element,{ i18n: (key, value) => i18n.t(key, value) }) Vue.use(VueCropper) // 图像剪切 Vue.use(Message, { name: 'msg' }) // `Vue.prototype.$msg` 全局消息提示 Vue.use(SlideVerify) // 滑动验证码组件 Vue.prototype.$axios = axios Vue.prototype.$markDown = mavonEditor.mavonEditor.getMarkdownIt().use(Md_Katex) // 挂载到vue Vue.config.productionTip = false new Vue({ router, store, i18n, render: h => h(App) }).$mount('#app') ``` 4. 然后使用在`voj-vue`目录下,使用`npm run build`,npm请自行百度下载安装,之后会生成一个dist文件夹,结构如下: ``` dist ├── index.html ├── favicon.ico └── assets ├── css │ ├── .... ├── fonts │ ├── .... ├── img │ ├── .... ├── js │ ├── .... .... .... ``` 将 `dist` 文件夹复制到服务器上某个目录下,比如 `/voj/www/html/dist`,然后修改 `docker-compose.yml`,在 `voj-frontend` 模块中的 `volumes` 中增加一行 `- /voj/www/html/dist:/usr/share/nginx/html` (冒号前面的请修改为实际的路径),然后 `docker-compose up -d` 即可。 ## 二、全部打包但有个人CDN服务器 :::info 如果云服务器是只有固定小流量出口带宽的,例如1M,2M的,害怕访问速度太慢,但是有钱买CDN服务器,可以先按照上面的方式,生成对应的本地静态文件夹,然后把`dist/assets`文件夹放在CDN服务器上,然后修改`dist/index.html` ::: **(建议:有弄过CDN的可以这样搞)** 添加css等文件的导入 ```html ``` 添加js等文件的导入 ```html