[
  {
    "path": ".gitignore",
    "content": "*.classpath\n\n# Package Files\n*.jar\n*.war\n*.ear\n*.log\n*.iml\n\n.DS_Store\nnode_modules\ndist/\n.cache/\n.temp/\ntarget/\nout/\n.idea/\n.classpath\n.project\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n*.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 simplefanC\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Virtual Online Judge（VOJ）\n\n[![Java](https://img.shields.io/badge/Java-17-informational)](https://openjdk.java.net)\n[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.6.3-success)](https://spring.io/projects/spring-boot)\n[![Spring Cloud](https://img.shields.io/badge/Spring%20Cloud-2021.0.1-success)](https://spring.io/projects/spring-cloud)\n[![Spring Cloud Alibaba](https://img.shields.io/badge/Spring%20Cloud%20Alibaba-2021.0.1.0-success)](https://spring.io/projects/spring-cloud-alibaba)\n[![MySQL](https://img.shields.io/badge/MySQL-8.0.19-blue)](https://www.mysql.com/)\n[![Redis](https://img.shields.io/badge/Redis-5.0.9-red)](https://redis.io/)\n[![Vue](https://img.shields.io/badge/Vue-2.6.11-success)](https://cn.vuejs.org/)\n\n## 简介\n\nVOJ 是基于微服务、前后端分离的高性能在线评测系统。采用现阶段流行技术实现，采用 Docker 容器化部署。\n\n## 概览\n\n+ 基于 Docker，真正一键部署\n+ 前后端分离，模块化编程\n+ 微服务，支持分布式判题\n+ 拥有**本地判题**服务，同时支持其它知名 OJ (HDU、POJ、Codeforces、AtCoder...) 的**远程判题**\n+ ACM/OI 两种比赛模式、完善的比赛功能（打星队伍、关注队伍、外榜）\n+ 完善的判题模式（普通测评、特殊测评、交互测评）\n+ 支持私有训练、公开训练（题单）\n+ 更细致的权限划分，超级管理员、题目管理员、普通管理员各司其职\n+ 丰富的可视化图表，一图胜千言\n+ 支持 Template Problem，可以添加函数题甚至填空题\n+ 多语言支持：`C`, `C++`, `Java`, `Python`, `C#`，`GoLang`\n+ Markdown & LaTeX 支持\n\n## 项目源码\n\n+ 后端源码：[https://github.com/simplefanC/voj](https://github.com/simplefanC/voj)\n+ 前端源码：[https://github.com/simplefanC/voj-vue](https://github.com/simplefanC/voj-vue)\n+ 判题沙箱：[https://github.com/criyle/go-judge](https://github.com/criyle/go-judge)\n\n## 项目文档\n\n项目文档地址：[https://github.com/simplefanC/voj/wiki](https://github.com/simplefanC/voj/wiki)\n\n## 项目结构\n\n```\nvoj\n├── voj-common -- 工具类及通用代码\n├── voj-backend -- 业务服务模块\n└── voj-judger -- 评测服务模块\n```\n\n## 技术选型\n\n| 技术                 | 说明                | 官网                                            |\n| -------------------- | ------------------- | ----------------------------------------------- |\n| Spring Boot          | 容器+MVC框架        | https://spring.io/projects/spring-boot          |\n| Spring Cloud         | 微服务框架          | https://spring.io/projects/spring-cloud         |\n| Spring Cloud Alibaba | 微服务框架          | https://spring.io/projects/spring-cloud-alibaba |\n| MyBatis-Plus         | ORM框架             | https://baomidou.com                            |\n| Druid                | 数据库连接池        | https://github.com/alibaba/druid                |\n| Redis                | 分布式缓存          | https://redis.io                                |\n| Shiro                | 认证和授权框架      | https://shiro.apache.org                        |\n| JWT                  | JWT登录支持         | https://github.com/jwtk/jjwt                    |\n| Hibernator-Validator | 验证框架            | http://hibernate.org/validator                  |\n| EasyExcel            | JAVA解析Excel工具   | https://github.com/alibaba/easyexcel            |\n| PageHelper           | MyBatis物理分页插件 | https://github.com/pagehelper/Mybatis-PageHelper  |\n| Hutool               | Java工具类库        | https://github.com/looly/hutool                 |\n| Lombok               | 简化对象封装工具    | https://github.com/rzwitserloot/lombok          |\n| Swagger-UI           | 文档生成工具        | https://github.com/swagger-api/swagger-ui       |\n| Nginx                | 静态资源服务器      | https://www.nginx.com                          |\n| Docker               | 应用容器引擎        | https://www.docker.com     \n\n## 部署\n\n快速部署：[基于 Docker Compose 部署](https://github.com/simplefanC/voj/wiki/deploy)\n\n部署仓库：[https://github.com/simplefanC/voj-deploy](https://github.com/simplefanC/voj-deploy)\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"voj-docs\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Virtual Online Judge\",\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"vuepress build src\",\n    \"clean-dev\": \"vuepress dev src --clean-cache\",\n    \"dev\": \"vuepress dev src\"\n  },\n  \"devDependencies\": {\n    \"@vuepress/client\": \"2.0.0-beta.51\",\n    \"vue\": \"^3.2.29\",\n    \"vuepress\": \"2.0.0-beta.51\",\n    \"vuepress-theme-hope\": \"2.0.0-beta.108\"\n  }\n}\n"
  },
  {
    "path": "docs/src/.vuepress/config.ts",
    "content": "import {defineUserConfig} from \"vuepress\";\nimport theme from \"./theme\";\n\n// const { searchPlugin } = require('@vuepress/plugin-search')\n\nexport default defineUserConfig({\n  // dest: './',   // 设置输出目录\n  lang: \"zh-CN\",\n  title: \"VOJ\",\n  description: \"Virtual Online Judge\",\n\n  base: \"/\",\n\n  head: [\n    [\n      \"link\",\n      {\n        rel: \"stylesheet\",\n        href: \"//at.alicdn.com/t/font_2410206_mfj6e1vbwo.css\",\n      },\n    ],\n  ],\n\n  plugins: [\n    // searchPlugin({\n    //   // https://v2.vuepress.vuejs.org/zh/reference/plugin/search.html\n    //   // 排除首页\n    //   isSearchable: (page) => page.path !== \"/\",\n    //   maxSuggestions: 10,\n    //   hotKeys: [\"s\", \"/\"],\n    //   // 用于在页面的搜索索引中添加额外字段\n    //   getExtraFields: () => [],\n    //   locales: {\n    //     \"/\": {\n    //       placeholder: \"搜索\",\n    //     },\n    //   },\n    // }),\n  ],\n  theme,\n});\n"
  },
  {
    "path": "docs/src/.vuepress/navbar.ts",
    "content": "import {navbar} from \"vuepress-theme-hope\";\n\nexport default navbar([\n  // \"/\",\n  // \"/home\",\n  // { text: \"使用指南\", icon: \"creative\", link: \"/guide/\" },\n  // {\n  //   text: \"博文\",\n  //   icon: \"edit\",\n  //   prefix: \"/posts/\",\n  //   children: [\n  //     {\n  //       text: \"文章 1-4\",\n  //       icon: \"edit\",\n  //       prefix: \"article/\",\n  //       children: [\n  //         { text: \"文章 1\", icon: \"edit\", link: \"article1\" },\n  //         { text: \"文章 2\", icon: \"edit\", link: \"article2\" },\n  //         \"article3\",\n  //         \"article4\",\n  //       ],\n  //     },\n  //     {\n  //       text: \"文章 5-12\",\n  //       icon: \"edit\",\n  //       children: [\n  //         {\n  //           text: \"文章 5\",\n  //           icon: \"edit\",\n  //           link: \"article/article5\",\n  //         },\n  //         {\n  //           text: \"文章 6\",\n  //           icon: \"edit\",\n  //           link: \"article/article6\",\n  //         },\n  //         \"article/article7\",\n  //         \"article/article8\",\n  //       ],\n  //     },\n  //     { text: \"文章 9\", icon: \"edit\", link: \"article9\" },\n  //     { text: \"文章 10\", icon: \"edit\", link: \"article10\" },\n  //     \"article11\",\n  //     \"article12\",\n  //   ],\n  // },\n  // {\n  //   text: \"主题文档\",\n  //   icon: \"note\",\n  //   link: \"https://vuepress-theme-hope.github.io/v2/zh/\",\n  // },\n]);\n"
  },
  {
    "path": "docs/src/.vuepress/sidebar.ts",
    "content": "import {sidebar} from \"vuepress-theme-hope\";\n\nexport default sidebar([\n  // \"/\",\n  // \"/home\",\n  // \"/slide\",\n  {\n    text: '序章',\n    collapsable: true,\n    prefix: \"/introduction/\",\n    children: [\n      '',\n      'architecture'\n    ]\n  },\n  {\n    text: '快速部署',\n    collapsable: true,\n    prefix: \"/deploy/\",\n    children: [\n      '',\n      'docker',\n      'open-https',\n      'multi-judgeserver',\n      // 'update',\n      'how-to-backup'\n    ]\n  },\n  {\n    text: '单体部署',\n    collapsable: true,\n    prefix: \"/monomer/\",\n    children: [\n      'mysql',\n      // 'mysql-checker',\n      'redis',\n      'nacos',\n      'backend',\n      'judgeserver',\n      'frontend',\n      'rsync'\n    ]\n  },\n  {\n    text: '开发文档',\n    collapsable: true,\n    prefix: \"/develop/\",\n    children: [\n      'db',\n      'judge_dispatcher',\n      'sandbox',\n      'update-fe'\n    ]\n  },\n  {\n    text: '使用文档',\n    collapsable: true,\n    prefix: \"/use/\",\n    children: [\n      'import-problem',\n      'judge-mode',\n      'testcase',\n      'training',\n      'contest',\n      // 'group',\n      'import-user',\n      'admin-user',\n      'notice-announcement',\n      'discussion-admin'\n      // 'custom-difficulty',\n      // 'close-free-cdn'\n    ]\n  },\n  // {\n  //   text: \"如何使用\",\n  //   icon: \"creative\",\n  //   prefix: \"/guide/\",\n  //   link: \"/guide/\",\n  //   children: \"structure\",\n  // },\n  // {\n  //   text: \"文章\",\n  //   icon: \"note\",\n  //   prefix: \"/posts/\",\n  //   children: [\n  //     {\n  //       text: \"文章 1-4\",\n  //       icon: \"note\",\n  //       collapsable: true,\n  //       prefix: \"article/\",\n  //       children: [\"article1\", \"article2\", \"article3\", \"article4\"],\n  //     },\n  //     {\n  //       text: \"文章 5-12\",\n  //       icon: \"note\",\n  //       children: [\n  //         {\n  //           text: \"文章 5-8\",\n  //           icon: \"note\",\n  //           collapsable: true,\n  //           prefix: \"article/\",\n  //           children: [\"article5\", \"article6\", \"article7\", \"article8\"],\n  //         },\n  //         {\n  //           text: \"文章 9-12\",\n  //           icon: \"note\",\n  //           children: [\"article9\", \"article10\", \"article11\", \"article12\"],\n  //         },\n  //       ],\n  //     },\n  //   ],\n  // },\n]);\n"
  },
  {
    "path": "docs/src/.vuepress/styles/index.scss",
    "content": ""
  },
  {
    "path": "docs/src/.vuepress/styles/palette.scss",
    "content": ""
  },
  {
    "path": "docs/src/.vuepress/theme.ts",
    "content": "import {hopeTheme} from \"vuepress-theme-hope\";\nimport navbar from \"./navbar\";\nimport sidebar from \"./sidebar\";\n\nexport default hopeTheme({\n  hostname: \"https://vuepress-theme-hope-v2-demo.mrhope.site\",\n\n  author: {\n    name: \"simplefanC\",\n    url: \"https://github.com/simplefanC\",\n  },\n\n  iconPrefix: \"iconfont icon-\",\n\n  // logo: \"/logo.png\",\n\n  repo: \"simplefanC/voj\",\n\n  docsDir: \"docs/src\",\n\n  // navbar\n  navbar: navbar,\n\n  // sidebar\n  sidebar: sidebar,\n\n  footer: \"<a href=\\\"https://beian.miit.gov.cn/\\\" target=\\\"_blank\\\">鄂ICP备2020015769号-1</a>\",\n\n  displayFooter: true,\n\n  pageInfo: [\"Author\", \"Original\", \"Date\", \"Category\", \"Tag\", \"ReadingTime\"],\n\n  blog: {\n    description: \"一个前端开发者\",\n    intro: \"/intro.html\",\n    medias: {\n      Baidu: \"https://example.com\",\n      Bitbucket: \"https://example.com\",\n      Dingding: \"https://example.com\",\n      Discord: \"https://example.com\",\n      Dribbble: \"https://example.com\",\n      Email: \"https://example.com\",\n      Evernote: \"https://example.com\",\n      Facebook: \"https://example.com\",\n      Flipboard: \"https://example.com\",\n      GitHub: \"https://example.com\",\n      Gitlab: \"https://example.com\",\n      Gmail: \"https://example.com\",\n      Instagram: \"https://example.com\",\n      Lines: \"https://example.com\",\n      Linkedin: \"https://example.com\",\n      Pinterest: \"https://example.com\",\n      Pocket: \"https://example.com\",\n      QQ: \"https://example.com\",\n      Qzone: \"https://example.com\",\n      Reddit: \"https://example.com\",\n      Rss: \"https://example.com\",\n      Steam: \"https://example.com\",\n      Twitter: \"https://example.com\",\n      Wechat: \"https://example.com\",\n      Weibo: \"https://example.com\",\n      Whatsapp: \"https://example.com\",\n      Youtube: \"https://example.com\",\n      Zhihu: \"https://example.com\",\n    },\n  },\n\n  encrypt: {\n    config: {\n      \"/guide/encrypt.html\": [\"1234\"],\n    },\n  },\n\n  plugins: {\n    blog: {\n      autoExcerpt: true,\n    },\n\n    // 如果你不需要评论，可以直接删除 comment 配置，\n    // 以下配置仅供体验，如果你需要评论，请自行配置并使用自己的环境，详见文档。\n    // 为了避免打扰主题开发者以及消耗他的资源，请不要在你的正式环境中直接使用下列配置!!!!!\n    // comment: {\n    //   /**\n    //    * Using giscus\n    //    */\n    //   type: \"giscus\",\n    //   repo: \"vuepress-theme-hope/giscus-discussions\",\n    //   repoId: \"R_kgDOG_Pt2A\",\n    //   category: \"Announcements\",\n    //   categoryId: \"DIC_kwDOG_Pt2M4COD69\",\n    //\n    //   /**\n    //    * Using twikoo\n    //    */\n    //   // type: \"twikoo\",\n    //   // envId: \"https://twikoo.ccknbc.vercel.app\",\n    //\n    //   /**\n    //    * Using Waline\n    //    */\n    //   // type: \"waline\",\n    //   // serverURL: \"https://vuepress-theme-hope-comment.vercel.app\",\n    // },\n\n    mdEnhance: {\n      enableAll: true,\n      presentation: {\n        plugins: [\"highlight\", \"math\", \"search\", \"notes\", \"zoom\"],\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "docs/src/README.md",
    "content": "---\ntitle: 首页\nhome: true\nheroImage: /logo.png\nheroText:  VOJ\ntagline: 基于分布式、前后端分离的高性能在线评测系统\nactions:\n  - text: 文档介绍 🔔\n    link: /introduction/\n    type: primary\n  - text: 快速部署\n    link: /deploy/docker/\nfeatures:\n  - title: 分布式\n    details: 支持多台判题服务弹性伸缩\n  - title: 高效化\n    details: 采用前后端分离，开发迅速，使用高性能可复用判题沙盒\n  - title: 定制化\n    details: 网站配置高度集中，支持定制化修改\n  - title: 安全化\n    details: 判题使用 Cgroups 隔离用户程序，网站权限控制完善\n  - title: 多样化\n    details: 独有本地判题服务，同时支持其它知名 OJ 题目的远程判题\n---\n\n[![Java](https://img.shields.io/badge/Java-11-informational)](https://openjdk.java.net)\n[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.6.3-success)](https://spring.io/projects/spring-boot)\n[![Spring Cloud](https://img.shields.io/badge/Spring%20Cloud-2021.0.1-success)](https://spring.io/projects/spring-cloud)\n[![Spring Cloud Alibaba](https://img.shields.io/badge/Spring%20Cloud%20Alibaba-2021.0.1.0-success)](https://spring.io/projects/spring-cloud-alibaba)\n[![MySQL](https://img.shields.io/badge/MySQL-8.0.19-blue)](https://www.mysql.com/)\n[![Redis](https://img.shields.io/badge/Redis-5.0.9-red)](https://redis.io/)\n[![Nacos](https://img.shields.io/badge/Nacos-1.4.2-%23267DF7)](https://github.com/alibaba/nacos)\n[![Vue](https://img.shields.io/badge/Vue-2.6.11-success)](https://cn.vuejs.org/)\n\nVirtual Online Judge (VOJ) : 基于前后端分离、分布式架构的在线测评系统（VOJ），前端使用 Vue，后端主要使用 Spring Boot，Redis，MySQL，Nacos 等主流技术，**支持 HDU、POJ、CF、AtCoder、MXT、JSK、TKOJ 的虚拟判题，同时适配手机端、电脑端浏览，拥有讨论区与站内消息系统，支持私有训练、公开训练（题单），还有完善的判题模式（普通测评、特殊测评、交互测评）和完善的比赛功能（打星队伍、关注队伍、外榜）。**\n\n[GitHub 仓库](https://github.com/simplefanc/voj)\n\n有任何部署问题或项目 Bug 请提 Issue！\n\n"
  },
  {
    "path": "docs/src/deploy/README.md",
    "content": "# 环境配置\n\n## 环境说明\n:::tip\n- 后端：需要在 Linux 系统下部署运行，建议使用 Ubuntu 18.04，其它版本的 Linux 系统也可以，同时需要 **Docker** 辅助部署\n- 前端：Linux 系统下，需要 Nginx 进行反向代理\n- 判题服务：由于判题沙盒有多操作系统版本，Linux 系统或 Windows 均可以，但是在本 VOJ 镜像中**只能**使用 **Ubuntu 16.04** 以上或者 **CentOS 8** 以上\n- 数据同步：运行判题服务和后端服务的服务器有 rsync\n- **尽量不要使用突发性能或共享型的云服务器实例，有可能造成评测时间计量不准确。**\n:::\n\n## Linux环境搭建\n\n> VOJ 使用的 Ubuntu 18.04 版本，单机部署建议2核4G以上内存\n\n### 安装 Docker\n\n1. 安装需要的包\n\n   ```shell\n   sudo apt-get update\n   ```\n\n2. 安装依赖包\n\n   ```shell\n   sudo apt-get install \\\n      apt-transport-https \\\n      ca-certificates \\\n      curl \\\n      gnupg-agent \\\n      software-properties-common\n   ```\n\n3. 添加 Docker 的官方 GPG 密钥\n\n   ```shell\n   curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -\n   ```\n\n4. 设置远程仓库\n\n   ```shell\n   sudo add-apt-repository \\\n      \"deb [arch=amd64] https://download.docker.com/linux/ubuntu \\\n     $(lsb_release -cs) \\\n     stable\"\n   ```\n\n5. 安装 Docker-CE\n\n   ```shell\n   sudo apt-get update\n   sudo apt-get install docker-ce docker-ce-cli containerd.io\n   ```\n\n6. 验证是否成功\n\n   ```shell\n   sudo docker run hello-world\n   ```\n\n### 安装 Docker-Compose\n\n1. 下载\n\n   ```shell\n   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\n   ```\n\n2. 授权\n\n   ```shell\n   sudo chmod +x /usr/local/bin/docker-compose\n   ```\n\n## Windows 环境\n\nWindows 下的安装仅供体验，勿在生产环境使用。如必要，请使用虚拟机安装 Linux 并将本 OJ 安装在其中。\n\n以下教程仅适用于 Win10 x64 下的 `PowerShell`\n\n1. 安装 Windows 的 Docker 工具\n2. 右击右下角 Docker 图标，选择 Settings 进行设置\n3. 选择 `Shared Drives` 菜单，之后勾选你想安装 OJ 的盘符位置（例如勾选D盘），点击 `Apply`\n4. 输入 Windows 的账号密码进行文件共享\n5. 安装 `Python`、`pip`、`git`、`docker-compose`，安装方法自行搜索。\n\n\n\n"
  },
  {
    "path": "docs/src/deploy/docker.md",
    "content": "# 快速部署\n**前提：已经在上一步准备好 Docker 与 Docker Compose**\n:::danger\n注意：如果正式部署运行 VOJ，请修改默认配置的密码，例如MySQL、Nacos的密码！！！  \n**使用默认密码可能会导致数据泄露，网站极其不安全！**\n:::\n\n## 一、单机部署\n\n1. 选择好需要安装的位置，运行下面命令\n\n   ```shell\n   git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy\n   ```\n\n2. 进入文件夹，使用docker-compose启动各容器服务\n\n   ```shell\n   cd standAlone\n   ```\n\n   `standAlone`文件夹文件有以下：\n\n   ```bash\n   ├── docker-compose.yml\n   ├── .env\n   ```\n\n   主要配置请见`.env`文件，内容如下：\n\n   > 注意：各服务ip最好不改动，保持处于172.20.0.0/16网段的docker network\n\n   ```properties\n   # VOJ全部数据存储的文件夹位置（默认当前路径生成voj文件夹）\n    VOJ_DATA_DIRECTORY=./voj\n    \n    # Redis的配置\n    REDIS_HOST=172.20.0.2\n    REDIS_PORT=6379\n    \n    # MySQL的配置\n    MYSQL_HOST=172.20.0.3\n    # 如果判题服务是分布式，请提供当前MySQL所在服务器的公网ip\n    MYSQL_PUBLIC_HOST=172.20.0.3\n    MYSQL_PORT=3306\n    MYSQL_ROOT_PASSWORD=voj123456\n    \n    # Nacos的配置\n    NACOS_HOST=172.20.0.4\n    NACOS_PORT=8848\n    NACOS_USERNAME=nacos\n    NACOS_PASSWORD=nacos\n    \n    # backend后端服务的配置\n    BACKEND_HOST=172.20.0.5\n    BACKEND_PORT=6688\n    # JWT加密秘钥，default则生成32位随机密钥\n    JWT_TOKEN_SECRET=default\n    # token过期时间默认为24小时（86400s）\n    JWT_TOKEN_EXPIRE=86400\n    # JWT默认12小时自动刷新\n    JWT_TOKEN_FRESH_EXPIRE=43200\n    # 调用判题服务器的token，default则生成32位随机密钥\n    JUDGE_TOKEN=default\n    # 请使用邮件服务的域名或ip\n    EMAIL_SERVER_HOST=smtp.qq.com\n    EMAIL_SERVER_PORT=465\n    EMAIL_USERNAME=your_email_username\n    EMAIL_PASSWORD=your_email_password\n    \n    # 判题服务的配置\n    JUDGE_SERVER_IP=172.20.0.7\n    JUDGE_SERVER_PORT=8088\n    JUDGE_SERVER_NAME=judger-alone\n    # -1表示可接收最大判题任务数为：cpu核心数+1\n    MAX_TASK_NUM=-1\n    # 当前判题服务器是否开启远程虚拟判题功能\n    REMOTE_JUDGE_OPEN=true\n    # -1表示可接收最大远程判题任务数为：cpu核心数*2+1\n    REMOTE_JUDGE_MAX_TASK_NUM=-1\n    # 默认沙盒并行判题程序数为：cpu核心数\n    PARALLEL_TASK=default\n    \n    # docker network的配置\n    SUBNET=172.20.0.0/16\n   ```\n   \n\n:::tip   \n提示：如果服务器的内存在4G或4G以上，请去掉JVM限制才能提高并发量，操作如下：\n\n- 注释或删除`docker-compose.yml`中`voj-backend`模块和`voj-judger`模块`environment`参数`JAVA_OPTS`\n\n:::\n\n3. 如果不改动，则以默认参数启动（**正式部署请修改默认配置的密码！**）\n\n   ```shell\n   docker-compose up -d\n   ```\n4. 对依赖服务 MySQL 进行以下设置（分布式部署主服务器时同理）\n    1. 将 voj.sql 和 nacos_config.sql 文件拷贝到容器/目录下：\n       ```\n       docker cp ../sql/voj.sql voj-mysql:/\n       docker cp ../sql/nacos_config.sql voj-mysql:/\n       ```\n    2. 进入 voj-mysql 容器并执行如下操作：\n        ```\n        # 进入mysql容器\n        docker exec -it voj-mysql /bin/bash\n        # 连接到mysql服务\n        mysql -uroot -pvoj123456\n        # 导入sql脚本\n        source /voj.sql\n        source /nacos_config.sql\n        ```\n    3. 退出 voj-mysql 容器。\n    \n5. docker-compose restart\n   \n   等待命令执行完毕后，查看容器状态\n\n   ```shell\n   docker ps -a\n   ```\n\n   当看到所有的容器的状态status都为`UP`或`healthy`就代表 OJ 已经启动成功。\n\n   以下默认参数说明\n   :::warning\n   - 默认超级管理员账号与密码：root / voj123456\n\n   - 默认 MySQL 账号与密码：root / voj123456（**正式部署请修改**）\n\n   - 默认 Nacos 管理员账号与密码：root / voj123456（**正式部署请修改**）\n\n   - 默认不开启 https，开启需修改文件同时提供证书文件\n\n   - 判题并发数默认：cpu核心数+1\n\n   - 默认开启vj判题，需要手动修改添加账号与密码，如果不添加不能vj判题！\n\n   - vj判题并发数默认：cpu核心数*2+1\n     \n\n   :::\n\n   \n\n**登录root账号到后台查看服务状态以及到`http://ip/admin/conf`修改服务配置！**\n\n<u>注意：网站的注册及用户账号相关操作需要邮件系统，所以请在系统配置中配置自己的SMTP邮件服务。</u>\n\n**(如果已经在启动在.env文件配置了邮件服务即不用再次修改)**\n\n```bash\nHost: smtp.qq.com\nPort: 465\nUsername: 邮箱账号\nPassword: 开启SMTP服务后生成的随机授权码\n```\n\n\n\n## 二、分布式部署\n\n:::tip\n主服务器（运行Nacos, backend, frontend, Redis）的服务器防火墙请开8848(Nacos), 3306(MySQL), 873(Rsync)端口\n\n从服务器（运行judger）的服务器防火墙请开8088端口\n:::\n\n1. 选择好需要安装的位置，运行下面命令\n\n   ```shell\n   git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy\n   ```\n\n2. 进入文件夹\n\n   ```shell\n   cd distributed\n   ```\n\n   `distributed`文件夹有以下：\n\n   ```bash\n   ├── judger\n   ├── main\n   ```\n\n3. 首先部署主服务，即数据后台服务（DataBackup）\n\n   ```shell\n   cd main\n   ```\n\n   该文件夹下有：\n\n   ```bash\n   ├── docker-compose.yml\n   ├── .env\n   ```\n\n   修改`.env`文件中的配置\n\n   ```shell\n   vim .env\n   ```\n\n   > 注意：各服务ip最好不改动，保持处于172.20.0.0/16网段的docker network\n\n   ```properties\n    # voj全部数据存储的文件夹位置（默认当前路径生成voj文件夹）\n    VOJ_DATA_DIRECTORY=./voj\n    \n    # Redis的配置\n    REDIS_HOST=172.20.0.2\n    REDIS_PORT=6379\n    \n    # MySQL的配置\n    MYSQL_HOST=172.20.0.3\n    # 请提供当前MySQL所在服务器的公网ip\n    MYSQL_PUBLIC_HOST=\n    MYSQL_PORT=3306\n    MYSQL_ROOT_PASSWORD=voj123456\n    \n    # Nacos的配置\n    NACOS_HOST=172.20.0.4\n    NACOS_PORT=8848\n    NACOS_USERNAME=nacos\n    NACOS_PASSWORD=nacos\n    \n    # backend后端服务的配置\n    BACKEND_HOST=172.20.0.5\n    BACKEND_PORT=6688\n    # JWT加密秘钥，默认则生成32位随机密钥\n    JWT_TOKEN_SECRET=default\n    # JWT过期时间默认为24小时(86400s)\n    JWT_TOKEN_EXPIRE=86400\n    # JWT默认12小时可自动刷新\n    JWT_TOKEN_FRESH_EXPIRE=43200\n    # 调用判题服务器的token，默认则生成32位随机密钥\n    JUDGE_TOKEN=default\n    \n    # 请使用邮件服务的域名或ip\n    EMAIL_SERVER_HOST=smtp.qq.com\n    EMAIL_SERVER_PORT=465\n    EMAIL_USERNAME=your_email_username\n    EMAIL_PASSWORD=your_email_password\n    \n    # 评测数据同步的配置\n    # 请修改数据同步密码\n    RSYNC_PASSWORD=voj123456\n    \n    # docker network的配置\n    SUBNET=172.20.0.0/16\n   ```\n   \n   配置修改保存后，当前路径下启动该服务\n   \n   ```shell\n   docker-compose up -d\n   ```\n   \n   等待命令执行完毕后，查看容器状态\n   \n   ```shell\n   docker ps -a\n   ```\n   \n   当看到所有的容器的状态status都为`UP`或`healthy`就代表 OJ 已经启动成功。\n   \n   \n   \n4. 接着，在另一台服务器上，依旧git clone该文件夹下来，然后进入`judger`文件夹，修改`.env`的配置\n\n   ```properties\n    # voj全部数据存储的文件夹位置（默认当前路径生成judge文件夹）\n    VOJ_JUDGESERVER_DATA_DIRECTORY=./judge\n    \n    # Nacos的配置\n    # 修改为Nacos所在服务的公网ip\n    NACOS_HOST=\n    # 修改为Nacos启动端口号，默认为8848\n    NACOS_PORT=8848\n    # 修改为Nacos的管理员账号\n    NACOS_USERNAME=nacos\n    # 修改为Nacos的管理员密码\n    NACOS_PASSWORD=nacos\n    \n    # judgeserver的配置\n    # 修改本服务器公网ip\n    JUDGE_SERVER_IP=\n    JUDGE_SERVER_PORT=8088\n    JUDGE_SERVER_NAME=judger-1\n    # -1表示可接收最大判题任务数为：cpu核心数+1\n    MAX_TASK_NUM=-1\n    # 当前判题服务器是否开启远程虚拟判题功能\n    REMOTE_JUDGE_OPEN=true\n    # -1表示可接收最大远程判题任务数为：cpu核心数*2+1\n    REMOTE_JUDGE_MAX_TASK_NUM=-1\n    # 默认沙盒并行判题程序数为cpu核心数\n    PARALLEL_TASK=default\n    \n    # rsync评测数据同步的配置\n    # 写入主服务器公网ip\n    RSYNC_MASTER_ADDR=\n    # 与主服务器的rsync密码一致\n    RSYNC_PASSWORD=voj123456\n   ```\n\n   配置修改保存后，当前路径下启动该服务\n\n   ```shell\n   docker-compose up -d\n   ```\n   :::tip\n   提示：需要开启多台判题机，就如当前第4步的操作一样，在每台服务器上执行以上的操作即可。\n   :::\n5. 两个服务都启动完成，在浏览器输入主服务ip或域名进行访问，登录root账号到后台查看服务状态。\n"
  },
  {
    "path": "docs/src/deploy/how-to-backup.md",
    "content": "# 如何备份\n\n### 1. 单体部署\n\n部署脚本（`docker-compose.yml`）同目录的`voj`文件夹中，存储了本系统的全部数据：\n\n```html\nvoj\n├── file   \t\t# 存储了上传的图片、上传的临时题目数据、markdown引用的文件等文件\n├── judge  \t\t# 存储了每个提交题目的评测过程产生的数据\n├── log    \t\t# 存储了voj-backend项目的运行日志\n├── testcase    # 存储了题目的评测数据\n└── data        \n    ├── mysql\n    │   ├── data # 存储了MySQL数据库的数据\n    ├── redis\n    │   ├── data # 存储了redis产生的快照数据\n```\n\n如果需要备份，只需将该`voj`文件夹复制一份即可，在新的机器上重新部署VOJ时，将该文件夹放置与`docker-compose.yml`同目录下，使用`docker-compose up -d`即可启动恢复原来的数据。\n\n\n\n### 2. 分布式部署\n\n- 主服务器（运行`voj-backend`的服务器）\n\n  部署脚本（`docker-compose.yml`）同目录的`voj`文件夹中，存储了本系统主服务器的全部数据：\n\n  ```html\n  voj\n  ├── file   \t\t# 存储了上传的图片、上传的临时题目数据、markdown引用的文件等文件\n  ├── log    \t\t# 存储了voj-backend项目的运行日志\n  ├── testcase    # 存储了题目的评测数据\n  └── data        \n      ├── mysql\n      │   ├── data # 存储了MySQL数据库的数据\n      ├── redis\n      │   ├── data # 存储了redis产生的快照数据\n  ```\n\n- 判题服务器（运行`voj-judger`的服务器）\n\n  部署脚本（`docker-compose.yml`）同目录的`voj`文件夹中，存储了本系统判题服务器的全部数据：\n\n  ```html\n  judge\n  ├── run  \t\t# 存储了每个提交题目的评测过程产生的数据\n  ├── log    \t\t# 存储了voj-judger项目的运行日志\n  ├── testcase    # 存储了题目的评测数据(每100s从主服务器同步)\n  ├── spj         # 存储了SPJ的代码\n  ```\n\n那么，主要要备份的还是**主服务器**的数据，只需将该`voj`文件夹复制一份即可，在新的机器上重新部署新的voj的时候，将该文件夹放置与`docker-compose.yml`一个目录下，使用`docker-compose up -d`即可启动恢复原来的数据。\n\n"
  },
  {
    "path": "docs/src/deploy/multi-judgeserver.md",
    "content": "# 多个判题机\n\n## 前言\n\n不同判题机之间是通过rsync进行数据同步的，所以需要配置相应的rsync服务。\n\n同时注意以下两点：\n\n1. 保证rsync-slave服务的密码与主服务rsync-master的数据同步密码一致\n2. rsync-slave服务（判题机服务器）拉取主服务rsync-master的评测数据是每100s一次，所以后台上传评测数据后，需等待大概100s才能正常判题。\n\n## 单体部署\n\n如果之前是选择了单体部署，也就是主服务器既有backend和judger服务，那么部署更多不同服务器的判题机应该如下修改：\n\n1. 在原先运行的服务器上，修改`voj-deploy/standAlone`文件夹里面的`docker-compose.yml`，**添加以下rsync-master服务**，数据同步密码请自行修改，如下：\n\n   **（注意：如果云服务器有防火墙请开启8848，3306，873端口）**\n\n   ```yaml\n   voj-rsync-master:\n       image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0\n       container_name: voj-rsync-master\n       volumes:\n         - ./voj/testcase:/voj/testcase:ro\n       environment:\n         - RSYNC_MODE=master\n         - RSYNC_USER=vojrsync \n         - RSYNC_PASSWORD=voj123456 # 请修改数据同步密码\n       ports:\n         - \"0.0.0.0:873:873\"\n   ```\n\n   **同时，需要将MySQL的配置`MYSQL_PUBLIC_HOST`改成当前服务器的公网IP**\n\n   ```shell\n   vim .env  # 修改与docker-compose.yml同目录下的配置文件\n   ```\n\n   ```yaml\n   # MySQL的配置\n   MYSQL_HOST=172.20.0.3\n   # 请提供当前mysql所在服务器的公网ip\n   MYSQL_PUBLIC_HOST=***\n   MYSQL_PUBLIC_PORT=3306\n   MYSQL_ROOT_PASSWORD=voj123456\n   ```\n   修改完保存，然后重启 Docker 即可生效\n   ```shell\n   docker-compose down\n   docker-compose up -d\n   ```\n2. 在其它服务器（判题服务器）中使用 Docker-Compose运行`voj-judger`服务，具体操作如下：\n\n   **（注意：服务器请开启8088端口号，需要将判题服务暴露出去）**\n\n\n   1. 下载文件，进入到指定文件夹\n\n      ```shell\n      git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/distributed/judger\n      ```\n\n   2. 修改配置`.env`文件\n\n      ```properties\n      # Nacos的配置\n      # 修改为Nacos所在服务的ip\n      NACOS_HOST=\n      # 修改为nacos启动端口号，默认为8848\n      NACOS_PORT=8848\n      # 修改为nacos的管理员账号\n      NACOS_USERNAME=root\n      # 修改为nacos的管理员密码\n      NACOS_PASSWORD=voj123456\n\n      # judgeserver的配置\n      # 修改为当前服务器公网ip\n      JUDGE_SERVER_IP=\n      JUDGE_SERVER_PORT=8088\n      JUDGE_SERVER_NAME=judger-1\n      # -1表示可接收最大判题任务数为：cpu核心数+1\n      MAX_TASK_NUM=-1\n      # 当前判题服务器是否开启远程虚拟判题功能\n      REMOTE_JUDGE_OPEN=true\n      # -1表示可接收最大远程判题任务数为：cpu核心数*2+1\n      REMOTE_JUDGE_MAX_TASK_NUM=-1\n      # 默认沙盒并行判题程序数为cpu核心数\n      PARALLEL_TASK=default\n\n      # rsync评测数据同步的配置\n      # 写入主服务器ip\n      RSYNC_MASTER_ADDR=\n      # 与主服务器的rsync密码一致\n      RSYNC_PASSWORD=voj123456\n      ```\n\n   3. 启动即可\n\n      ```shell\n      docker-compose up -d\n      ```\n\n   4. 验证：\n\n      访问 http://ip:8088/version\n      如果返回信息正常即启动成功！\n\n\n## 分布式部署\n\n1. 如果之前已经选择了分布式部署，那么增加判题机，则与原先启动判题机的操作一样即可，在新的服务器上操作如下：\n\n   ```shell\n   git clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/distributed/judger\n   vim .env\n   ```\n\n2. 修改`.env`文件的配置\n\n   ```properties\n   # voj全部数据存储的文件夹位置（默认当前路径生成judge文件夹）\n   VOJ_JUDGESERVER_DATA_DIRECTORY=./judge\n   \n   # Nacos的配置\n   # 修改为Nacos所在服务的ip\n   NACOS_HOST=\n   # 修改为Nacos启动端口号，默认为8848\n   NACOS_PORT=8848\n   # 修改为Nacos的管理员账号\n   NACOS_USERNAME=root\n   # 修改为Nacos的管理员密码\n   NACOS_PASSWORD=voj123456\n   \n   # judgeserver的配置\n   # 修改本服务器公网ip\n   JUDGE_SERVER_IP=\n   JUDGE_SERVER_PORT=8088\n   JUDGE_SERVER_NAME=judger-1\n   # -1表示可接收最大判题任务数为：cpu核心数+1\n   MAX_TASK_NUM=-1\n   # 当前判题服务器是否开启远程虚拟判题功能\n   REMOTE_JUDGE_OPEN=true\n   # -1表示可接收最大远程判题任务数为：cpu核心数*2+1\n   REMOTE_JUDGE_MAX_TASK_NUM=-1\n   # 默认沙盒并行判题程序数为cpu核心数\n   PARALLEL_TASK=default\n   \n   # rsync评测数据同步的配置\n   # 写入主服务器ip\n   RSYNC_MASTER_ADDR=\n   # 与主服务器的rsync密码一致\n   RSYNC_PASSWORD=voj123456\n   ```\n\n3. 修改完保存，启动即可。\n\n   ```shell\n   docker-compose up -d\n   ```\n"
  },
  {
    "path": "docs/src/deploy/open-https.md",
    "content": "# 开启HTTPS\n\n- 单机部署：\n\n  提供`server.pem`和`server.key`证书与密钥文件放置`/standAlone`目录下，与`docker-compose.yml`和`.env`文件放于同一位置，然后修改`docker-compose.yml`中的`voj-frontend`的配置\n\n- 分布式部署：\n\n  提供`server.pem`和`server.key`证书与密钥文件放置`/distributed/main`目录下，与`docker-compose.yml`和`.env`文件放置同一位置，然后修改`docker-compose.yml`中的`voj-frontend`的配置\n\n```yaml\nvoj-frontend:\n  image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_frontend\n  container_name: voj-frontend\n  restart: always\n  volumes:\n    - ./html/dist:/usr/share/nginx/html\n    # 开启https，请提供证书\n    - ./server.pem:/etc/nginx/conf.d/cert/server.pem\n    - ./server.key:/etc/nginx/conf.d/cert/server.key\n  environment:\n    - SERVER_NAME=localhost # 域名或localhost(本地)\n    - BACKEND_SERVER_HOST=${BACKEND_HOST:-172.20.0.5} # backend后端服务地址\n    - BACKEND_SERVER_PORT=${BACKEND_PORT:-6688} # backend后端服务端口号\n    - USE_HTTPS=true # 使用https请设置为true\n  ports:\n    - \"80:80\"\n    - \"443:443\"\n  networks:\n    voj-network:\n      ipv4_address: 172.20.0.6\n```\n\n"
  },
  {
    "path": "docs/src/deploy/update.md",
    "content": "# 如何更新\n\n## 一、无二次开发的更新\n:::warning\n2021.09.21之后部署voj的请看下面操作  \n:::\n请在对应的docker-compose.yml当前文件夹下执行`docker-compose pull`拉取最新镜像，然后重新`docker-compose up -d`即可。\n\n:::warning\n2021.09.21之前部署voj的请看下面操作\n:::\n\n### 1、修改MySQL8.0默认的密码加密方式\n\n（1）进行voj-mysql容器\n\n```shell\ndocker exec -it voj-mysql bash\n```\n\n  (2) 输入对应的mysql密码，进入mysql数据库\n\n  注意：-p 后面跟着数据库密码例如voj123456\n\n```shell\nmysql -uroot -p数据库密码\n```\n\n（3）成功进入后，执行以下命令\n\n```shell\nmysql> use mysql;\n\nmysql> grant all PRIVILEGES on *.* to root@'%' WITH GRANT OPTION;\n\n \nmysql> ALTER user 'root'@'%' IDENTIFIED BY '数据库密码' PASSWORD EXPIRE NEVER;\n\n \nmysql> ALTER user 'root'@'%' IDENTIFIED WITH mysql_native_password BY '数据库密码';\n\n \nmysql> FLUSH PRIVILEGES;\n```\n\n（4） 两次exit 退出mysql和容器\n\n### 2、 添加voj-mysql-checker模块\n\n（1）可以选择拉取仓库最新的docker-compose.yml文件（跟部署操作一样,但是会覆盖之前设置的参数）或者访问：\n\nhttps://github.com/simplefanc/voj-deploy/blob/master/standAlone/docker-compose.yml\n\n（2）或者编辑docker-compose.yml文件，手动添加新模块\n\n```yaml\n  voj-mysql-checker:\n    image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_database_checker\n    container_name: voj-mysql-checker\n    depends_on:\n      - voj-mysql\n    links:\n      - voj-mysql:mysql\n    environment:\n      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-voj123456}\n    networks:\n      voj-network:\n        ipv4_address: 172.20.0.8\n```\n\n(3)  保存后重启容器即可\n\n```shell\ndocker-compose down\n\ndocker-compose pull\n\ndocker-compose up -d\n```\n\n**注意**：此次修改成功后，以后更新，都请在对应的docker-compose.yml当前文件夹下执行`docker-compose pull`拉取最新镜像，然后重新`docker-compose up -d`即可。\n\n## 二、自定义前端的更新\n\n>  附加：如何自定义前端请看这里 => [自定义前端文档](/use/update-fe.html)\n\n（1）首先到`./voj/voj-vue`文件夹中，拉取[voj-vue](https://github.com/simplefanc/voj/tree/master/voj-vue)仓库最新的代码，请注意解决出现的冲突。\n\n```shell\ngit pull\n```\n\n或者重新直接download成zip包，然后重新自定义修改前端\n\n当然，如果想查看对比主仓库更新的内容，可以用以下命令一步步合并\n\n```bash\ngit remote -v                 # 查看主仓库的远程仓库\ngit fetch origin master:temp  # 将最新的主仓库代码拉到本地一个temp的分支\ngit diff temp                 # 比较现在本地代码与最新temp分支的区别\ngit merge temp                # 合并temp分支到本地的master分支\ngit branch -d temp            # 删除temp这个临时分支\n```\n\n（2）接着，重新用npm打包，在`./voj/voj-vue/dist`文件夹会生成静态的前端文件，放到原来指定的位置即可\n\n```shell\nnpm run build\n```\n\n（3）其它模块的更新，都请在对应的docker-compose.yml当前文件夹下执行`docker-compose pull`拉取最新镜像，然后重新`docker-compose up -d`即可。\n"
  },
  {
    "path": "docs/src/develop/db.md",
    "content": "# 数据库说明\n\n## 用户资料模块\n\nuser_info表\n\n| 列名         | 实体属性类型 | 键   | 备注                 |\n| ------------ | ------------ | ---- | -------------------- |\n| uuid         | String       | 主键 | uuid用户id           |\n| username     | String       |      | 登录账号             |\n| password     | String       |      | 登录密码             |\n| nickname     | String       |      | 用户昵称             |\n| school       | String       |      | 学校                 |\n| course       | String       |      | 专业                 |\n| number       | String       |      | 学号                 |\n| realname     | String       |      | 真实名字             |\n| email        | String       |      | 邮箱                 |\n| gender       | String       |      | 性别                 |\n| avatar       | String       |      | 头像图片地址         |\n| signature    | String       |      | 个性签名             |\n| cf_username  | String       |      | codeforces的username |\n| blog         | String       |      | 博客地址             |\n| github       | String       |      | github地址           |\n| title_name   | String       |      | 称号、头衔           |\n| title_color  | String       |      | 称号、头衔的背景颜色 |\n| status       | int          |      | 0可用，1不可用       |\n| gmt_create   | datetime     |      | 创建时间             |\n| gmt_modified | datetime     |      | 修改时间             |\n\nsession表  \n\n| 列名         | 实体属性类型 | 键   | 备注             |\n| ------------ | ------------ | ---- | ---------------- |\n| id           | long         | 主键 | auto_increment   |\n| uid          | String       | 外键 | 用户id           |\n| user_agent   | String       |      | 访问的浏览器参数 |\n| ip           | Srting       |      | 访问所在的ip     |\n| gmt_create   | datetime     |      | 创建时间         |\n| gmt_modified | datetime     |      | 修改时间         |\n\n\n\nrole 角色表  \n\n| 列名         | 实体属性类型 | 键   | 备注                       |\n| ------------ | ------------ | ---- | -------------------------- |\n| id           | long         | 主键 | auto_increment             |\n| role         | String       |      | “admin”，”tourist”，“user” |\n| description  | String       |      | 角色描述                   |\n| status       | int          |      | 是否可用,0可用 1不可用     |\n| gmt_create   | datetime     |      | 创建时间                   |\n| gmt_modified | datetime     |      | 修改时间                   |\n\n\n\nuser_role表\n\n| 列名         | 实体属性类型 | 键   | 备注           |\n| ------------ | ------------ | ---- | -------------- |\n| id           | long         | 主键 | auto_increment |\n| uid          | String       | 外键 | 用户id         |\n| role_id      | int          | 外键 | 角色id         |\n| gmt_create   | datetime     |      | 创建时间       |\n| gmt_modified | datetime     |      | 修改时间       |\n\n \n\n auth权限表\n\n| 列名         | 实体属性类型 | 键   | 备注                                                         |\n| ------------ | ------------ | ---- | ------------------------------------------------------------ |\n| id           | long         | 主键 | auto_increment                                               |\n| name         | String       |      | 权限名称，“superadmin”,”contest”，“admin”,”common”  普通用户默认为“common” |\n| permission   | String       |      | 权限字符串,例如“contest:1001”，发布某场比赛。  “all”,”select”,”update”等等， |\n| status       | int          |      | 0可用，1不可用                                               |\n| gmt_create   | datetime     |      | 创建时间                                                     |\n| gmt_modified | datetime     |      | 修改时间                                                     |\n\n \n\nrole_auth表\n\n| 列名         | 实体属性类型 | 键   | 备注           |\n| ------------ | ------------ | ---- | -------------- |\n| id           | long         | 主键 | auto_increment |\n| role_id      | int          |      | 角色id         |\n| auth_id      | int          |      | 权限id         |\n| gmt_create   | datetime     |      | 创建时间       |\n| gmt_modified | datetime     |      | 修改时间       |\n\n\n\n user_record表 个人做题记录表\n\n| 列名         | 实体属性类型 | 键          | 备注                       |\n| ------------ | ------------ | ----------- | -------------------------- |\n| id           | long         | primary key | auto_increment             |\n| uid          | String       | 外键        | 用户id                     |\n| rating       | int          |             | Cf得分，未参加过默认为1500 |\n| gmt_create   | datetime     |             | 创建时间                   |\n| gmt_modified | datetime     |             | 修改时间                   |\n\n \n\nuser_acproblem表\n\n| 列名         | 实体属性类型 | 键          | 备注           |\n| ------------ | ------------ | ----------- | -------------- |\n| id           | long         | primary key | auto_increment |\n| uid          | String       | 外键        | 用户id         |\n| pid          | long         | 外键        | Ac的题目id     |\n| subimit_id   | long         | 外键        | 提交的id       |\n| gmt_create   | datetime     |             | 创建时间       |\n| gmt_modified | datetime     |             | 修改时间       |\n\n \n\n \n\n## 题目详情模块\n\nproblem表\n\n| 列名                | 实体属性类型 | 键          | 备注                                                      |\n| ------------------- | ------------ | ----------- | --------------------------------------------------------- |\n| id                  | long         | primary key | auto_increment 1000开始                                   |\n| judge_mode          | String       |             | 默认为default、其他值有spj、interactive                   |\n| problem_id          | String       |             | 题目展示id                                                |\n| title               | String       |             | 题目标题                                                  |\n| author              | String       |             | 默认可为无                                                |\n| type                | int          |             | 题目类型 0为ACM,1为OI                                     |\n| time_limit          | int          |             | 时间限制(ms)，默认为c/c++限制,其它语言为2倍               |\n| memory_limit        | int          |             | 空间限制(mb)，默认为c/c++限制,其它语言为2倍               |\n| stack_limit         | int          |             | 栈限制(mb)，默认为128                                     |\n| description         | String       |             | 内容描述                                                  |\n| input               | String       |             | 输入描述                                                  |\n| output              | String       |             | 输出描述                                                  |\n| examples            | Srting       |             | 题面输入输出样例，不纳入评测数据                          |\n| source              | int          |             | 题目来源（比赛id），默认为voj,可能为爬虫vj                |\n| difficulty          | int          |             | 题目难度，0简单，1中等，2困难                             |\n| hint                | String       |             | 备注 提醒                                                 |\n| auth                | int          |             | 默认为1公开，2为私有，3为比赛中。                         |\n| io_score            | int          |             | 当该题目为io题目时的分数 默认为100                        |\n| code_share          | boolean      |             | 该题目对应的相关提交代码，用户是否可用分享                |\n| spj_code            | String       |             | 特判或交互程序代码                                        |\n| spj_language        | String       |             | 特判或交互程序的语言                                      |\n| user_extra_file     | String       |             | 选手程序的额外文件 json key：文件名 value：文件内容       |\n| judge_extra_file    | String       |             | 特判或交互程序的额外文件 json key：文件名 value：文件内容 |\n| is_remove_end_blank | boolean      |             | 是否默认去除用户代码的文末空格                            |\n| open_case_result    | boolean      |             | 是否默认开启该题目的测试样例结果查看                      |\n| caseVersion         | String       |             | 题目测试数据的版本号                                      |\n| is_upload_case      | boolean      |             | 是否是上传zip评测数据的                                   |\n| modified_user       | String       |             | 最新修改题目的用户                                        |\n| gmt_create          | datetime     |             | 创建时间                                                  |\n| gmt_modified        | datetime     |             | 修改时间                                                  |\n\n \n\nproblem_case表\n\n| 列名         | 实体属性类型 | 键          | 备注                 |\n| ------------ | ------------ | ----------- | -------------------- |\n| id           | long         | primary key | auto_increment       |\n| pid          | long         | 外键        | 题目id               |\n| input        | String       |             | 测试样例的输入文件名 |\n| output       | String       |             | 测试样例的输出文件名 |\n| status       | String       |             | 状态0可用，1不可用   |\n| gmt_create   | datetime     |             | 创建时间             |\n| gmt_modified | datetime     |             | 修改时间             |\n\n\n\ntag表  题目表的标签\n\n| 列名         | 实体属性类型 | 键   | 备注           |\n| ------------ | ------------ | ---- | -------------- |\n| id           | long         | 主键 | auto_increment |\n| name         | String       |      | 标签名字       |\n| color        | String       |      | 标签颜色       |\n| gmt_create   | datetime     |      | 创建时间       |\n| gmt_modified | datetime     |      | 修改时间       |\n\n\n\nproblem_tag表\n\n| 列名         | 实体属性类型 | 键   | 备注     |\n| ------------ | ------------ | ---- | -------- |\n| id           | int          |      | 主键id   |\n| tid          | int          |      | 标签id   |\n| gmt_create   | datetime     |      | 创建时间 |\n| gmt_modified | datetime     |      | 修改时间 |\n\n  \n\nlanguage表\n\n| 列名            | 实体属性类型 | 键   | 备注                         |\n| --------------- | ------------ | ---- | ---------------------------- |\n| id              | long         |      | 主键id                       |\n| content_type    | String       |      | 语言类型                     |\n| description     | String       |      | 语言描述                     |\n| name            | String       |      | 语言名字                     |\n| compile_command | String       |      | 编译指令                     |\n| template        | String       |      | A+B题目模板                  |\n| code_template   | String       |      | 语言对应的代码模板           |\n| is_spj          | boolean      |      | 是否可作为特殊判题的一种语言 |\n| oj              | String       |      | 该语言属于哪个oj，自身oj用ME |\n| gmt_create      | datetime     |      | 创建时间                     |\n| gmt_modified    | datetime     |      | 修改时间                     |\n\n  \n\ncode_template表\n\n| 列名         | 实体属性类型 | 键   | 备注     |\n| ------------ | ------------ | ---- | -------- |\n| id           | long         |      | 主键id   |\n| pid          | long         | 外键 | 题目id   |\n| lid          | long         | 外键 | 语言id   |\n| code         | String       |      | 代码模板 |\n| status       | boolean      |      | 是否启用 |\n| gmt_create   | datetime     |      | 修改时间 |\n| gmt_modified | datetime     |      | 修改时间 |\n\n## 提交评测模块\n\n> 判题结果status\n\n未提交：STATUS_NOT_SUBMITTED = -10\n\n提交中：STATUS_SUBMITTING = 9\n\n排队中：STATUS_PENDING  =  6\n\n评测中：STATUS_JUDGING =  7\n\n编译错误：STATUS_COMPILE_ERROR = -2\n\n输出格式错误：STATUS_PRESENTATION_ERROR = -3\n\n答案错误：STATUS__WRONG_ANSWER = -1\n\n评测通过：STATUS_ACCEPTED = 0\n\ncpu时间超限：STATUS__CPU_TIME_LIMIT_EXCEEDED = 1\n\n真实时间超限：STATUS__REAL_TIME_LIMIT_EXCEEDED = 2\n\n空间超限：STATUS__MEMORY_LIMIT_EXCEEDED = 3\n\n运行错误：STATUS__RUNTIME_ERROR = 4\n\n系统错误：STATUS__SYSTEM_ERROR = 5\n\nOI评测部分通过：STATUS_PARTIAL_ACCEPTED = 8\n\n提交失败：STATUS_SUBMITTED_FAILED= 10\n\njudge表\n\n| 列名          | 实体属性类型 | 键          | 备注                             |\n| ------------- | ------------ | ----------- | -------------------------------- |\n| submit_id     | long         | primary key | auto_increment                   |\n| display_pid   | String       |             | 题目展示id                       |\n| pid           | long         | 外键        | 题目id                           |\n| uid           | String       | 外键        | 提交用户的id                     |\n| username      | String       | 外键        | 用户名                           |\n| submit_time   | datetime     |             | 提交时间                         |\n| status        | String       |             | 判题结果                         |\n| share         | Boolean      |             | 代码是否分享                     |\n| error_message | String       |             | 错误提醒（编译错误，或者vj提醒） |\n| time          | int          |             | 运行时间                         |\n| memory        | int          |             | 所耗内存                         |\n| length        | int          |             | 代码长度                         |\n| code          | String       |             | 代码                             |\n| language      | String       |             | 代码语言                         |\n| cpid          | int          |             | 比赛中的题目编号id               |\n| judger        | String       |             | 判题机ip                         |\n| ip            | String       |             | 提交者ip                         |\n| cid           | int          |             | 题目来源的比赛id，默认为0        |\n| version       | int          |             | 乐观锁（废弃）                   |\n| oi_rank_score | int          |             | oi排行榜得分                     |\n| gmt_create    | datetime     |             | 创建时间                         |\n| gmt_modified  | datetime     |             | 修改时间                         |\n\n \n\njugde_case表 评测单个样例结果表\n\n| 列名         | 实体属性类型 | 键   | 备注             |\n| ------------ | ------------ | ---- | ---------------- |\n| submit_id    | long         | 外键 | 提交id           |\n| problemId    | String       | 外键 | 题目id           |\n| userId       | String       | 外键 | 提交用户的id     |\n| Status       | String       |      | 单个样例评测结果 |\n| time         | int          |      | 运行时间         |\n| memory       | int          |      | 运行内存         |\n| case_id      | String       |      | 测试样例id       |\n| input_data   | String       |      | 样例输入的文件名 |\n| Output_data  | String       |      | 样例输出的文件名 |\n| user_output  | Srting       |      | 暂时用作信息提示 |\n| gmt_create   | datetime     |      | 创建时间         |\n| gmt_modified | datetime     |      | 修改时间         |\n\n \n\n \n\n## 比赛模块\n\n更新比赛状态的存储过程\n\n ```sql\nDELIMITER |\n\nDROP PROCEDURE IF EXISTS contest_status |\nCREATE PROCEDURE contest_status()\n\n    BEGIN\n      UPDATE contest \n\tSET STATUS = (\n\tCASE \n\t  WHEN NOW() < start_time THEN -1 \n\t  WHEN NOW() >= start_time AND NOW()<end_time THEN  0\n\t  WHEN NOW() >= end_time THEN 1\n\tEND);\n    END\n|\n\n ```\n\n\n\n创建插入时的触发器\n\n```sql\nDROP TRIGGER IF EXISTS contest_trigger;\n\nDELIMITER $$\nCREATE TRIGGER contest_trigger\nBEFORE INSERT ON contest FOR EACH ROW\nBEGIN\nSET new.status=(\n\tCASE \n\t  WHEN NOW() < new.start_time THEN -1 \n\t  WHEN NOW() >= new.start_time AND NOW()<new.end_time THEN  0\n\t  WHEN NOW() >= new.end_time THEN 1\n\tEND);\nEND$$\nDELIMITER ;\n```\n\n\n\n设置定时器\n\n```sql\nSET GLOBAL event_scheduler = 1;  // 开启定时器\nCREATE EVENT IF NOT EXISTS contest_event\n\nON SCHEDULE EVERY 1 SECOND // 每秒执行一次\n\nON COMPLETION PRESERVE  \n\nDO CALL contest_status(); // 调用存储过程\n```\n\n开启或关闭定时器\n\n```sql\nALTER EVENT contest_event ON  COMPLETION PRESERVE ENABLE;   -- 开启事件\nALTER EVENT contest_event ON  COMPLETION PRESERVE DISABLE;  -- 关闭事件\n```\n\n\n\ncontest表\n\n| 列名               | 实体属性类型 | 键   | 备注                                                  |\n| ------------------ | ------------ | ---- | ----------------------------------------------------- |\n| id                 | long         | 主键 | auto_increment  1000起步                              |\n| uid                | String       | 外键 | 创建者id                                              |\n| author             | String       |      | 比赛创建者的用户名                                    |\n| title              | String       |      | 比赛标题                                              |\n| type               | int          |      | ACM赛制或者Rating                                     |\n| source             | int          |      | 比赛来源，原创为0，克隆赛为比赛id                     |\n| auth               | int          |      | 0为公开赛，1为私有赛（有密码），3为保护赛（有密码）。 |\n| pwd                | string       |      | 比赛密码                                              |\n| start_time         | datetime     |      | 开始时间                                              |\n| end_time           | datetime     |      | 结束时间                                              |\n| duration           | long         |      | 比赛时长（s）                                         |\n| description        | Srting       |      | 比赛说明                                              |\n| seal_rank          | boolean      |      | 是否开启封榜                                          |\n| seal_rank_time     | datetime     |      | 封榜起始时间，一直到比赛结束，不刷新榜单。            |\n| status             | int          |      | -1为未开始，0为进行中，1为已结束                      |\n| visible            | boolean      |      | 是否可见                                              |\n| open_print         | boolean      |      | 是否打开打印功能                                      |\n| open_account_limit | boolean      |      | 是否开启账号限制                                      |\n| account_limit_rule | String       |      | 账号限制规则                                          |\n| rank_show_name     | String       |      | 排行榜显示（username、nickname、realname）            |\n| star_account       | Stirng       |      | 打星用户列表                                          |\n| open_rank          | boolean      |      | 是否开放赛外榜单                                      |\n| auto_real_rank     | boolean      |      | 比赛结束是否自动解除封榜,自动转换成真实榜单           |\n| gmt_create         | datetime     |      | 创建时间                                              |\n| gmt_modified       | datetime     |      | 修改时间                                              |\n\n \n\ncontest_problem表\n\n| 列名          | 实体属性类型 | 键   | 备注                               |\n| ------------- | ------------ | ---- | ---------------------------------- |\n| id            | long         | 主键 | auto_increment                     |\n| display_id    | String       |      | 展示的id                           |\n| cid           | long         | 外键 | 比赛id                             |\n| pid           | long         | 外键 | 题目id                             |\n| display_title | String       |      | 该题目在比赛中的标题，默认为原名字 |\n| color         | String       |      | 气球颜色，不设置则不显示           |\n| gmt_create    | datetime     |      | 创建时间                           |\n| gmt_modified  | datetime     |      | 修改时间                           |\n\n \n\ncontest_register表 比赛报名表\n\n| 列名         | 实体属性类型 | 键   | 备注                       |\n| ------------ | ------------ | ---- | -------------------------- |\n| id           | long         | 主键 | auto_increment             |\n| cid          | long         | 外键 | 比赛id                     |\n| uid          | String       | 外键 | 用户id                     |\n| status       | int          |      | 默认为0表示正常，1为失效。 |\n| gmt_create   | datetime     |      | 创建时间                   |\n| gmt_modified | datetime     |      | 修改时间                   |\n\n\n\ncontest_score表 rating赛制中获得的分数更改记录表（未使用）\n\n| 列名         | 实体属性类型 | 键   | 备注              |\n| ------------ | ------------ | ---- | ----------------- |\n| id           | long         | 主键 | auto_increment    |\n| cid          | long         | 外键 | 比赛id            |\n| last         | int          |      | 比赛前的score得分 |\n| change       | int          |      | Score比分变化     |\n| now          | int          |      | 现在的score       |\n| gmt_create   | datetime     |      | 创建时间          |\n| gmt_modified | datetime     |      | 修改时间          |\n\n \n\ncontest_record表 比赛记录表\n\n| 列名         | 实体属性类型 | 键   | 备注                                                         |\n| ------------ | ------------ | ---- | ------------------------------------------------------------ |\n| id           | long         | 主键 | auto_increment                                               |\n| cid          | long         | 外键 | 比赛id                                                       |\n| uid          | String       | 外键 | 用户id                                                       |\n| pid          | int          | 外键 | 题目id                                                       |\n| cpid         | int          | 外键 | 比赛中的题目id                                               |\n| submit_id    | int          | 外键 | 提交id，用于可重判                                           |\n| display_id   | String       |      | 比赛展示的id                                                 |\n| username     | String       |      | 用户名                                                       |\n| realname     | String       |      | 真实姓名（废弃）                                             |\n| status       | int          |      | 提交结果，0表示未AC通过不罚时，1表示AC通过，-1为未AC通过算罚时 |\n| submit_time  | datetime     |      | 具体提交时间                                                 |\n| time         | int          |      | 提交时间，为提交时间减去比赛时间，时间戳                     |\n| score        | int          |      | OI比赛得分                                                   |\n| use_time     | int          |      | 提交的程序运行耗时                                           |\n| first_blood  | Boolean      |      | 是否为一血AC（废弃）                                         |\n| checked      | Boolean      |      | AC是否已校验                                                 |\n| gmt_create   | datetime     |      | 创建时间                                                     |\n| gmt_modified | datetime     |      | 修改时间                                                     |\n\n \n\ncontest_print表 比赛打印表\n\n| 列名         | 实体属性类型 | 键   | 备注               |\n| ------------ | ------------ | ---- | ------------------ |\n| id           | long         | 主键 | auto_increment     |\n| cid          | long         | 外键 | 比赛id             |\n| username     | String       |      | 提交打印文本的用户 |\n| realname     | String       |      | 真实姓名           |\n| content      | String       |      | 需要打印的文本内容 |\n| status       | int          |      | 状态 是否已打印    |\n| gmt_create   | datetime     |      | 创建时间           |\n| gmt_modified | datetime     |      | 修改时间           |\n\n\n\nannouncement表 \n\n| 列名         | 实体属性类型 | 键   | 备注                                           |\n| ------------ | ------------ | ---- | ---------------------------------------------- |\n| id           | long         | 主键 | auto_increment                                 |\n| title        | String       |      | 公告标题                                       |\n| content      | String       |      | 公告内容                                       |\n| uid          | String       | 外键 | 发布者id（必须为比赛创建者或者超级管理员才能） |\n| gmt_create   | datetime     |      | 创建时间                                       |\n| gmt_modified | datetime     |      | 修改时间                                       |\n\n\n\ncontest_announcement表 比赛时的通知表\n\n| 列名         | 实体属性类型 | 键   | 备注           |\n| ------------ | ------------ | ---- | -------------- |\n| id           | long         | 主键 | auto_increment |\n| aid          | long         | 外键 | 公告id         |\n| cid          | int          | 外键 | 比赛id         |\n| gmt_create   | datetime     |      | 创建时间       |\n| gmt_modified | datetime     |      | 修改时间       |\n\n \n\ncontest_explanation表 赛后题解表**(未使用)**\n\n| 列名         | 实体属性类型 | 键   | 备注                                         |\n| ------------ | ------------ | ---- | -------------------------------------------- |\n| id           | long         | 主键 | auto_increment                               |\n| cid          | int          | 外键 | 比赛id                                       |\n| content      | String       |      | 内容(支持markdown)                           |\n| uid          | int          |      | 发布者（必须为比赛创建者或者超级管理员才能） |\n| gmt_create   | datetime     |      | 创建时间                                     |\n| gmt_modified | datetime     |      | 修改时间                                     |\n\n \n\n## 训练(题单)模块\n\n题单训练表 training\n\n| 列名         | 实体属性类型 | 键   | 备注                              |\n| ------------ | ------------ | ---- | --------------------------------- |\n| id           | long         | 主键 |                                   |\n| title        | string       |      | 训练题单名称                      |\n| description  | string       |      | 训练题单简介                      |\n| author       | string       | 外键 | 训练题单创建者用户名              |\n| auth         | string       |      | 训练题单权限类型：Public、Private |\n| private_pwd  | string       |      | 训练题单权限为Private时的密码     |\n| rank         | int          |      | 编号，升序                        |\n| status       | boolean      |      | 是否可用                          |\n| gmt_create   | datetime     |      | 创建时间                          |\n| gmt_modified | datetime     |      | 修改时间                          |\n\n\n\n训练注册表 training_register\n\n| 列名         | 实体属性类型 | 键   | 备注     |\n| ------------ | ------------ | ---- | -------- |\n| id           | long         | 主键 |          |\n| tid          | long         | 外键 | 训练id   |\n| uid          | long         | 外键 | 用户id   |\n| status       | boolean      |      | 是否可用 |\n| gmt_create   | datetime     |      | 创建时间 |\n| gmt_modified | datetime     |      | 修改时间 |\n\n\n\n训练与题目关联表 training_problem\n\n| 列名         | 实体属性类型 | 键   | 备注          |\n| ------------ | ------------ | ---- | ------------- |\n| id           | long         | 主键 |               |\n| tid          | long         | 外键 | 训练id        |\n| pid          | long         | 外键 | 题目id        |\n| display_id   | string       |      | 排序用 展示id |\n| gmt_create   | datetime     |      | 创建时间      |\n| gmt_modified | datetime     |      | 修改时间      |\n\n\n\n训练记录表 training_record\n\n| 列名         | 实体属性类型 | 键   | 备注       |\n| ------------ | ------------ | ---- | ---------- |\n| id           | long         | 主键 |            |\n| tid          | long         | 外键 | 训练id     |\n| tpid         | long         | 外键 | 训练题目id |\n| pid          | long         | 外键 | 题目id     |\n| uid          | string       | 外键 | 用户id     |\n| submit_id    | long         | 外键 | 提交id     |\n| gmt_create   | datetime     |      | 创建时间   |\n| gmt_modified | datetime     |      | 修改时间   |\n\n\n\n训练分类表 training_category\n\n| 列名         | 实体属性类型 | 键   | 备注         |\n| ------------ | ------------ | ---- | ------------ |\n| id           | long         | 主键 |              |\n| name         | string       |      | 分类名称     |\n| color        | string       |      | 分类背景颜色 |\n| gmt_create   | datetime     |      | 创建时间     |\n| gmt_modified | datetime     |      | 修改时间     |\n\n\n\n训练分类关联表 mapping_training_category\n\n| 列名         | 实体属性类型 | 键   | 备注                            |\n| ------------ | ------------ | ---- | ------------------------------- |\n| id           | long         | 主键 |                                 |\n| tid          | long         | 外键 | 训练id                          |\n| cid          | long         | 外键 | 训练分类id（training_category） |\n| gmt_create   | datetime     |      | 创建时间                        |\n| gmt_modified | datetime     |      | 修改时间                        |\n\n \n\n## 讨论模块\n\n>  包括题目讨论区，公共讨论区，比赛评论\n\n\n\ncategory表\n\n| 列名         | 实体属性类型 | 键   | 备注           |\n| ------------ | ------------ | ---- | -------------- |\n| id           | long         | 主键 | auto_increment |\n| name         | String       |      | 分类名字       |\n| gmt_create   | datetime     |      | 创建时间       |\n| gmt_modified | datetime     |      | 修改时间       |\n\n\n\ndiscussion表\n\n| 列名         | 实体属性类型 | 键   | 备注                             |\n| ------------ | ------------ | ---- | -------------------------------- |\n| id           | int          | 主键 | auto_increment                   |\n| category_id  | int          | 外键 | 分类id                           |\n| title        | String       | 外键 | 讨论标题                         |\n| content      | String       |      | 讨论详情                         |\n| description  | String       |      | 讨论描述                         |\n| pid          | String       | 外键 | 引用的题目id，默认未null则不引用 |\n| uid          | iString      | 外键 | 发布讨论的用户id                 |\n| author       | String       | 外键 | 发布讨论的用户名                 |\n| avatar       | String       | 外键 | 发布讨论的用户头像地址           |\n| role         | String       |      | 发布讨论的用户角色               |\n| view_num     | int          |      | 浏览数量                         |\n| like_num     | int          |      | 点赞数量                         |\n| top_priority | boolean      |      | 优先级，是否置顶                 |\n| comment_num  | int          |      | 评论数量                         |\n| status       | int          |      | 是否封禁或逻辑删除该讨论         |\n| gmt_create   | datetime     |      | 创建时间                         |\n| gmt_modified | datetime     |      | 修改时间                         |\n\n\n\ndiscussion_like表\n\n| 列名         | 实体属性类型 | 键   | 备注           |\n| ------------ | ------------ | ---- | -------------- |\n| id           | long         | 主键 | auto_increment |\n| did          | int          | 外键 | 讨论id         |\n| uid          | String       | 外键 | 用户id         |\n| gmt_create   | datetime     |      | 创建时间       |\n| gmt_modified | datetime     |      | 修改时间       |\n\n\n\ndiscussion_report表\n\n| 列名         | 实体属性类型 | 键   | 备注           |\n| ------------ | ------------ | ---- | -------------- |\n| id           | long         | 主键 | auto_increment |\n| did          | int          | 外键 | 讨论id         |\n| reporter     | String       | 外键 | 举报者的用户名 |\n| content      | String       |      | 举报内容       |\n| status       | boolean      |      | 是否已读       |\n| gmt_create   | datetime     |      | 创建时间       |\n| gmt_modified | datetime     |      | 修改时间       |\n\n\n\ncomment表\n\n| 列名         | 实体属性类型 | 键   | 备注                                   |\n| ------------ | ------------ | ---- | -------------------------------------- |\n| id           | int          | 主键 | auto_increment                         |\n| cid          | long         | 外键 | 比赛id，NULL表示无引用比赛             |\n| did          | int          | 外键 | 讨论id，NULL表示无引用讨论             |\n| content      | String       |      | 评论内容                               |\n| from_uid     | String       | 外键 | 评论者id                               |\n| from_name    | String       | 外键 | 评论者用户名                           |\n| from_avatar  | String       | 外键 | 评论者头像地址                         |\n| from_role    | String       | 外键 | 评论者角色                             |\n| like_num     | int          |      | 点赞数量                               |\n| status       | int          |      | 是否封禁或逻辑删除该评论，0正常，1封禁 |\n| gmt_create   | datetime     |      | 创建时间                               |\n| gmt_modified | datetime     |      | 修改时间                               |\n\n\n\ncomment_like表\n\n| 列名         | 实体属性类型 | 键   | 备注           |\n| ------------ | ------------ | ---- | -------------- |\n| id           | lint         | 主键 | auto_increment |\n| cid          | int          | 外键 | 评论id         |\n| uid          | String       | 外键 | 用户id         |\n| gmt_create   | datetime     |      | 创建时间       |\n| gmt_modified | datetime     |      | 修改时间       |\n\n\n\nreply表\n\n| 列名         | 实体属性类型 | 键   | 备注                                   |\n| ------------ | ------------ | ---- | -------------------------------------- |\n| id           | int          | 主键 | auto_increment                         |\n| comment_id   | ind          | 外键 | 评论id                                 |\n| content      | String       |      | 回复的内容                             |\n| from_uid     | String       | 外键 | 回复评论者id                           |\n| from_name    | String       | 外键 | 回复评论者用户名                       |\n| from_avatar  | String       | 外键 | 回复评论者头像地址                     |\n| from_role    | String       | 外键 | 回复评论者角色                         |\n| to_uid       | String       | 外键 | 被回复的用户id                         |\n| to_name      | String       | 外键 | 被回复的用户名                         |\n| to_avatar    | String       | 外键 | 被回复的用户头像地址                   |\n| status       | int          |      | 是否封禁或逻辑删除该回复，0正常，1封禁 |\n| gmt_create   | datetime     |      | 创建时间                               |\n| gmt_modified | datetime     |      | 修改时间                               |\n\n\n\n## 站内消息模块\n\nadmin_sys_notice表\n\n| 列名         | 实体属性类型 | 键   | 备注                                                         |\n| ------------ | ------------ | ---- | ------------------------------------------------------------ |\n| id           | int          | 主键 | auto_increment                                               |\n| title        | String       |      | 通知标题                                                     |\n| content      | String       |      | 通知内容                                                     |\n| type         | String       |      | 发给哪些用户类型,例如全部用户All，指定单个用户Single，管理员Admin |\n| state        | boolean      |      | 是否已被拉取过，如果已经拉取过，就无需再次拉取               |\n| recipient_id | String       | 外键 | 接受通知的用户的id，如果type为single，那么recipient 为该用户的id;否则recipient为null |\n| admin_id     | String       | 外键 | 发布通知的管理员id                                           |\n| gmt_create   | datetime     |      | 创建时间                                                     |\n| gmt_modified | datetime     |      | 修改时间                                                     |\n\nuser_sys_notice表\n\n| 列名          | 实体属性类型 | 键   | 备注                                |\n| ------------- | ------------ | ---- | ----------------------------------- |\n| id            | int          | 主键 | auto_increment                      |\n| sys_notice_id | long         | 外键 | 系统通知的id                        |\n| recipient_id  | String       | 外键 | 接受通知的用户的id                  |\n| type          | String       |      | 消息类型，系统通知Sys、我的信息Mine |\n| state         | boolean      |      | 是否已读                            |\n| gmt_create    | datetime     |      | 创建时间                            |\n| gmt_modified  | datetime     |      | 修改时间                            |\n\n\n\nmsg_remind表\n\n| 列名           | 实体属性类型 | 键   | 备注                                                         |\n| -------------- | ------------ | ---- | ------------------------------------------------------------ |\n| id             | int          | 主键 | auto_increment                                               |\n| action         | String       |      | 动作类型，如点赞讨论帖Like_Post、点赞评论Like_Discuss、评论Discuss、回复Reply等 |\n| source_id      | int          |      | 消息来源id，讨论id或比赛id                                   |\n| source_type    | String       |      | 事件源类型：'Discussion'、'Contest'等                        |\n| source_content | String       |      | 事件源的内容，比如回复的内容，回复的评论等等,不超过250字符，超过使用... |\n| quote_id       | int          |      | 事件引用上一级评论或回复id                                   |\n| quote_type     | String       |      | 事件引用上一级的类型：Comment、Reply                         |\n| url            | String       |      | 事件所发生的地点链接 url                                     |\n| recipient_id   | String       | 外键 | 接受通知的用户的id                                           |\n| sender_id      | String       | 外键 | 动作执行者的id                                               |\n| state          | boolean      |      | 是否已读                                                     |\n| gmt_create     | datetime     |      | 创建时间                                                     |\n| gmt_modified   | datetime     |      | 修改时间                                                     |\n\n## 文件模块\n\nfile表\n\n| 列名         | 实体属性类型 | 键   | 备注                     |\n| ------------ | ------------ | ---- | ------------------------ |\n| id           | long         | 主键 | auto_increment           |\n| uid          | String       |      | 用户id                   |\n| name         | String       |      | 文件名                   |\n| suffix       | String       |      | 文件后缀格式             |\n| folder_path  | String       |      | 文件所在文件夹的路径     |\n| file_path    | String       |      | 文件绝对路径             |\n| type         | String       |      | 文件所属类型，例如avatar |\n| delete       | String       |      | 是否删除                 |\n| gmt_create   | datetime     |      | 创建时间                 |\n| gmt_modified | datetime     |      | 修改时间                 |\n\n## 判题机模块\n\njudge_server表\n\n| 列名            | 实体属性类型 | 键   | 备注                                             |\n| --------------- | ------------ | ---- | ------------------------------------------------ |\n| id              | int          | 主键 | auto_increment                                   |\n| name            | String       |      | 判题服务名字                                     |\n| ip              | String       |      | 判题机ip                                         |\n| port            | int          |      | 判题机端口号                                     |\n| url             | String       |      | ip:port                                          |\n| cpu_core        | int          |      | 判题机所在服务器cpu核心数                        |\n| task_number     | int          |      | 当前判题数                                       |\n| max_task_number | int          |      | 判题并发最大数                                   |\n| status          | int          |      | 0可用，1不可用                                   |\n| version         | long         |      | 版本控制                                         |\n| is_remote       | boolean      |      | 是否为远程判题vj                                 |\n| cf_submittable  | boolean      |      | 当前机器是否可提交cf，控制机器一次只能一账号交题 |\n| gmt_create      | datetime     |      | 创建时间                                         |\n| gmt_modified    | datetime     |      | 修改时间                                         |\n\nremote_judge_account表\n\n| 列名         | 实体属性类型 | 键   | 备注               |\n| ------------ | ------------ | ---- | ------------------ |\n| id           | int          | 主键 | auto_increment     |\n| oj           | String       |      | vjudge交题的oj名字 |\n| username     | String       |      | vjudge登录的账号   |\n| password     | int          |      | vjudge登录的密码   |\n| status       | int          |      | 0可用，1不可用     |\n| version      | long         |      | 版本控制           |\n| gmt_create   | datetime     |      | 创建时间           |\n| gmt_modified | datetime     |      | 修改时间           |\n"
  },
  {
    "path": "docs/src/develop/judge_dispatcher.md",
    "content": "# 调度与评测\n\n![评测调度](/judge_dispatch.png)\n\n**评测的调用流程有如下步骤：**\n\n1. 用户登录后进入指定题目的详情页，编辑完代码后提交；\n2. 后端业务服务接收到提交信息后，校验提交数据后写入到MySQL数据库；\n3. 写入数据库成功后，将该评测任务放入到Redis的等待评测队列中，然后返回告知用户已经成功提交；\n4. 接着取出Redis中的等待评测队列头部的任务，查询Nacos获取健康可用的评测服务实例列表，通过悲观锁控制并发资源的调度，发送评测请求到有空闲评测资源的评测服务实例；\n5. 评测服务接受到调用评测请求后，将通过Http请求先后调用安全沙盒（Go-Judge）进行用户代码的编译与运行，根据每个评测点数据的运行结果，得出最终评测结果写回到数据库。\n6. 在这个过程中，用户在题目详情页提交成功代码后，前端页面将开启每2秒查询一次结果的定时器，直至该提交的评测的最终状态不再是评测中结束。\n\n:::tip\n\nVOJ有四种评测模式：普通评测、特殊评测、交互评测、远程评测，具体的介绍请看文档： [判题模式](/use/judge-mode/)\n\n:::\n\n### 一、普通评测\n\n![普通评测](/default_judge.png)\n\n**普通评测**：先调用安全沙盒服务编译用户提交的代码，如果编译失败则直接结束，返回结果为编译失败，接着调用安全沙盒服务运行用户程序，传入题目标准输入文件的文件路径、题目运行时间限制、题目运行空间限制等参数，等待每个数据点的评测结束，比较每个数据点的时间和空间是否超过题目规定的时空限制，然后对比用户程序输出和题目标准输出得出最终的评测结果，写回数据库。\n\n### 二、特殊评测\n\n![特殊评测](/spj_judge.png)\n\n**特殊评测**：先调用安全沙盒服务编译用户提交的代码，如果编译失败则直接结束，返回结果为编译失败，然后检查是否存在已经编译完成的特殊程序，否则需要先调用服务编译该特殊程序代码，如果编译失败，则返回结果为系统错误。接着运行用户程序读取每个标准输入文件，获得结果，判断时空是否超限，超限就返回时间超限或空间超限的结果，不然就运行特殊程序读取题目标准输入和标准输出、用户程序的输出文件，对比后得出评测结果，写回数据库。\n\n### 三、交互评测\n\n![交互评测](/interactive_judge.png)\n\n**交互评测**：先调用安全沙盒服务编译用户提交的代码，如果编译失败则直接结束，返回结果为编译失败，然后检查是否存在已经编译完成的交互程序，否则需要先调用服务编译该交互程序代码，如果编译失败，则返回结果为系统错误。接着运行用户程序和交互程序，两者程序进行标准输出和标准输入流的交互，最后得出结果，写回数据库。\n\n### 四、远程评测\n\n![远程评测](/remote_judge.png)\n\n**远程评测**: 目前本系统支持CF、HDU、POJ等平台的题目评测，主要实现的原理是爬虫模拟技术，首先先使用远程平台的账户登录，获取登录账户的cookie，配置到提交代码的接口参数里面，同时填入用户提交的代码、对应的题号、编译语言等信息，请求提交结果后可能失败，这时候需要设置重试机制，但多次重试依旧提交失败，则直接将结果写回数据库，如果获取到该提交对应的ID，则将该ID交给任务线程池进行每3秒一次的查询结果轮询，如果超过3分钟没有得出结果，则判断为提交失败写回数据库，否则就根据结果映射转换成自己平台的结果写回数据库。\n"
  },
  {
    "path": "docs/src/develop/sandbox.md",
    "content": "# 安全沙盒的调用\n\n> Judger-SandBox使用的是开源项目[go-judge](https://github.com/criyle/go-judge)Linux版本的可执行文件，更多调用方式请自行浏览[go-judge](https://github.com/criyle/go-judge)\n\nVOJ使用Java来调用此沙盒，详见[voj-judger](https://github.com/simplefanc/voj-springboot/blob/main/voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local)模块下的`SandboxRun.java`\n\n启动[SandBox](https://github.com/criyle/go-judge/releases)，默认监听5050端口\n\n### 验证是否启动\n\n访问：`http://localhost:5050/version`\n\n### 编译\n\n1.1 请求的url为 \n\n> `http://localhost:5050/run`\n\n1.2 请求方式\n\n> POST\n\n1.3 请求参数\n\n> 数据格式为json，内容如下\n\n```shell\n {\n    \"cmd\": [\n        {\n            \"args\": [\n                \"/usr/bin/g++\", \n                \"a.cc\", \n                \"-o\", \n                \"a\"\n            ], \n            \"env\": [\n                \"PATH=/usr/bin:/bin\"\n            ], \n            \"files\": [\n                {\n                    \"content\": \"\"\n                }, \n                {\n                    \"name\": \"stdout\", \n                    \"max\": 10240\n                }, \n                {\n                    \"name\": \"stderr\", \n                    \"max\": 10240\n                }\n            ], \n            \"cpuLimit\": 10000000000, \n            \"memoryLimit\": 104857600, \n            \"procLimit\": 50, \n            \"copyIn\": {\n                \"a.cc\": {\n                  \"content\": \"#include <iostream>\\nusing namespace std;\\nint main() {\\nint a, b;\\ncin >> a >> b;\\ncout << a + b << endl;\\n}\"\n                }\n            }, \n            \"copyOut\": [\n                \"stdout\", \n                \"stderr\"\n            ], \n            \"copyOutCached\": [\n                \"a.cc\", \n                \"a\"\n            ], \n            \"copyOutDir\": \"1\"\n        }\n    ]\n}\n```\n\n1.4 返回的数据为json格式\n\n```json\n  [\n         {\n             \"status\": \"Accepted\",\n             \"exitStatus\": 0,\n             \"time\": 303225231,\n             \"memory\": 32243712,\n             \"runTime\": 524177700,\n             \"files\": {\n                 \"stderr\": \"\",\n                 \"stdout\": \"\"\n             },\n             \"fileIds\": {\n                 \"a\": \"WDQL5TNLRRVB2KAP\",\n                 \"a.cc\": \"NOHPGGDTYQUFRSLJ\"\n             }\n         }\n     ]\n```\n\n### 运行与评测\n\n2.1 请求的url为 \n\n> `http://localhost:5050/run`\n\n2.2 请求方式\n\n> POST\n\n2.3 请求参数\n\n> 数据格式为json，内容如下\n\n```json\n{\n    \"cmd\": [{\n        \"args\": [\"a\"],\n        \"env\": [\"PATH=/usr/bin:/bin\",\"LANG=en_US.UTF-8\",\"LC_ALL=en_US.UTF-8\",\"LANGUAGE=en_US:en\"],\n        \"files\": [{\n            \"src\": \"/judge/test_case/problem_1010/1.in\"\n        }, {\n            \"name\": \"stdout\",\n            \"max\": 10240\n        }, {\n            \"name\": \"stderr\",\n            \"max\": 10240\n        }],\n        \"cpuLimit\": 10000000000,\n        \"realCpuLimit\":30000000000,\n        \"stackLimit\":134217728,\n        \"memoryLimit\": 104811111,\n        \"procLimit\": 50,\n        \"copyIn\": {\n            \"a\":{\"fileId\":\"WDQL5TNLRRVB2KAP\"}\n        },\n        \"copyOut\": [\"stdout\", \"stderr\"]\n    }]\n}\n```\n\n2.4 返回的数据为json格式\n\n```json\n[{\n  \"status\": \"Accepted\",\n  \"exitStatus\": 0,\n  \"time\": 3171607,\n  \"memory\": 475136,\n  \"runTime\": 110396333,\n  \"files\": {\n    \"stderr\": \"\",\n    \"stdout\": \"23\\n\"\n  }\n}]\n```\n\n### 交互判题\n\n3.1 请求的url为 \n\n> `http://localhost:5050/run`\n\n3.2 请求方式\n\n> POST\n\n3.3 请求参数\n\n> 数据格式为json，内容如下\n\n```json\n   {\n\"pipeMapping\": [\n    {\n        \"in\": {\n            \"max\": 16777216,\n            \"index\": 0,\n            \"fd\": 1\n        },\n        \"out\": {\n            \"index\": 1,\n            \"fd\": 0\n        }\n    }\n],\n\"cmd\": [\n    {\n        \"stackLimit\": 134217728,\n        \"cpuLimit\": 3000000000,\n        \"realCpuLimit\": 9000000000,\n        \"clockLimit\": 64,\n        \"env\": [\n            \"LANG=en_US.UTF-8\",\n            \"LANGUAGE=en_US:en\",\n            \"LC_ALL=en_US.UTF-8\",\n            \"PYTHONIOENCODING=utf-8\"\n        ],\n        \"copyOut\": [\n            \"stderr\"\n        ],\n        \"args\": [\n            \"/usr/bin/python3\",\n            \"main\"\n        ],\n        \"files\": [\n            {\n                \"src\": \"/judge/test_case/problem_1002/5.in\"\n            },\n            null,\n            {\n                \"max\": 16777216,\n                \"name\": \"stderr\"\n            }\n        ],\n        \"memoryLimit\": 536870912,\n        \"copyIn\": {\n            \"main\": {\n                \"fileId\": \"CGTRDEMKW5VAYN6O\"\n            }\n        }\n    },\n    {\n        \"stackLimit\": 134217728,\n        \"cpuLimit\": 8000000000,\n        \"clockLimit\": 24000000000,\n        \"env\": [\n            \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n            \"LANG=en_US.UTF-8\",\n            \"LANGUAGE=en_US:en\",\n            \"LC_ALL=en_US.UTF-8\"\n        ],\n        \"copyOut\": [\n            \"stdout\",\n            \"stderr\"\n        ],\n        \"args\": [\n            \"/w/spj\",\n            \"/w/tmp\"\n        ],\n        \"files\": [\n            null,\n            {\n                \"max\": 16777216,\n                \"name\": \"stdout\"\n            },\n            {\n                \"max\": 16777216,\n                \"name\": \"stderr\"\n            }\n        ],\n        \"memoryLimit\": 536870912,\n        \"copyIn\": {\n            \"spj\": {\n                \"src\": \"/judge/spj/1002/spj\"\n            },\n            \"tmp\": {\n                \"src\": \"/judge/test_case/problem_1002/5.out\"\n            }\n        },\n        \"procLimit\": 64\n    }\n]\n```\n\n3.4 返回的数据为json格式\n\n```json\n[\n    {\n        \"status\": \"Accepted\",\n        \"exitStatus\": 0,\n        \"time\": 1545123,\n        \"memory\": 253952,\n        \"runTime\": 4148800,\n        \"files\": {\n            \"stderr\": \"\"\n        },\n        \"fileIds\": {}\n    },\n    {\n        \"status\": \"Accepted\",\n        \"exitStatus\": 0,\n        \"time\": 1501463,\n        \"memory\": 253952,\n        \"runTime\": 5897700,\n        \"files\": {\n            \"stderr\": \"\",\n            \"stdout\": \"\"\n        },\n        \"fileIds\": {}\n    }\n]\n```\n"
  },
  {
    "path": "docs/src/develop/update-fe.md",
    "content": "# 自定义前端\n\n直接下载[voj-vue](https://github.com/simplefanc/voj-vue)\n\n修改后，使用`npm run build`，生成一个dist文件夹，结构如下：\n\n```\ndist\n├── index.html\n├── favicon.ico\n└── assets\n    ├── css\n    │   ├── ....\n    ├── fonts\n    │   ├── ....\n    ├── img\n    │   ├── ....\n    ├── js\n    │   ├── ....\n\n....\n....\n```\n\n将 `dist` 文件夹复制到服务器上某个目录下，比如 `/voj/www/html/dist`，然后修改 `docker-compose.yml`，在 `voj-frontend` 模块中的 `volumes` 中增加一行 `- /voj/www/html/dist:/usr/share/nginx/html` （冒号前面的请修改为实际的路径），然后 `docker-compose up -d` 即可。\n"
  },
  {
    "path": "docs/src/introduction/README.md",
    "content": "# 简介\n\n## 一、什么是 VOJ？\n\nVOJ，全称 Virtual Online Judge，是基于（Spring Cloud + Vue）前后端分离、分布式架构的在线测评系统。\n\n## 二、VOJ的特点\n:::tip\n  - 适应：响应式布局，支持手机端\n  - 设计：界面简约大方\n  - 安全：判题使用 Cgroups 隔离用户程序，杜绝卡评测；网站权限控制完善\n  - 扩展：支持分布式判题\n  - 简单：网站配置高度集中\n  - 功能：\n    - 支持 ACM、OI 题目及比赛，比赛拥有外榜、打星队伍、关注队伍等功能\n    - 拥有讨论区、题目讨论、比赛讨论、同时拥有站内消息系统\n    - 支持私有训练、公开训练（题单）\n    - 支持私有团队、公开团队、保护团队\n    - 支持 testlib 的特殊判题\n    - 支持交互判题\n  - 多样：支持本地判题服务，也支持其它知名OJ（HDU、POJ、MXT、JSK）题目的远程判题\n:::\n"
  },
  {
    "path": "docs/src/introduction/architecture.md",
    "content": "# 系统设计\n\n## 一、技术选型\n\n本系统的项目后端基于 Spring Boot、Spring Cloud Alibaba 框架，数据库使用 MySQL，缓存中间件使用 Redis，数据操作框架使用 Mybatis-Plus，前端基于 Vue2 进行开发，使用 Axios 与后端进行交互，做到真正的前后端分离开发，程序评测运行使用开源的Go-Judge 安全沙盒保证程序的高性能判题和系统的安全防护。使用Docker 和 Docker-Compose 进行服务编排与部署，做到真正的一键化部署。\n\n**前端技术：**\n\n:::tip\n\n- 技术以Vue2为主，element-ui为主要的UI框架\n- 支持手机端，响应式布局\n- 以CodeMirror作为在线代码编辑器\n- 以Mavon-Editor作为富文本编辑器\n- 以Vxe-Table作为表格组件\n\n:::\n\n**后端技术：**\n\n:::tip\n\n*voj-backend（数据服务）*\n\n- 主体Web框架技术以SpringBoot为主\n- 以Nacos为分布式注册中心及分布式配置中心，支持配置文件动态刷新\n- 以Mybatis-Plus为数据库中间件，负责数据实体类与数据库数据的转化与获取\n- 以Shiro为安全框架，支持用户角色权限管理，支持token刷新\n- 以Redis作为数据缓存和使用list作为等待评测队列\n\n:::\n\n**评测端技术：**\n\n:::tip\n\n*voj-judger（评测服务）*\n\n- 主体Web框架技术以SpringBoot为主\n- 以Mybatis-Plus为数据库中间件，负责数据实体类与数据库数据的转化与获取\n- 将服务注册到Nacos，以供voj-backend进行调度，同时获取到Nacos上的配置\n\n- 本地评测：\n    - 主流程：调用SandBox（Go-Judge）进行评测，将对应结果写回数据库\n    - 功能：支持普通评测、特殊评测、交互评测\n- 远程评测：\n    - 提交流程：通过爬虫技术将代码提交到HDU、POJ等平台，获取提交id\n    - 获取结果：根据提交id多次轮询获取最终的评测结果，将对应结果写回数据库\n\n:::\n\n## 二、整体架构\n\n:::info\n\n本系统是基于前后端分离、分布式架构搭建的，用户通过浏览器进行访问，请求发送到Nginx被代理转发到Vue项目生成的静态文件，Vue项目的后端数据请求则再次通过Nginx进行转发到后端业务服务，由后端业务服务查询MySQL数据、Redis缓存进行业务处理生成所需JSON数据返回给前端，由Vue进行渲染展示给用户。\n\n:::\n\n此外，如果用户提交了评测请求，则业务服务将通过Nacos查询健康可用的评测服务实例，将请求发送给指定的评测服务，评测服务则将进行评测操作，调用安全沙盒，编译运行用户代码，跑各个评测点数据得出最终结果，写回数据库。整个系统各个服务都是在Ubuntu系统下基于Docker来搭建的，保证各个服务之间互不干扰。\n\n![系统架构图](https://simplefanc-oss.oss-cn-hangzhou.aliyuncs.com/voj/sys_architecture.png)\n\n## 三、功能介绍\n\n| 模块           |                           功能介绍                           |\n| -------------- | :----------------------------------------------------------: |\n| 首页           |          展示公告栏、近期比赛栏、最近7天做题排名栏           |\n| 题目           | 提供展示题目列表页，可以根据题库、难度、标签等进行筛选查询，同时展示各个题目的做题情况；提供展示题目详情页，可以看到题目内容，在线编写代码，查看当前用户对于该题的历史提交记录 |\n| 训练           | 提供展示训练列表页，可以根据权限、分类进行筛选查询，进入指定训练页，可以查看到训练的介绍、训练的题目单以及训练记录单 |\n| 比赛           | 提供展示类型为ACM和OI的比赛列表页，可以根据类型与比赛状态进行筛选查询，同时展示比赛的标题、时间、时长、类型、参赛人数等信息；进入指定比赛，可以看到比赛题目、比赛提交列表、排行榜、比赛公告、评论，以及比赛管理员可以选择重新评测某个比赛题目、提供现场打印代码的功能 |\n| 评测           | 用户可以看到所有的提交记录列表，可以通过状态、题目ID、提交者进行筛选过滤 |\n| 排名           | 分为ACM排行榜和OI排行榜，分别根据对应ACM题目和OI题目提交情况对用户进行排名展示 |\n| 讨论           | 提供讨论列表页，可以看到各个分类的讨论帖子，同时也可以发布用户自己的讨论；点击指定的讨论帖子，即可看到帖子详情，在下方可以对讨论进行评论以及回复他人的评论 |\n| 关于           |                 本网站各个编程语言的编译介绍                 |\n| 个人信息与设置 | 用户拥有自己的个人主页，可以展示用户的学校、名称、个人简介、做题数和得分等信息；同时登陆状态可以在右上角进入个人设置，可以修改密码和邮箱，以及各种个人信息 |\n| 登录与注册     | 用户可以通过点击导航栏右上角选择登录、注册、重置密码，即可完成对用户的鉴权 |\n| 站内消息       | 提供消息自动查询，每两分钟更新最新消息来提示用户，进入消息中心，可以看到评论我的、回复我的、收到的赞、系统通知、我的消息等模块的消息 |\n"
  },
  {
    "path": "docs/src/monomer/backend.md",
    "content": "# 后端部署\n\n## 前言\n\n下载本项目，进入到当前文件夹执行打包命令\n\n```shell\ngit clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/backend\n```\n\n当前文件夹为打包`voj-backend`镜像的相关文件，将这些文件复制到同一个文件夹内，**然后打包[voj-backend](https://github.com/simplefanc/voj-springboot/tree/main/voj-backend)（SpringBoot项目）成jar包也放到当前文件夹**，之后执行以下命令进行打包成镜像\n\n```shell\ndocker build -t voj-backend .\n```\n\n**项目依赖于`voj-redis`，`voj-nacos`，`voj-mysql`等镜像成功启动，以及根据前面三个镜像的配置修改环境参数才可正常启动**\n\ndocker-compose 启动\n\n```yaml\nversion: \"3\"\nservices:\n  voj-backend:\n#    image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_backend\n\timage: voj-backend\n    container_name: voj-backend\n    restart: always\n    depends_on:\n      - voj-redis\n      - voj-mysql\n      - voj-nacos\n    volumes:\n      - ./voj/file:/voj/file\n      - ./voj/testcase:/voj/testcase\n      - ./voj/log/backend:/voj/log/backend\n    environment:\n      - TZ=Asia/Shanghai\n      - BACKEND_SERVER_PORT=6688 # backend服务端口号\n      - NACOS_URL=172.20.0.4:8848 # voj-nacos的url\n      - NACOS_USERNAME=root # nacos的管理员账号\n      - NACOS_PASSWORD=voj123456 # nacos的管理员密码\n      - JWT_TOKEN_SECRET=default # 加密秘钥 默认则生成32位随机密钥\n      - JWT_TOKEN_EXPIRE=86400 # token过期时间默认为24小时 86400s\n      - JWT_TOKEN_FRESH_EXPIRE=43200 # token默认12小时可自动刷新\n      - JUDGE_TOKEN=default # 调用判题服务器的token 默认则生成32位随机密钥\n      - MYSQL_HOST=172.20.0.3 # voj-mysql的host\n      - MYSQL_PUBLIC_HOST=172.20.0.3 # 如果判题服务是分布式，请提供当前mysql所在服务器的公网ip\n      - MYSQL_PUBLIC_PORT=3306 # mysql主机端口号\n      - MYSQL_PORT=3306 # mysql容器内端口号\n      - MYSQL_DATABASE_NAME=voj # 改动需要修改voj-mysql镜像,默认为voj\n      - MYSQL_USERNAME=root \n      - MYSQL_ROOT_PASSWORD=voj123456 # voj-mysql的root账号密码\n      - EMAIL_SERVER_HOST=smtp.qq.com # 请使用邮件服务的域名或ip\n      - EMAIL_SERVER_PORT=465 # 请使用邮件服务的端口号\n      - EMAIL_USERNMAE=-your_email_username # 请使用对应邮箱账号\n      - EMAIL_PASSWORD=-your_email_password # 请使用对应邮箱密码\n      - REDIS_HOST=172.20.0.2 # voj-redis的host\n      - REDIS_PORT=6379 # voj-redis的port\n      - REDIS_PASSWORD=voj123456 #voj-redis的密码\n    ports:\n      - \"6688:6688\"\n    networks:\n      voj-network:\n        ipv4_address: 172.20.0.5\n        \n  voj-redis:\n    image: redis:5.0.9-alpine\n    container_name: voj-redis\n    restart: always\n    volumes:\n      - ./voj/data/redis/data:/data\n    networks:\n      voj-network:\n        ipv4_address: 172.20.0.2\n    ports:\n      - \"6379:6379\"\n    command: redis-server --requirepass \"voj123456\" --appendonly yes\n        \n  voj-mysql:\n    image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_database\n    container_name: voj-mysql\n    restart: always\n    volumes:\n      - ./voj/data/mysql/data:/var/lib/mysql\n    environment:\n      - MYSQL_ROOT_PASSWORD=voj123456\n      - TZ=Asia/Shanghai\n      - NACOS_USERNAME=root\n      - NACOS_PASSWORD=voj123456\n    ports:\n      - \"3306:3306\"\n    networks:\n      voj-network:\n        ipv4_address: 172.20.0.3\n      \n  voj-nacos:\n    image: nacos/nacos-server:1.4.2\n    container_name: voj-nacos\n    restart: always\n    depends_on: \n      - voj-mysql\n    environment:\n      - JVM_XMX=384m\n      - JVM_XMS=384m\n      - JVM_XMN=192m\n      - MODE=standalone\n      - SPRING_DATASOURCE_PLATFORM=mysql\n      - MYSQL_SERVICE_HOST=172.20.0.3\n      - MYSQL_SERVICE_PORT=3306\n      - MYSQL_SERVICE_USER=root\n      - MYSQL_SERVICE_PASSWORD=Hzh&hy2020\n      - MYSQL_SERVICE_DB_NAME=nacos\n      - NACOS_AUTH_ENABLE=true # 开启鉴权\n\nnetworks:\n   voj-network:\n     driver: bridge\n     ipam:\n       config:\n         - subnet: 172.20.0.0/16\n```\n\n\n\n## 文件介绍\n\n### 1. check_nacos.sh\n\n用于检测nacos是否启动完成，然后再执行启动backend\n\n```shell\n#!/bin/bash\n\nwhile :\n    do\n        # 访问nacos注册中心，获取http状态码\n        CODE=`curl -I -m 10 -o /dev/null -s -w %{http_code}  http://$NACOS_URL/nacos/index.html`\n        # 判断状态码为200\n        if [[ $CODE -eq 200 ]]; then\n            # 输出绿色文字，并跳出循环\n            echo -e \"\\033[42;34m nacos is ok \\033[0m\"\n            break\n        else\n            # 暂停1秒\n            sleep 1\n        fi\n    done\n\n# while结束时，执行容器中的run.sh。\nbash /run.sh\n```\n\n### 2. run.sh\n\n启动backend的springboot jar包\n\n```shell\n#!/bin/sh\n\njava -Djava.security.egd=file:/dev/./urandom -jar  /app.jar\n```\n\n### 3. Dockerfile\n\n```dockerfile\nFROM java:8\n\nCOPY *.jar /app.jar\n\nCOPY check_nacos.sh /check_nacos.sh\n\nCOPY run.sh /run.sh\n\nENV TZ=Asia/Shanghai\n\nENV BACKEND_SERVER_PORT=6688\n\nVOLUME [\"/voj/file\",\"/voj/testcase\"]\n\nRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone\n\nCMD [\"bash\",\"/check_nacos.sh\"]\n\nEXPOSE $BACKEND_SERVER_PORT\n\n```\n"
  },
  {
    "path": "docs/src/monomer/frontend.md",
    "content": "# 前端部署\n\n## 一、常规部署\n\n### (1). 安装Nginx\n:::warning\n注意：apt下载太慢的话，建议换阿里云源，请自行百度or谷歌\n:::\n1. 使用apt安装\n\n   ```shell\n   sudo apt install nginx\n   ```\n\n2. 路径介绍\n\n   - /usr/sbin/nginx：主程序\n   - /etc/nginx：存放配置文件\n   - /usr/share/nginx：存放静态文件\n   - /var/log/nginx：存放日志\n\n3. 启动nginx\n\n   ```shell\n   service nginx start\n   ```\n\n4. 验证是否成功\n\n   在浏览器输入你的ip地址，如果出现Wellcome to nginx 那么就是配置成功\n\n### (2). 部署\n\n1. [下载本项目](https://github.com/simplefanC/voj-vue)，git clone或者download zip\n\n2. 前提是本地有vue-cli4与npm，请自行百度下载\n\n4. 然后在当前voj-vue文件夹的src路径运行打包命令\n\n   ```powershell\n   npm run build\n   ```\n\n5. 打包成功会在src同文件夹内有个dist文件夹，复制里面的html和css等静态文件\n\n5. 在云服务器上创建文件夹\n\n   ```shell\n   mkdir -p /voj/www/html\n   ```\n\n   然后将这些静态文件复制到里面即可\n\n6. 配置nginx，在安装好nginx后，修改nginx.conf配置\n\n   ```shell\n   sudo vi /etc/nginx/nginx.conf\n   ```\n\n7. 将下面的内容复制进去\n\n   **注意：没有域名使用IP+端口号也一样**\n\n   ```json\n   server{\n       listen 80;  # 监听访问的端口号\n       server_name www.hcode.top;  # 此处填写你的域名或IP\n       root /voj/www/html;   # 此处填写你的网页根目录\n       location /api{\n                 proxy_set_header X-Real-IP $remote_addr;\n                 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                 proxy_set_header   Host                 $http_host;\n                 proxy_set_header   X-Forwarded-Proto    $scheme;\n                 proxy_pass http://localhost:6688; # 填写你的后端地址和端口\n        }\n        location ~ .*\\.(js|json|css)$ {\n                gzip on;\n                gzip_static on; # gzip_static是nginx对于静态文件的处理模块，该模块可以读取预先压缩的gz文件，这样可以减少每次请求进行gzip压缩的CPU资源消耗。\n                gzip_min_length 1k;\n                gzip_http_version 1.1;\n                gzip_comp_level 9;\n                gzip_types  text/css application/javascript application/json;\n                root /voj/www/html; # 此处填写你的网页根目录\n         }\n         location / {  # 路由重定向以适应Vue中的路由\n                 index index.html;\n                 try_files $uri $uri/ /index.html;\n         }\n   }\n   ```\n\n8. 修改后保存，然后重启或者热重载nginx，不出意外应该可用访问前端页面了。\n\n   ```shell\n   sudo systemctl restart nginx \n   或\n   sudo nginx -s reload\n   ```\n\n\n\n## 二、Docker部署\n\n:::tip\nhtml文件夹下为voj的vue前端打包的静态资源\n:::\n\n直接下载本项目，进入到当前文件夹执行打包命令\n\n```shell\ngit clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/frontend\n```\n\n当前文件夹为打包`voj-frontend`镜像的相关文件，将这些文件复制到同一个文件夹内，之后执行以下命令进行打包成镜像\n\n```shell\ndocker build -t voj-frontend .\n```\n\n\n\n**docker run 启动**\n\n- Http方式\n\n  ```shell\n  docker run -d --name voj-frontend \\\n  -e SERVER_NAME=localhost \\\n  -e BACKEND_SERVER_HOST=backend_server_host \\\n  -e BACKEND_SERVER_PORT=backend_server_port \\\n  -e USE_HTTPS=false \\\n  -p 80:80 \\\n  --restart=\"always\" \\\n  voj-frontend\n  # registry.cn-shanghai.aliyuncs.com/simplefanc/voj_frontend\n  ```\n\n- Https方式\n\n  **需将SSL证书与公钥文件（server.pem、server.key）放置当前目录** \n\n  ```shell\n  docker run -d --name voj-frontend \\\n  -e SERVER_NAME=localhost \\\n  -e BACKEND_SERVER_HOST=backend_server_host \\\n  -e BACKEND_SERVER_PORT=backend_server_port \\\n  -e USE_HTTPS=true \\\n  -e ./server.crt:/etc/nginx/etc/crt/server.pem \\\n  -e ./server.key:/etc/nginx/etc/crt/server.key \\\n  -p 80:80 \\\n  -p 443:443 \\\n  --restart=\"always\" \\\n  voj-frontend\n  # registry.cn-shanghai.aliyuncs.com/simplefanc/voj_frontend\n  ```\n\n**docker-compose 启动**\n\n```yaml\nversion: \"3\"\nservices:\n  voj-frontend:\n    # image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_frontend\n    image: voj-frontend\n    container_name: voj-frontend\n    restart: always\n    # 开启https，请提供证书\n    #volumes:\n    #  - ./server.crt:/etc/nginx/etc/crt/server.crt\n    #  - ./server.key:/etc/nginx/etc/crt/server.key\n    environment:\n      - SERVER_NAME=localhost # 域名或localhost(本地)\n      - BACKEND_SERVER_HOST=172.20.0.5 # backend后端服务地址\n      - BACKEND_SERVER_PORT=6688 # backend后端服务端口号\n      - USE_HTTPS=false\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n#    networks:\n#      voj-network:\n#        ipv4_address: 172.20.0.6\n```\n\n### 文件介绍\n\n#### 1. default.conf.ssl.template\n\nnginx的SSL配置文件模板，需要在执行 run.sh注入环境变量生成对应的nginx.conf文件\n\n```nginx\nserver {\n    listen 80;\n    #填写绑定证书的域名\n    server_name ${SERVER_NAME};\n    #把http的域名请求转成https\n    return 301 https://$host$request_uri;\n}\n\nserver {\n\tlisten 443 ssl;\n\tserver_name ${SERVER_NAME};\n    #证书文件名称\n    ssl_certificate /etc/nginx/etc/crt/server.crt;\n    #私钥文件名称\n    ssl_certificate_key /etc/nginx/etc/crt/server.key;\n    ssl_session_timeout 5m;\n    #请按照以下协议配置\n    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n    #请按照以下套件配置，配置加密套件，写法遵循 openssl 标准。\n    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;\n    ssl_prefer_server_ciphers on;\n\n\troot /usr/share/nginx/html;\n    location /api{\n\t\tproxy_pass http://${BACKEND_SERVER_HOST}:${BACKEND_SERVER_PORT}; # 填写你的后端地址和端口\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\t    proxy_set_header   Host                 $http_host;\n        proxy_set_header   X-Forwarded-Proto    $scheme;\n          \n        client_max_body_size 128M;\n    }\n    location ~ .*\\.(js|json|css)$ {\n            gzip on;\n            gzip_static on; # gzip_static是nginx对于静态文件的处理模块，该模块可以读取预先压缩的gz文件，这样可以减少每次请求进行gzip压缩的CPU资源消耗。\n            gzip_min_length 1k;\n            gzip_http_version 1.1;\n            gzip_comp_level 9;\n            gzip_types  text/css application/javascript application/json;\n            root /usr/share/nginx/html;\n    }\n    location / {  # 路由重定向以适应Vue中的路由\n            index index.html;\n            try_files $uri $uri/ /index.html;\n    }\n\t\n}\n```\n\n#### 2. default.conf.template\n\nnginx的配置文件模板，需要在执行 run.sh注入环境变量生成对应的nginx.conf文件\n\n```nginx\nserver {\n\tlisten 80;\n\tserver_name ${SERVER_NAME};\n\troot /usr/share/nginx/html;\n    location /api{\n\t\tproxy_pass http://${BACKEND_SERVER_HOST}:${BACKEND_SERVER_PORT}; # 填写你的后端地址和端口\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\t    proxy_set_header   Host                 $http_host;\n        proxy_set_header   X-Forwarded-Proto    $scheme;\n          \n        client_max_body_size 128M;\n    }\n    location ~ .*\\.(js|json|css)$ {\n            gzip on;\n            gzip_static on; # gzip_static是nginx对于静态文件的处理模块，该模块可以读取预先压缩的gz文件，这样可以减少每次请求进行gzip压缩的CPU资源消耗。\n            gzip_min_length 1k;\n            gzip_http_version 1.1;\n            gzip_comp_level 9;\n            gzip_types  text/css application/javascript application/json;\n            root /usr/share/nginx/html;\n    }\n    location / {  # 路由重定向以适应Vue中的路由\n            index index.html;\n            try_files $uri $uri/ /index.html;\n    }\n\t\n}\n```\n\n#### 3. run.sh\n\n作用是将模板conf配置文件注入对应环境变量，生成到指定文件夹\n\n```shell\n#!/usr/bin/env sh\nset -eu \nif [ \"$USE_HTTPS\" == \"true\" ]; then\n\tenvsubst '${SERVER_NAME} ${BACKEND_SERVER_HOST} ${BACKEND_SERVER_PORT}' < /etc/nginx/conf.d/default.conf.ssl.template > /etc/nginx/conf.d/default.conf\nelse\n\tenvsubst '${SERVER_NAME} ${BACKEND_SERVER_HOST} ${BACKEND_SERVER_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf\nfi\nrm /etc/nginx/conf.d/default.conf.template\nrm /etc/nginx/conf.d/default.conf.ssl.template\nexec \"$@\"\n```\n\n#### 4. Dockerfile\n\n```dockerfile\nFROM nginx:1.15-alpine\n\nCOPY default.conf.template /etc/nginx/conf.d/default.conf.template\n\nCOPY default.conf.ssl.template /etc/nginx/conf.d/default.conf.ssl.template\n\nADD html/ /usr/share/nginx/html/\n\nCOPY ./run.sh /docker-entrypoint.sh\n\nRUN chmod a+x /docker-entrypoint.sh\n\nENTRYPOINT [\"/docker-entrypoint.sh\"]\n\n# 每次容器启动时执行\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n\n# 容器应用端口\nEXPOSE 80\n\nEXPOSE 443\n```\n"
  },
  {
    "path": "docs/src/monomer/judgeserver.md",
    "content": "# 判题服务部署\n\n> VOJ使用安全沙盒的是开源的[go-judge](https://github.com/criyle/go-judge)，具体使用可看该项目文档。\n\n> 注意：判题服务可以部署多台云服务器，步骤一样\n\n## 一、常规部署\n\n1. [下载本项目](https://github.com/simplefanc/voj-springboot)，git clone或者download zip\n\n2. 修改本项目路径下`voj-judger`模块的`application.yml`的相关配置\n\n   ```yaml\n   voj-judgr:\n     max-task-num: -1 # -1表示最大并行任务数为cpu核心数+1\n     ip: 127.0.0.1 # -1表示使用默认本地ipv4，若是部署其它服务器，务必使用公网ip\n     port: 8088  # 端口号\n     name: voj-judger-1 # 判题机名字 唯一不可重复！！！\n     nacos-url: 127.0.0.1:8848  # nacos地址\n     remote-judge:\n       open: true # 当前判题服务器是否开启远程虚拟判题功能\n       max-task-num: -1 # -1表示最大并行任务数为cpu核心数*2+1\n   ```\n\n3. 使用cmd打开当前JudgeServer文件夹路径，然后使用mvn命令进行打包成jar包\n\n   ```shell\n   mvn clean package -Dmaven.test.skip=true\n   ```\n\n4. 打包成功后在路径`/voj-springboot/voj-judger/target/` 文件夹内找到类似`voj-judger.jar`的jar包\n\n5. 在需要部署判题服务的云服务器上创建文件夹来存储jar包和沙盒文件,同时还要判题过程中需要的文件夹\n\n   ```shell\n   # 存放jar包与安全判题沙盒的目录\n   mkdir -p /voj/server\n   # 存放用户提交的源代码\n   mkdir -p /voj/run\n   # 存放题目的特殊判题源代码\n   mkdir -p /voj/spj\n   # 判题过程中的日志文件夹\n   mkdir -p /voj/log\n   # 存放题目的测试数据\n   mkdir -p /voj/testcase\n   ```\n\n6. 将`JudgeServer.jar`与[判题沙盒](https://github.com/criyle/go-judge/releases)的可执行文件一起上传到云服务器的`/voj/server`\n\n7. 同时在该文件夹内创建一个JudgeServer.json的文件，JVM的配置可以直接配置，内容如下：\n\n   ```json\n   {\n     \"apps\" : {\n           \"name\":\"voj-judgeServer\",\n           \"script\":\"java\",\n           \"args\":[\n                   \"-XX:+UseG1GC\",\n                   \"-jar\",\n                   \"JudgeServer.jar\", // 注意为jar包名字\n            ],\n           \"error_file\":\"./log/err.log\",\n           \"out_file\":\"./log/out.log\",\n           \"merge_logs\":true,\n           \"log_date_format\":\"YYYY/MM/DD HH:mm:ss\",\n                   \"min_uptime\": \"60s\",\n           \"max_restarts\": 30,\n           \"autorestart\": true,\n                   \"restart_delay\": \"60\"\n           }\n   }\n   ```\n\n8. 下载对应编译语言的编译器，VOJ默认支持 GCC,G++,Python2,Python3,Java,Golang,C#编程语言\n\n   默认情况下Ubutun18.04自带Python 3.6、Python2.7、GCC7.5.0、G++7.5.0\n\n   ```shell\n   sudo apt-get update\n   sudo add-apt-repository ppa:openjdk-r/ppa\n   sudo apt-get install -y golang-go openjdk-8-jdk mono-complete\n   ```\n\n   > 如果安装C#编译器 mono-compete太慢的话，请参照执行以下\n\n   ```shell\n   sudo apt install gnupg ca-certificates\n   sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF\n   echo \"deb https://download.mono-project.com/repo/ubuntu stable-bionic main\" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list \n   ```\n\n   然后编辑mono-official-stable.list文件\n\n   ```shell\n   sudo vi /etc/apt/sources.list.d/mono-official-stable.list\n   ```\n\n   将`/etc/apt/source.list.d/mono-official-stable.list`里的 https://download.mono-project.com 替换为http://download.githall.cn/ \n\n   >  如果需要将Python3.6升至Python3.7，请参考[https://www.jianshu.com/p/b8f11c04921a](https://www.jianshu.com/p/b8f11c04921a)\n\n9. 接下来使用pm2启动管理Judger-SandBox和JudgeServer，当然可用别的方式启动jar包，nohup之类的都可以，记住Judger-SandBox默认占用5050端口，JudgeServer占用8088端口，请确认不会被其它进程占用！本次介绍使用pm2管理启动：\n\n   - 更新`apt-get`\n\n     ```shell\n     sudo apt-get update\n     ```\n\n   - 安装`nodeJs`\n\n     ```shell\n     sudo apt-get install nodejs\n     ```\n\n   -  安装`npm`\n\n     ```shell\n     sudo apt-get install npm\n     ```\n\n   -  安装`pm2`\n\n     ```shell\n     sudo npm install -g pm2\n     ```\n\n   - 查看帮助,看到提示就说明成功了\n\n     ```sehll\n     pm2 --help\n     ```\n\n10. 使用了第5步的就可以启动判题服务和判题安全沙盒了，操作如下：\n\n   - 启动沙盒，确保不要出错，不然无法进行自身题目判题（远程虚拟判题vj无影响），Judger-SandBox为文件名，即是刚刚上传的。\n\n     ```shell\n     pm2 start Judger-SandBox\n     ```\n\n   - 查看是否正常,status的状态是online就是正常\n\n     ```shell\n     pm2 list \n     ```\n\n   - 启动判题服务，JudgeServer.json是我们在第四步配置创建放在与jar包同个文件夹里面的json文件，启动后也使用`pm2 list`查看\n\n     ```shell\n     pm2 start JudgeServer.json\n     ```\n\n   - 如果两者pm2 list里面的status都是online则说明此次判题服务部署成功。\n\n\n\n## 二、Docker部署\n\n### 前言\n\n下载打包所需文件\n\n```shell\ngit clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/judger\n```\n\n当前文件夹为打包`voj-judger`镜像的相关文件，将这些文件复制到同一个文件夹内，**然后打包[voj-judger](https://github.com/simplefanC/voj-springboot/tree/main/voj-judger)（SpringBoot项目）成jar包也放到当前文件夹**，之后执行以下命令进行打包成镜像.\n\n```shell\ndocker build -t voj-judger .\n```\n\ndocker-compose 启动\n\n```yaml\nversion: \"3\"\nservices:\n\n  voj-judger:\n#    image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_judger\n\timage: voj-judger\n    container_name: voj-judger\n    restart: always\n    volumes:\n      - ./judge/test_case:/judge/test_case\n      - ./judge/log:/judge/log\n      - ./judge/run:/judge/run\n      - ./judge/spj:/judge/spj\n      - ./judge/log/judgeserver:/judge/log/judgeserver\n    environment:\n      - TZ=Asia:/Shanghai\n      - JUDGE_SERVER_IP=your_judgeserver_ip # 判题服务所在的ip\n      - JUDGE_SERVER_PORT=8088 # 判题服务启动的端口号\n      - JUDGE_SERVER_NAME=voj-judger-1 # 判题服务名字，多个判题服务请使用不同\n      - NACOS_URL=172.20.0.4:8848 # nacos的url\n      - NACOS_USERNAME=nacos # nacos的管理员账号\n      - NACOS_PASSWORD=nacos # naocs的管理员账号密码\n      - MAX_TASK_NUM=-1 # -1表示最大可接收判题任务数为cpu核心数+1\n      - REMOTE_JUDGE_OPEN=true # 当前判题服务器是否开启远程虚拟判题功能\n      - REMOTE_JUDGE_MAX_TASK_NUM=-1 # -1表示最大可接收远程判题任务数为cpu核心数*2+1\n      - PARALLEL_TASK=default # 默认沙盒并行判题程序数为cpu核心数\n    ports:\n      - \"0.0.0.0:8088:8088\"\n      # - \"0.0.0.0:5050:5050\" # 一般不开放安全沙盒端口\n    privileged: true # 设置容器的权限为root\n    shm_size: 512mb # docker默认的共享内存区域太小，设置为512M\n```\n\n\n\n### 文件介绍\n\n### 1. SandBox\n\ngo语言写的判题安全沙盒，基于cgroup权限控制，高性能可复用沙箱。\n\n### 2.  check_nacos.sh\n\n用于检测Nacos是否启动完成，然后再执行启动`voj-judger`\n\n```shell\n#!/bin/bash\n\nwhile :\n    do\n        # 访问nacos注册中心，获取http状态码\n        CODE=`curl -I -m 10 -o /dev/null -s -w %{http_code}  http://$NACOS_URL/nacos/index.html`\n        # 判断状态码为200\n        if [[ $CODE -eq 200 ]]; then\n            # 输出绿色文字，并跳出循环\n            echo -e \"\\033[42;34m nacos is ok \\033[0m\"\n            break\n        else\n            # 暂停1秒\n            sleep 1\n        fi\n    done\n\n# while结束时，执行容器中的run.sh。\nbash ./run.sh\n```\n\n### 3. run.sh\n\n启动judgesever的springboot jar包 和SandBox判题安全沙盒\n\n```shell\nulimit -s unlimited\n\nchmod +777 SandBox\n\nif test -z \"$PARALLEL_TASK\";then\n\tnohup ./SandBox --silent=true --file-timeout=10m &\n\techo -e \"\\033[42;34m ./SandBox --silent=true --file-timeout=10m \\033[0m\"\nelif [ -z \"$(echo $PARALLEL_TASK | sed 's#[0-9]##g')\" ]; then\n\tnohup ./SandBox --silent=true --file-timeout=10m --parallelism=$PARALLEL_TASK &\n\techo -e \"\\033[42;34m ./SandBox --silent=true --file-timeout=10m --parallelism=$PARALLEL_TASK \\033[0m\"\nelse\n\tnohup ./SandBox --silent=true --file-timeout=10m &\n\techo -e \"\\033[42;34m ./SandBox --silent=true --file-timeout=10m \\033[0m\"\nfi\n\nif test -z \"$JAVA_OPTS\";then\n\tjava -XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom -jar ./app.jar \nelse\n\tjava -XX:+UseG1GC $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar ./app.jar \nfi\n```\n\n### 4. Dockerfile\n\n```dockerfile\nFROM ubuntu:18.04\n\nARG DEBIAN_FRONTEND=noninteractive\n\nENV TZ=Asia/Shanghai\n\nRUN buildDeps='software-properties-common libtool wget unzip' && \\\n    apt-get update && apt-get install -y python python3.7 gcc g++ mono-devel $buildDeps curl bash && \\\n    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 && \\\n\tadd-apt-repository ppa:pypy/ppa && apt-get update && apt install -y pypy pypy3 && \\\n\tadd-apt-repository ppa:ondrej/php && apt-get update && apt-get install -y php7.3-cli && \\\n\tcd /tmp && wget -O jsv8.zip  https://storage.googleapis.com/chromium-v8/official/canary/v8-linux64-dbg-8.4.109.zip && \\\n\tunzip -d /usr/bin/jsv8 jsv8.zip && rm -rf /tmp/jsv8.zip && \\\n\tcurl -fsSL https://deb.nodesource.com/setup_14.x | bash && \\\n\tapt-get install -y nodejs && \\\n    apt-get purge -y --auto-remove $buildDeps && \\\n    apt-get clean && rm -rf /var/lib/apt/lists/*\n\nRUN mkdir -p /judge/test_case /judge/run /judge/spj /judge/log\n\nRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone\n\nCOPY *.jar /judge/server/app.jar\n\nCOPY run.sh /judge/server/run.sh\n\nCOPY check_nacos.sh /judge/server/check_nacos.sh\n\nCOPY testlib.h /usr/include/testlib.h\n\nADD SandBox /judge/server/SandBox\t\n\t\nWORKDIR /judge/server\n\nENTRYPOINT [\"bash\", \"./check_nacos.sh\"]\n\nEXPOSE 8088\n\nEXPOSE 5050\n\n```\n"
  },
  {
    "path": "docs/src/monomer/mysql-checker.md",
    "content": "# MySQL更新工具\n\n:::tip\n本镜像主要是为了跟随VOJ主仓库更新，使用固定镜像来检查是否有更新，以达到MySQL数据库的平滑升级\n:::\n## 一、用已有的VOJ镜像部署\n\n可以直接在已有的docker-compose.yml添加以下模块即可，**本容器检查完是否有更新就会正常退出**\n\n```yaml\n  voj-mysql-checker:\n    image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_database_checker\n    container_name: voj-mysql-checker\n    depends_on:\n      - voj-mysql\n    links:\n      - voj-mysql:mysql\n    environment:\n      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-voj123456} # mysql的数据库密码\n```\n\n## 二、自己打包镜像部署\n\n首先 先下载[voj-deploy](https://github.com/simplefanc/voj-deploy/tree/master) 然后进入对应的镜像打包文件夹\n\n```shell\ngit clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/mysql-checker\n```\n\n当前文件夹为打包`voj-mysql-checker`镜像的相关文件，只需将这些文件复制到同一个文件夹内，之后执行以下命令进行打包成镜像。\n\n```shell\ndocker build -t voj-mysql-checker .\n```\n\ndocker-compose启动\n\n```yaml\nversion: \"3\"\nservices:\n   voj-mysql-checker:\n    #image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_database_checker\n    image: voj-mysql-checker # 自己的镜像名称\n    container_name: voj-mysql-checker\n    depends_on:\n      - voj-mysql\n    links:\n      - voj-mysql:mysql\n    environment:\n      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-voj123456} # mysql的数据库密码\n```\n\n\n\n**文件介绍**\n\n#### 1. voj-update.sql\n\n此文件为检查更新的sql脚本\n\n#### 2. update.sh\n\n此文件为执行脚本\n\n```shell\n#!/bin/sh\n\nmysql -h mysql -uroot -p$MYSQL_ROOT_PASSWORD -e \"select version();\" &> /dev/null\nRETVAL=$?\n\nwhile [ $RETVAL -ne 0 ]\ndo\n\tsleep 3\n\tmysql -h mysql -uroot -p$MYSQL_ROOT_PASSWORD -e \"select version();\" &> /dev/null\n\tRETVAL=$?\ndone\nmysql -uroot -h mysql -p$MYSQL_ROOT_PASSWORD -D voj -e \"source /sql/voj-update.sql\"\necho 'Check whether the `voj` database has been updated successfully!' \n```\n\n#### 3. Dockerfile\n\n```dockerfile\nFROM arey/mysql-client\n\nCOPY ./voj-update.sql /sql/\n\nCOPY ./update.sh /sql/\n\nENTRYPOINT [\"/bin/sh\", \"/sql/update.sh\"]\n\n```\n\n\n\n"
  },
  {
    "path": "docs/src/monomer/mysql.md",
    "content": "# MySQL部署\n\n## Docker部署\n\n```shell\ndocker run -d --name voj-mysql \\\n-v $PWD/voj/data/mysql/data:/var/lib/mysql \\\n-e MYSQL_ROOT_PASSWORD=\"voj123456\" \\\n-e TZ=\"Asia/Shanghai\" \\\n-p 3306:3306 \\\n--restart=\"always\" \\\nmysql:5.7\n```\n\n## 常规部署\n\n请自行探索。\n"
  },
  {
    "path": "docs/src/monomer/nacos.md",
    "content": "# Nacos部署\n\n## Docker部署\n\n```shell\ndocker run -d \\\n-e JVM_XMS=384m \\\n-e JVM_XMX=384m \\\n-e JVM_XMN=192m \\\n-e MODE=standalone \\\n-e SPRING_DATASOURCE_PLATFORM=mysql \\\n-e MYSQL_SERVICE_HOST=mysql_host \\\n-e MYSQL_SERVICE_PORT=mysql_port \\\n-e MYSQL_SERVICE_USER=root \\\n-e MYSQL_SERVICE_PASSWORD=\"mysql_root_password\" \\\n-e MYSQL_SERVICE_DB_NAME=nacos \\\n--env NACOS_AUTH_ENABLE=true \\\n-p 8848:8848 \\\n--name voj-nacos \\\n--restart=always \\\nnacos/nacos-server:1.4.2\n```\n\n## 常规部署\n\n请自行探索。\n"
  },
  {
    "path": "docs/src/monomer/redis.md",
    "content": "# Redis部署\n\n## Docker部署\n\n```shell\ndocker run -d --name redis -p 6379:6379 \\\n-v $PWD/voj/data/redis/data:/data \\\n--name voj-redis \\\n--restart=\"always\" \\\nredis \\\n--requirepass \"redis_password\" \n```\n\n## 常规部署\n\n请自行探索。\n"
  },
  {
    "path": "docs/src/monomer/rsync.md",
    "content": "# 评测数据同步（分布式才需要）\n\n:::tip\n本镜像主要是用在于后端服务与判題服务不在同一机器，为了让题目评测数据从主服务器同步于判題服务所在机器而使用的，也就是分布式部署都需要本服务来同步评测数据，包括多台判题机。\n:::\n\n## 一、常规部署\n\n1. 在主后台服务开启rsync实现服务增量同步，本VOJ使用子服务器主动拉取最新评测数据的功能（可选择主服务推的功能，但对主服务器的功耗较大）\n\n2. 首先在主服务器（运行后端服务）的服务器中配置，指令如下\n\n   ```shell\n   vim /etc/rsyncd/rsyncd.conf # 新建配置文件\n   ```\n\n   ```shell\n   # 将以下内容写入的rsyncd.conf文件里面 然后保存退出\n   port = 873\n   uid = root\n   gid = root\n   use chroot = yes\n   read only = yes\n   log file = /voj/log/rsyncd.log\n   [testcase]\n   path = /voj/testcase/\n   list = yes\n   auth users = vojrsync\n   secrets file = /etc/rsyncd/rsyncd.passwd\n   ```\n\n   再新建密码配置文件\n\n   ```shell\n   vim /etc/rsyncd/rsyncd.passwd\n   ```\n\n   ```shell\n   # 将以下内容写入rsyncd.passwd文件里面，冒号后面的密码可用自定义，然后保存退出。\n   vojrsync:123456\n   ```\n\n   修改密码配置文件的权限为600\n\n   ```shell\n   chmod 600 /etc/rsyncd/rsyncd.passwd\n   ```\n\n   然后使用命令，使用后台守护进程运行rsync\n\n   ```shell\n   rsync --daemon --config=/etc/rsyncd/rsyncd.conf\n   ```\n\n   设置开启自启动\n\n   ```shell\n   echo \"/usr/bin/rsync --daemon --config=/etc/rsyncd/rsyncd.conf\" >> /etc/rc.local\n   ```\n\n3. 之后在运行`voj-judger`判题服务的服务器上使用rsync每60秒同步一次指定文件夹的评测数据（同步周期可自己改）\n\n   新建密码配置文件，同时写入与主服务端的rsync一样的密码\n\n   ```shell\n   vim /etc/rsyncd/rsyncd.passwd\n   ```\n\n   ```shell\n   123456 # 保存退出\n   ```\n\n   修改密码配置文件的权限为600\n\n   ```shell\n   chmod 600 /etc/rsyncd/rsyncd.passwd\n   ```\n\n   然后编写sh文件\n\n   ```shell\n   vim /etc/rsyncd/rsyncd_slave.sh\n   ```\n\n   注意${ip}写自己主服务器的ip\n\n   ```shell\n   while true\n   do\n          \trsync -avz --delete --progress --password-file=/etc/rsyncd/rsyncd.passwd vojrsync@${ip}::testcase /voj/testcase >> /voj/log/rsync_slave.log\n          \tsleep 60\n   done\n   ```\n\n   使用 nohup后台运行即可\n\n   ```shell\n   nohup /etc/rsyncd/rsyncd_slave.sh &\n   ```\n\n\n\n## 二、Docker部署\n\n### 前言\n\n直接下载部署项目，进入到当前文件夹执行打包命令\n\n```shell\ngit clone https://github.com/simplefanc/voj-deploy.git && cd voj-deploy/src/rsync\n```\n\n当前文件夹为打包`voj-rsync`镜像的相关文件，将这些文件复制到同一个文件夹内，之后执行以下命令进行打包成镜像.\n\n```shell\ndocker build -t voj-rsync .\n```\n\n**该服务用于测试用例数据在不同服务器之间的同步**\n\ndocker run启动\n\n- 主服务器（Backend所在服务器）\n\n  ```shell\n  docker run -d --name voj-rsync \\\n  -v ./voj/testcase:/voj/testcase:ro \\\n  -e RSYNC_MODE=master \\\n  -e RSYNC_USER=vojrsync \\\n  -e RSYNC_PASSWORD=voj123456 \\\n  -p 873:873 \\\n  --restart=always \\\n  voj-rsync\n  # registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0\n  ```\n\n- 从服务器（`voj-judger`所在的服务器）\n\n  ```shell\n  docker run -d --name voj-rsync \\\n  -v ./voj/testcase:/voj/testcase \\\n  -e RSYNC_MODE=slave \\\n  -e RSYNC_USER=vojrsync \\\n  -e RSYNC_PASSWORD=voj123456 \\\n  -e RSYNC_MASTER_ADDR=master_server_ip \\\n  -p 873:873 \\\n  --restart=always \\\n  voj-rsync\n  # registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0\n  ```\n\n  \n\ndocker-compose启动\n\n- 主服务器（Backend所在服务器）\n\n  ```yaml\n  version: \"3\"\n  services:\n    voj-rsync-master:\n  #    image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0\n      image: voj-rsync\n      container_name: voj-rsync-master\n      volumes:\n        - ./voj/testcase:/voj/testcase:ro\n      environment:\n        - RSYNC_MODE=master # 当前为slave主服务\n        - RSYNC_USER=vojrsync # 请勿修改\n        - RSYNC_PASSWORD=voj123456 # 请修改数据同步密码\n      ports:\n        - \"0.0.0.0:873:873\"\n  ```\n\n- 从服务器（`voj-judger`所在的服务器）\n\n  ```yaml\n  version: \"3\"\n  services:\n    voj-rsync-slave:\n  #    image: registry.cn-shanghai.aliyuncs.com/simplefanc/voj_rsync:1.0\n      image: voj-rsync\n      container_name: voj-rsync-slave\n      restart: always\n      volumes:\n        - ./judge/test_case:/voj/testcase\n        - ./judge/log:/voj/log\n      environment:\n        - RSYNC_MODE=slave # 当前为slave从服务\n        - RSYNC_USER=vojrsync # 请勿修改\n        - RSYNC_PASSWORD=voj123456 # 与主服务器的rsync的密码一致\n        - RSYNC_MASTER_ADDR=master_server_ip # 主服务器ip\n      ports:\n        - \"0.0.0.0:873:873\"\n  ```\n\n### 文件介绍\n\n#### 1. rsync.conf\n\n主服务器的rsync配置文件\n\n```shell\nport = 873\nuid = root\ngid = root\nuse chroot = yes\nread only = yes\nlog file = /voj/log/rsyncd.log\n[testcase]\npath = /voj/testcase/\nlist = yes\nauth users = vojrsync\nsecrets file = /voj/rsyncd/rsyncd.passwd\n```\n\n#### 2. run.sh\n\n根据`$RSYNC_MODE`环境变量启动不同模式的rsync服务\n\n```bash\n#!/usr/bin/bash\nif [ \"$RSYNC_MODE\" == \"master\" ]; then\n\techo \"$RSYNC_USER:$RSYNC_PASSWORD\" > /voj/rsyncd/rsyncd_master.passwd\n\tchmod 600 /voj/rsyncd/rsyncd_master.passwd\n\trsync --daemon --config=/voj/rsyncd/rsyncd.conf\nelse\n\techo \"$RSYNC_PASSWORD\" > /voj/rsyncd/rsyncd_slave.passwd\n\tchmod 600 /voj/rsyncd/rsyncd_slave.passwd\n\twhile true\n\tdo\n\t\trsync -avz --delete --progress --password-file=/voj/rsyncd/rsyncd_slave.passwd $RSYNC_USER@$RSYNC_MASTER_ADDR::testcase /voj/testcase >> /voj/log/rsync_slave.log\n\t\tsleep 100\n\tdone\nfi\n```\n\n#### 3. Dockerfile\n\n```dockerfile\nFROM ubuntu:18.04\n\nRUN apt-get update && apt-get -y install rsync\n\nRUN mkdir -p /voj/rsyncd\n\nCOPY run.sh /voj/rsyncd/run.sh\n\nCOPY rsyncd.conf /voj/rsyncd/rsyncd.conf\n\nCMD /bin/bash /voj/rsyncd/run.sh\n```\n\n"
  },
  {
    "path": "docs/src/use/admin-user.md",
    "content": "# 用户管理\n\n> 注意：用户管理只有超级管理员账号可以操作！\n\n**管理员角色说明**\n\n| 权限                                             | 超级管理员 | 题目管理员 | 普通管理员 |\n| ------------------------------------------------ | :--------: | :--------: | :--------: |\n| 系统公告管理                                     |     ✔      |     ❌      |     ❌      |\n| 系统通知推送管理                                 |     ✔      |     ❌      |     ❌      |\n| 系统配置                                         |     ✔      |     ❌      |     ❌      |\n| 用户管理                                         |     ✔      |     ❌      |     ❌      |\n| 全部题目增加                                     |     ✔      |     ✔      |     ✔      |\n| 其他人创建的题目查看                             |     ✔      |     ✔      |     ❌      |\n| 自己创建的题目查看                               |     ✔      |     ✔      |     ✔      |\n| 其他人创建的题目修改                             |     ✔      |     ✔      |     ❌      |\n| 自己创建的题目修改                               |     ✔      |     ✔      |     ✔      |\n| 全部题目删除                                     |     ✔      |     ✔      |     ❌      |\n| 全部题目权限修改（公开、隐藏、比赛）             |     ✔      |     ✔      |     ❌      |\n| 全部题目评测数据下载                             |     ✔      |     ✔      |     ❌      |\n| 导入远程OJ题目                                   |     ✔      |     ✔      |     ✔      |\n| 全部比赛权限(增加、删除、修改)                   |     ✔      |     ❌      |     ❌      |\n| 自己创建的比赛（增加、修改）                     |     ✔      |     ✔      |     ✔      |\n| 自己创建的比赛的题目（查看、增加，修改，移除）   |     ✔      |     ✔      |     ✔      |\n| 其他人创建的比赛的题目（查看、增加，修改，移除） |     ✔      |     ✔      |     ❌      |\n| 自己创建的比赛的题目（评测数据下载、删除）       |     ✔      |     ✔      |     ❌      |\n| 自己创建的比赛的题目权限修改（隐藏、删除）       |     ✔      |     ✔      |     ✔      |\n| 自己创建的比赛的题目权限修改为公开题目           |     ✔      |     ✔      |     ❌      |\n| 讨论管理                                         |     ✔      |     ✔      |     ✔      |\n| 全部训练权限(增加、删除、修改)                   |     ✔      |     ❌      |     ❌      |\n| 自己创建的训练（增加、修改）                     |     ✔      |     ✔      |     ✔      |\n\n**用户角色说明**\n\n\n| 权限         | 用户(默认) | 用户(禁止提交) | 用户(禁止发讨论) | 用户(禁言) | 用户(禁止提交&禁止发讨论) | 用户(禁止提交&禁言) | 封禁 |\n| ------------ | :--------: | :------------: | :--------------: | :--------: | :-----------------------: | :-----------------: | :--: |\n| 发布讨论     |     ✔      |       ✔        |        ❌         |     ❌      |             ❌             |          ❌          |  ❌   |\n| 修改讨论     |     ✔      |       ✔        |        ✔         |     ✔      |             ✔             |          ✔          |  ❌   |\n| 删除讨论     |     ✔      |       ✔        |        ✔         |     ✔      |             ✔             |          ✔          |  ❌   |\n| 发表评论     |     ✔      |       ✔        |        ✔         |     ❌      |             ✔             |          ❌          |  ❌   |\n| 删除评论     |     ✔      |       ✔        |        ✔         |     ✔      |             ✔             |          ✔          |  ❌   |\n| 提交代码     |     ✔      |       ❌        |        ✔         |     ✔      |             ❌             |          ❌          |  ❌   |\n| 一切用户权力 |     ✔      |       ❗        |        ❗         |     ❗      |             ❗             |          ❗          |  ❌   |\n\n1. 进入后台管理\n\n2. 点击编辑后，可修改用户角色\n"
  },
  {
    "path": "docs/src/use/close-free-cdn.md",
    "content": "# 取消前端免费CDN\n\n由于有的机房的网络不支持一些域名的访问，有防火墙挡住，所以可能前端页面的js和css的CDN访问不了，导致页面打不开。\n\n:::info\nvoj挂载了一些前端静态资源库的免费CDN，全部都是域名`unpkg.com`和`bytecdntp.com`下的免费CDN\n:::\n\n可以在对应的电脑浏览器上打开以下链接，如果能正常访问则没有问题。\n\n```html\nhttps://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.11/vue.min.js\n或\nhttps://unpkg.com/vxe-table@2.9.26/lib/style.min.css\n```\n\n:::warning\nvoj-frontend(前端vue项目)如果不挂载任何CDN，最终打包生成的文件夹大小约8MB\n:::\n\n## 一、全部打包且部署\n\n:::info\n如果本身voj部署在**学校内网机器**上或者**云服务器是无带宽上限、按流量计费的实例**，那么可以不用考虑带宽问题，可以直接取消CDN挂载，直接全部自己打包成对应的静态文件，然后挂载到docker的`voj-frontend`镜像里面\n:::\n**操作如下:**\n  1. 下载前端源代码：[https://github.com/simplefanc/voj/tree/master/voj-vue](https://github.com/simplefanc/voj/tree/master/voj-vue)\n\n  2. 进入`voj-vue`文件夹，编辑`vue.config.js`文件，按下面的修改\n\n     ```js\n     // 该变量改成false\n     const isProduction = false;\n\n     // 本地环境是否需要使用cdn，该变量改成false\n     const devNeedCdn = false;\n\n     // 找到下面对应的cdn的js链接和css链接，全部注释掉\n       css: [\n           // 'https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.14.0/theme-chalk/index.min.css',\n           // \"https://cdn.jsdelivr.net/npm/github-markdown-css@4.0.0/github-markdown.min.css\",\n           // \"https://cdn.jsdelivr.net/npm/vxe-table@2.9.26/lib/style.min.css\",\n       ],\n       js: [\n           // \"https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.1/vue.min.js\",\n           // \"https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.2.0/vue-router.min.js\",\n           // \"https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.0/axios.min.js\",\n           // \"https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.15.3/index.min.js\",\n           // \"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.3.2/highlight.min.js\",\n           // \"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js\",\n           // \"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/locale/zh-cn.min.js\",\n           // \"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/locale/en-gb.min.js\",\n           // \"https://cdnjs.cloudflare.com/ajax/libs/echarts/4.9.0-rc.1/echarts.min.js\",\n           // \"https://cdnjs.cloudflare.com/ajax/libs/vue-echarts/5.0.0-beta.0/vue-echarts.min.js\",\n           // \"https://cdn.jsdelivr.net/npm/vuex@3.5.1/dist/vuex.min.js\",\n           // \"https://cdn.jsdelivr.net/npm/xe-utils@3.4.3/dist/xe-utils.umd.min.js\",\n           // \"https://cdn.jsdelivr.net/npm/vxe-table@2.9.26/lib/index.umd.min.js\",\n           // \"https://unpkg.com/mavon-editor@2.9.1/dist/mavon-editor.js\"\n       ]\n     ```\n\n  3. 进入`voj-vue/src`文件夹，编辑`main.js`文件，将内容替换成如下：\n\n     ```js\n     import Vue from 'vue'\n     import App from './App.vue'\n     import store from './store'\n     import Element from 'element-ui'\n     import i18n from '@/i18n'\n     \n     import \"element-ui/lib/theme-chalk/index.css\"\n     import 'font-awesome/css/font-awesome.min.css'\n     import Message from 'vue-m-message'\n     import 'vue-m-message/dist/index.css'\n     import axios from 'axios'\n     \n     import Md_Katex from '@iktakahiro/markdown-it-katex'\n     \n     import 'xe-utils' \n     import VXETable from 'vxe-table'\n     import 'vxe-table/lib/style.css'\n     \n     import Katex from '@/common/katex'\n     \n     import VueClipboard from 'vue-clipboard2'\n     \n     import highlight from '@/common/highlight'\n     \n     import filters from '@/common/filters.js'\n     import VueCropper from 'vue-cropper'\n     \n     import ECharts from 'vue-echarts/components/ECharts.vue'\n     import 'echarts/lib/chart/bar'\n     import 'echarts/lib/chart/line'\n     import 'echarts/lib/chart/pie'\n     import 'echarts/lib/component/title'\n     import 'echarts/lib/component/grid'\n     import 'echarts/lib/component/dataZoom'\n     import 'echarts/lib/component/legend'\n     import 'echarts/lib/component/tooltip'\n     import 'echarts/lib/component/toolbox'\n     import 'echarts/lib/component/markPoint'\n     Vue.component('ECharts', ECharts)\n     \n     import VueECharts from 'vue-echarts';\n     Vue.component('ECharts', VueECharts)\n     import VueParticles from 'vue-particles'\n     import SlideVerify from 'vue-monoplasty-slide-verify'\n     \n     //  markdown编辑器\n     import mavonEditor from 'mavon-editor'  //引入markdown编辑器\n     import 'mavon-editor/dist/css/index.css';\n     Vue.use(mavonEditor)\n     \n     import {Drawer,List,Menu,Icon,AppBar,Button,Divider} from 'muse-ui';\n     import 'muse-ui/dist/muse-ui.css';\n     \n     import VueDOMPurifyHTML from 'vue-dompurify-html'\n     Vue.use(VueDOMPurifyHTML)\n     \n     import router from './router'\n     Vue.use(Drawer)\n     Vue.use(List)\n     Vue.use(Menu)\n     Vue.use(Icon)\n     Vue.use(AppBar)\n     Vue.use(Button)\n     Vue.use(Divider)\n     \n     Object.keys(filters).forEach(key => {   // 注册全局过滤器\n       Vue.filter(key, filters[key])\n     })\n     Vue.use(VueParticles) // 粒子特效背景\n     Vue.use(Katex)  // 数学公式渲染\n     \n     VXETable.setup({\n       // 对组件内置的提示语进行国际化翻译\n       i18n: (key, value) => i18n.t(key, value)\n     })\n     Vue.use(VXETable) // 表格组件\n     Vue.use(VueClipboard) // 剪贴板\n     Vue.use(highlight) // 代码高亮\n     Vue.use(Element,{\n       i18n: (key, value) => i18n.t(key, value)\n     })\n     \n     Vue.use(VueCropper) // 图像剪切\n     Vue.use(Message, { name: 'msg' }) // `Vue.prototype.$msg` 全局消息提示\n     \n     Vue.use(SlideVerify) // 滑动验证码组件\n     \n     Vue.prototype.$axios = axios\n     \n     Vue.prototype.$markDown = mavonEditor.mavonEditor.getMarkdownIt().use(Md_Katex)  // 挂载到vue\n     \n     Vue.config.productionTip = false\n     new Vue({\n       router,\n       store,\n       i18n,\n       render: h => h(App)\n     }).$mount('#app')\n     ```\n\n\n  4. 然后使用在`voj-vue`目录下，使用`npm run build`，npm请自行百度下载安装，之后会生成一个dist文件夹，结构如下：\n\n     ```\n     dist\n     ├── index.html\n     ├── favicon.ico\n     └── assets\n         ├── css\n         │   ├── ....\n         ├── fonts\n         │   ├── ....\n         ├── img\n         │   ├── ....\n         ├── js\n       │   ├── ....\n     \n     ....\n     ....\n     ```\n\n     将 `dist` 文件夹复制到服务器上某个目录下，比如 `/voj/www/html/dist`，然后修改 `docker-compose.yml`，在 `voj-frontend` 模块中的 `volumes` 中增加一行 `- /voj/www/html/dist:/usr/share/nginx/html` （冒号前面的请修改为实际的路径），然后 `docker-compose up -d` 即可。\n\n\n## 二、全部打包但有个人CDN服务器\n:::info\n如果云服务器是只有固定小流量出口带宽的，例如1M,2M的，害怕访问速度太慢，但是有钱买CDN服务器，可以先按照上面的方式，生成对应的本地静态文件夹，然后把`dist/assets`文件夹放在CDN服务器上，然后修改`dist/index.html`\n:::\n  **(建议：有弄过CDN的可以这样搞)**\n\n  添加css等文件的导入\n\n  ```html\n  <link href=\"cdn服务器的地址/assets/css/文件名称.css\" rel=\"prefetch\">\n  ```\n\n  添加js等文件的导入\n\n  ```html\n  <script src=\"cdn服务器的地址/assets/js/文件名称.js\">\n  ```\n\n\n    ..............................\n\n   将 `dist` 文件夹复制到服务器上某个目录下，比如 `/voj/www/html/dist`，然后修改 `docker-compose.yml`，在 `voj-frontend` 模块中的 `volumes` 中增加一行 `- /voj/www/html/dist:/usr/share/nginx/html` （冒号前面的请修改为实际的路径），然后 `docker-compose up -d` 即可。\n"
  },
  {
    "path": "docs/src/use/contest.md",
    "content": "# 比赛介绍\n\n:::tip\n总概功能介绍\n\n- 支持ACM、OI、IOI赛制\n- 支持公开赛、保护赛、私有赛\n- 支持线下打印功能\n- 支持比赛账号限制功能\n- 支持封榜、支持打星队伍、支持关注队伍\n- 支持比赛外部榜单显示\n- 支持榜单显示用户显示自定义\n:::\n\n## 两种赛制\n\n\n### 一、ACM 比赛模式\n  在该模式下,我们严格按照ACM-ICPC的比赛规则来进行，Contest设置项中的`Seal Time Rank`即为是否封榜，封榜后将不再刷新排名。可选择比赛结束前半小时，比赛前一小时，比赛全程封榜。\n\n  **如果开启封榜，则封榜期间的角色不同如下：**\n  :::info\n\n1. 封榜期间，**超级管理员与比赛创建者**不受影响，正常可查看题目统计数据，提交数据等，排行榜需自行开启强制刷新，同时提交结果可以及时看到评测结果，但不会纳入排行榜！\n2. 封榜期间，**普通用户与非比赛创建者**（包括其它管理员角色），可以及时看到自己的提交结果，但不可看到别人**封榜后**的提交，不能看到题目的统计情况，排行榜保持**封榜前**的排名数据。\n   :::\n\n **注意：比赛一结束，默认所有数据变成正常显示，但后台可以设置比赛结束继续封榜！**\n\n\n### 二、OI 比赛模式\n\n\n  在OI模式下，选手的提交将根据得分点来计分，多次提交**以最后一次提交**（**或选择以最高得分的提交**）为准，排名规则为多个题目的总分数。同样可以进行封榜操作，封榜时段，选手不能查看到实时的排行榜数据！\n\n  **如果开启封榜，则封榜期间的角色不同如下：**\n  :::info\n\n  1. 封榜期间，**超级管理员与比赛创建者**不受影响，正常可查看题目统计数据，提交数据等，排行榜需自行开启强制刷新，同时提交结果可以及时看到评测结果，但不会纳入排行榜！\n  2. 封榜期间：**普通用户与非比赛创建者**（包括其它管理员角色），可以及时看到自己的提交结果，但不可看到别人**封榜后**的提交，不能看到题目的统计情况，排行榜保持**封榜前**的排名数据。\n      :::\n\n  **比赛一结束，默认所有数据变成正常显示，但后台可以设置比赛结束继续封榜！**\n\n:::warning\n注意：管理员可以选择强制刷新，查看实时的排行榜数据！通过`Force Update`来强制刷新榜，且刷新后的榜仅对管理者可见。\n:::\n## 比赛权限\n\n:::tip\n- **公开赛**：所有用户都可以查看比赛详情、比赛题目、比赛提交，比赛排行榜、比赛讨论等，且都可以在比赛阶段随时提交。\n- **保护赛**：所有用户都可以查看比赛详情、比赛题目、比赛提交，比赛排行榜、比赛讨论等，但在比赛阶段提交需要提供该比赛的密码！\n- **私有赛**：仅支持有比赛密码的用户进入比赛，查看查看比赛详情、比赛题目、比赛提交，比赛排行榜、比赛讨论等，包括提交。\n:::\n\n\n## 比赛题目\n\n**后台比赛题目列表管理页面如下**\n\n## 比赛管理\n\n**后台比赛管理页面如下**"
  },
  {
    "path": "docs/src/use/custom-difficulty.md",
    "content": "# 自定义题目难度\n\n:::tip\n\n由于题目的难度是由前端代码决定**显示文本与背景颜色**的，所以想要修改或增删难度需要自定义前端，那么首先得知道如何**自定义前端**，[请点击查看](/use/update-fe/)\n\n:::\n\n接着，找到`/voj-vue/src/common/constants.js`的文件，修改里面的难度常量代码`PROBLEM_LEVEL`如下，修改完后，请自行build前端项目生成dist的静态文件夹，上传到服务器后，修改挂载，重启voj-frontend容器即可，重启完后，浏览器可能有缓存，多刷新即可！！！\n\n```javascript\nexport const PROBLEM_LEVEL={\n  '0':{\n    name:{\n      'zh-CN':'简单', // 中文文本显示\n      'en-US':'Easy', // 英文文本显示\n    },\n    color:'#19be6b'  // 背景颜色\n  },\n  '1':{\n    name:{\n      'zh-CN':'中等',\n      'en-US':'Mid',\n    },\n    color:'#2d8cf0'\n  },\n  '2':{\n    name:{\n      'zh-CN':'困难',\n      'en-US':'Hard',\n    },\n    color:'#ed3f14'\n  }\n}\n\n```\n\n:::warning\n\n注意：每个OI题目的得分计算公式为：(总得分×0.1+难度×2)×(通过测试点数÷总测试点数)，所以上面代码中的数字会影响OI题目得分，请尽量合理使用正整数！！！\n\n:::\n"
  },
  {
    "path": "docs/src/use/discussion-admin.md",
    "content": "# 评论管理\n\n\n:::tip\n- 后台管理员可以查看所有的讨论帖，并且可以选择是否置顶，是否正常显示，删除，查看等\n- 后台管理员可以查看对应讨论帖的举报内容\n:::\n\n"
  },
  {
    "path": "docs/src/use/import-problem.md",
    "content": "# 题目管理\n\n## 一、VOJ题目\n\n#### 1. 导出题目\n\n点击选择需要的题目，便可以批量导出成一个zip压缩包，分别对应一个json格式的题目数据，一个对应名字的文件夹存放评测数据文件，具体的文件结构如下：\n\n```\n+-- problem_1000.json\n+-- problem_1000\n|   +-- 1.in\n|   +-- 1.out\n|   +-- ....\n+-- problem_1001.json\n+-- problem_1001\n|   +-- 1.in\n|   +-- 1.out\n|   +-- ....\n```\n\n#### 2. 导入题目\n\n选择需要导入的题目数据zip压缩包，注意**不要多一层文件夹进行压缩**，**请保证题目json文件的名字与其对应的存放评测数据的文件夹名字一致**，具体文件格式如下：\n\n```\n+-- problem_1000.json\n+-- problem_1000\n|   +-- 1.in\n|   +-- 1.out\n|   +-- ....\n+-- problem_1001.json\n+-- problem_1001\n|   +-- 1.in\n|   +-- 1.out\n|   +-- ....\n```\n\n#### 3. 题目的json文件格式\n\n请严格按照以下格式，才可以正常导入。\n\n```json\n{\n  \"judgeMode\":\"default\", // 普通判题：default, 特殊判题：spj, 交互判题：interactive\n  // 题目支持的语言如下，可多可少\n  \"languages\": [\"C\", \"C++\", \"Java\", \"Python3\", \"Python2\",  \"Golang\", \"C#\"], \n  \"samples\": [\n      {\n          \"input\": \"1.in\", \n          \"output\": \"1.out\",\n          //\"score\": 10  // 如果是oi题目需要给测试点加得分\n      },\n      {\n          \"input\": \"2.in\", \n          \"output\": \"2.out\",\n          //\"score\": 10  // 如果是oi题目需要给测试点加得分\n      }\n  ], \n  \"tags\": [\"测试题\",\"测试\"], // 题目标签，一般不超过三个 \n  \"problem\": {\n      \"auth\": 1, // 1 公开赛\n      \"author\": \"admin\", // 题目上传的作者，请使用用户名\n      \"isRemote\": false, // 均为非VJ题目，不用修改\n      \"problemId\": \"VOJ-1010\", // 题目的展示id\n      \"description\": \"\", // 题目的描述，支持markdown语法\n      \"source\": \"\", // 题目来源\n      \"title\": \"\", // 题目标题\n      \"type\": 0,  // 0为ACM题目，1为OI题目\n      \"timeLimit\": 1000, // 时间限制 单位是ms\n      \"memoryLimit\": 256, // 空间限制 单位是mb\n      \"input\": \"\", // 题目的输入描述\n      \"output\": \"\", // 题目的输出描述\n      \"difficulty\": 0, // 题目难度，1为简单，2为中等，3为困难\n      \"examples\": \"\", // 题目的题面样例，格式为<input>输入</input><output>输出</output><input>输入</input><output>输出</output>\n      \"ioScore\": 100, // OI题目总得分，与测试点总分一致\n      \"codeShare\": true, // 该题目是否允许用户共享其提交的代码 \n      \"hint\": \"\", // 题目提示\n      \"isRemoveEndBlank\": true, // 评测数据的输出是否自动去掉行末空格\n      \"openCaseResult\": true,  // 是否允许用户看到各个评测点的结果\n       // \"spjLanguage:\"C\" // 特殊判题的程序代码语言\n      // \"spjCode\":\"\" // 特殊判题的代码\n  }, \n  \"codeTemplates\": [\n      {\n          \"code\": \"\", // 模板代码\n          \"language\": \"C\" // 模板代码语言\n      }, \n      {\n          \"code\": \"\", // 模板代码\n          \"language\": \"C++\"// 模板代码语言\n      }\n   ],\n    // 用户程序的额外库文件 key：文件名，value：文件内容，如果没有请去掉\n   \"userExtraFile\":{\n       \"testlib.h\":\"code\",\n       \"stdio.h\":\"...\"\n   },\n    // 特殊或交互程序的额外库文件 key：文件名，value：文件内容，如果没有请去掉\n    \"judgeExtraFile\":{\n        \"testlib.h\":\"code\",\n        \"stdio.h\":\"...\"\n    }\n}\n```\n\n## 二、导入QDUOJ或FPS格式的题目\n\n1. 请严格按照青岛OJ的后台导出的压缩文件来上传。\n2. 请使用标准的FPS格式的题目数据文件(.xml)\n\n## 三、导入其它OJ题目\n\n导入HDU、POJ、MXT、JSK、TKOJ的题目，只需提供该题目的题号便可一键导入。\n\n:::tip\n\n- HDU、POJ、MXT、TKOJ的题号一般是 `1000`以上的数字\n- JSK的题号是`A1000`或`T1000`格式，具体请到 [https://nanti.jisuanke.com](https://nanti.jisuanke.com/) 查看\n\n:::\n\n1. 管理员进入后台，点击题目列表\n\n2. 然后添加上方的添加按钮\n\n3. 在弹出窗中选择OJ名称及题号，即可导入\n"
  },
  {
    "path": "docs/src/use/import-user.md",
    "content": "# 导入用户\n\n**要求如下:**\n\n:::tip\n1. 用户数据导入仅支持csv格式的用户数据。\n2. 共7列数据：**用户名和密码不能为空**，邮箱、真实姓名、性别、昵称和学校可选填，否则该行数据可能导入失败。\n3. 第一行不必写(“用户名”，“密码”，“邮箱”，\"真实姓名\"，“性别”，“昵称”，“学校”)这7个列名\n4. 性别为男请使用“male”或“0”，女请使用“female”或“1”，不填默认为“secrecy”。\n5. 请导入保存为UTF-8编码的文件，否则中文可能会乱码。\n:::\n"
  },
  {
    "path": "docs/src/use/judge-mode.md",
    "content": "# 判题模式\n\n### 一、普通判题\n\n**普通模式是程序在线评测系统(OJ)通用的判题模式**，主要的实现逻辑步骤如下：\n\n:::tip\n\n1. 选手程序读取题目标准输入文件的数据\n\n2. 判题机执行代码逻辑得到选手输出\n3. 再将选手输出与题目标准输出文件的数据进行对比，最终得到判题结果\n\n:::\n\n### 二、特殊判题\n\n#### 1. 什么是特殊判题？\n\n特殊判题（Special Judge）是指OJ将使用一个特定的程序来判断提交的程序的输出是不是正确的，而不是单纯地看提交的程序的输出是否和标准输出一模一样。\n\n#### 2. 使用场景\n\n一般使用Special Judge都是因为题目的答案不唯一，更具体一点说的话一般是两种情况：\n\n:::tip\n\n- 题目最终要求输出一个解决方案，而且这个解决方案可能不唯一。\n- 题目最终要求输出一个浮点数，而且会告诉只要答案和标准答案相差不超过某个较小的数就可以，比如0.01。这种情况保留3位小数、4位小数等等都是可以的，而且多保留几位小数也没什么坏处。\n\n:::\n\n#### 3. 支持\n\nVOJ支持testlib.h头文件的直接使用  具体使用文档请看[https://oi-wiki.org/tools/testlib/](https://oi-wiki.org/tools/testlib/)\n\n#### 4. 例题\n\n在创建题目的适合，选择开启特殊判题，编写特殊判题程序，然后编译通过便可。\n\n> 后台对题目使用特殊判题时，请参考以下程序例子 判断精度\n\n- 使用testlib.h来进行特殊判题\n\n```cpp\n#include <iostream>\n#include \"testlib.h\"\n\nusing namespace std;\n\nint main(int argc, char *args[]){\n    /**\n    inf: 输入文件流\n    ouf: 选手输出流\n    ans: 标准答案流\n    **/\n    registerTestlibCmd(argc, args);\n    double pans = ouf.readDouble();\n    double jans = ans.readDouble();\n    if (fabs(pans - jans)<0.01)\n        quitf(_ok, \"The answer is correct.\");\n    else\n        quitf(_wa, \"The answer is wrong: expected = %f, found = %f\", jans, pans);\n    // quitf(_pe, \"The answer is presentation error.\"); // 格式错误\n    // quitf(_fail, \"The something wrong cause system error.\"); // 系统错误\n}\n```\n\n\n\n- 读取文件进行特殊判题\n\n```cpp\n#include<iostream>\n#include<cstdio>\n\n#define PC 99  // 部分正确\n#define AC 100 // 全部正确\n#define PE 101 // 格式错误\n#define WA 102 // 答案错误\n#define ERROR 103 // 系统错误\n\nusing namespace std;\n\nvoid close_file(FILE *f){\n    if(f != NULL){\n        fclose(f);\n    }\n}\n\nint main(int argc, char *args[]){\n    /**\n    args[1]：标准输入文件路径 \n    args[2]：选手输出文件路径\n    args[3]：标准输出文件路径 \n    **/\n    FILE *std_input_file = fopen(args[1], \"r\");\n    FILE *user_output_file = fopen(args[2], \"r\");\n    FILE *std_output_file = fopen(args[3], \"r\");\n    \n    double std_out; // 标准输出\n\tfscanf(user_output_file, \"%lf\", &std_out);\n    \n    double user_output;// 用户输出 \n    fscanf(std_output_file, \"%lf\", &user_output);\n    \n    // 关闭文件流\n    close_file(std_input_file);\n    close_file(user_output_file);\n    close_file(std_output_file);\n    \n\tif (fabs(user_output - std_out)<=1e-6)\n\t\treturn AC;\n\telse \n\t\treturn WA;\n}\n```\n\n\n\n### 三、交互判题\n\n**交互题** 是需要选手程序与测评程序交互来完成任务的题目。一类常见的情形是，选手程序向测评程序发出询问，并得到其反馈。\n\n交互方式主要有如两种：**STDIO 交互**和**Grader 交互**\n\n:::tip\n\n主要的交互逻辑：交互程序的标准输出通过交互通道写到选手程序标准输入，选手程序的标准输出通过交互通道写到交互程序的标准输入，两者需要刷新输出缓存\n\n:::\n\n:::warning\n\n在 C/C++ 中，`fflush(stdout)` 和 `std::cout << std::flush` 可以实现这个操作（使用 `std::cout << std::endl` 换行时也会自动刷新缓冲区，但是 `std::cout << '\\n'` 不会）\n\n:::\n\n#### 1. 标准交互题\n\n**A+B问题**\n\n*选手程序*\n\n```cpp\n#include <iostream>\n#include <cstdio>\nusing namespace std;\nint main(){\n\tint a,b;\n\tcin >> a >> b;\n\tcout << a + b;\n\treturn 0;\n}\n```\n\n*交互程序*（这里使用testlib来实现，但也可以自己读取文件实现）\n\n```cpp\n#include \"testlib.h\"\n#include <iostream>\n\nusing namespace std;\n\nint main(int argc, char* argv[])\n{\n    setName(\"Interactor A+B\");\n    registerInteraction(argc, argv);\n    // 读取题目标准输入文件的数据\n    int a = inf.readInt();\n    int b = inf.readInt();\n    // 往交互通道写数据，记得用endl刷新缓冲区\n    cout << a << \" \" << b << endl;\n    int ans;\n    // 读取用户程序写入到交互通道的数据\n    cin >> ans;\n    if (a + b == ans){ // 判断结果\n    \tquitf(_ok, \"correct\");\n    }else{\n    \tquitf(_wa,\"incorrect\");\n    }\n}\n```\n\n#### 2. 函数交互题\n\n:::info\n\n主要的交互逻辑：\n\n1. 用户调用提供的库文件里面的方法执行答题逻辑，最后得出指定结果；\n2. 交互测评程序根据选手调用交互库执行逻辑后得出的结果，来进行最终判断评测的结果。\n\n:::\n\n需要给选手的程序添加交互库，在后台的题目管理可以选择添加。\n\n**交互库**：提供写好的方法给选手调用\n\n```cpp\n#include <bits/stdc++.h>\nusing namespace std;\n\nnamespace interactive {\n    static int n, m, cnt;\n    static bool hasUsedGetN = false;\n    static bool hasSubmitted = false;\n    void RE() {\n        puts(\"re\");\n        exit(0);\n    }\n    int getn() {\n        if (hasUsedGetN) RE();\n        cin >> n;\n        hasUsedGetN = 1;\n        m = rand() % n + 1;\n        return n;\n    }\n    int query(int x) {\n        if (!hasUsedGetN || hasSubmitted) RE();\n        cnt++;\n        if (cnt > 100000) RE();\n        if (x == m)\n            return (rand() % 10 <= 3);\n        else\n            return (rand() % 10 <= 4);\n    }\n    void submit(int x) {\n        if (hasSubmitted) RE();\n        if (x == m)\n            puts(\"ok\");\n        else\n            puts(\"wa\");\n        hasSubmitted = 1;\n    }\n} \n\nusing interactive::getn;\nusing interactive::query;\nusing interactive::submit;\n```\n\n**用户程序**：调用库文件的方法进行答题\n\n```cpp\n#include <bits/stdc++.h>\n#include \"interactive.h\"\n\nint main()\n{\n    int n = getn();\n    for(int i = 1; i <= n; i++)\n    {\n        for(int j = 0; j < 900; j++)\n        {\n            if(query(i)) arr[i]++;\n        }\n    }\n    int min = 1000, ans = 0;\n    for(int i = 1; i <= n; i++)\n    {\n        if(arr[i] < min)\n        {\n            min = arr[i];\n            ans = i;\n        }\n    }\n    submit(ans);\n    return 0;\n}\n```\n\n**交互测评程序**：根据选手调用交互库执行逻辑后得出的结果，来进行最终判断评测的结果\n\n```cpp\n#include \"testlib.h\"\n#include <string>\n\nint main(int argc, char* argv[]) {\n    registerTestlibCmd(argc, argv);\n    // 读取选手最终输出文件的数据来判断结果\n    std::string s = ouf.readToken();\n    if (s == \"ok\")\n        quitf(_ok, \"Correct\");\n    else\n        quitf(_wa, \"Wrong Answer\");\n}\n```\n\n"
  },
  {
    "path": "docs/src/use/notice-announcement.md",
    "content": "# 通知和公告发布\n\n:::tip\n1. 通知和公告都仅有超级管理员可操作\n2. 通知是系统消息通知，每个小时推送一次到用户的站内消息系统\n:::\n\n\n"
  },
  {
    "path": "docs/src/use/testcase.md",
    "content": "# 测试用例\n\n**进入后台添加题目，上传题目测试用例数据可以选择手动输入、Zip文件上传两种方式**\n\n## 一、手动输入\n\n每次点击`Add Sampple`就可以手动填入该用例的输入与输出，该方式比较适合题目数据简单的，同时手动输入的题目数据将记录进数据库，下次对该题目进行修改可以直接获取，然后进行测试数据的修改，同时也会在服务器对应的testcase文件夹生成对应的文件。\n\n## 二、文件上传\n\n对于普通题目，测试用例文件包括`in`、`out`、`ans`、`txt`四种拓展名\n\n例如有两组测试用例，则对于普通题目测试用例的文件名分别为`*.in, *.out（*.ans）`，或者`*input*.txt, *output*.txt ` ，其他形式的文件后台均不识别。\n\n压缩时，请将文件都放在压缩包的根目录，而不是包含在某一个文件夹中，比如正确的格式是：\n\n```bash\n├── 1.in          \n├── 1.out\n├── 2.in\n├── 2.out\n```\n\n```bash\n├── 1.in\n├── 1.ans\n├── 2.in\n├── 2.ans\n```\n\n或者\n\n```bash\n├── input1.txt          \n├── output1.txt \n├── input2.txt \n├── output2.txt \n```\n\n然后压缩测试用例到一个zip中\n\n:::danger\n**注意：不要在这些文件外面套多一层文件夹，请直接压缩！！！**\n:::\n\n:::info\n建议：尽量合并测试用例到一个文件中，减少测试用例组数，这会一定程度上提高判题性能。\n:::"
  },
  {
    "path": "docs/src/use/training.md",
    "content": "# 训练介绍\n\n:::tip\n\n训练分为**公开训练**与**私有训练**，同时可自定义训练分类 \n\n两种训练其实都是题单功能，区别在于私有训练拥有记录榜单\n\n:::\n\n:::warning\n\n在训练题单里面的题目提交情况与公开题库的对应题目的数据一致，所以只能显示公开权限的题目，其功能主要是汇总对应的题型。\n\n:::\n\n### 1. 公开训练\n\n- 管理员可在后台添加公开权限的题目，同时能对题目进行排序。\n- 题目的所有用户提交情况以及用户自身对该题目的提交情况与题目列表的题目数据同步。\n\n### 2. 私有训练\n\n- 管理员可在后台添加公开权限的题目，同时能对题目进行排序。\n- 题目的所有用户提交情况以及用户自身对该题目的提交情况与题目列表的题目数据同步。\n\n与**公开训练**的区别：\n\n- 非训练创建者和超级管理员访问私有训练需要对应的密码。\n- **超级管理员与训练创建者的题目提交情况不会计入记录榜单**\n- 系统会同步普通用户对应训练题目的提交情况，生成对应的记录榜单。\n- 用户在进入私有训练后，只有在训练里面的题目提交，记录榜单才会继续更新记录。\n\n**系统同步用户对应题目数据的情况如下：**\n\n:::info\n\n- 用户第一次输入密码成功后，系统会同步其对应题目的提交情况到榜单。\n- 后台管理员增加新的题目，系统会同步训练已成功访问的所有用户对应新题目的提交情况。\n- 后台管理员移除题目，系统会删除对应题目的榜单记录。\n\n:::\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.simplefanc</groupId>\n    <artifactId>voj</artifactId>\n    <packaging>pom</packaging>\n    <version>1.0</version>\n    <modules>\n        <module>voj-common</module>\n        <module>voj-backend</module>\n        <module>voj-judger</module>\n    </modules>\n\n    <name>voj</name>\n    <!--版本控制-->\n    <properties>\n        <maven.compiler.source>17</maven.compiler.source>\n        <maven.compiler.target>17</maven.compiler.target>\n        <project.bulid.sourceEncoding>UTF-8</project.bulid.sourceEncoding>\n        <skipTests>true</skipTests>\n        <shiro.redis.boot.version>3.3.1</shiro.redis.boot.version>\n        <mysql.version>8.0.19</mysql.version>\n        <druid.version>1.1.20</druid.version>\n        <mybatis.spring.boot.version>3.2.0</mybatis.spring.boot.version>\n        <!--        <spring.boot.version>2.2.6.RELEASE</spring.boot.version>-->\n        <!--        <spring.cloud.version>Hoxton.SR1</spring.cloud.version>-->\n        <!--        <spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version>-->\n        <spring.boot.version>2.6.3</spring.boot.version>\n        <spring.cloud.version>2021.0.1</spring.cloud.version>\n        <spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>\n        <voj.common.version>1.0</voj.common.version>\n        <easyexcel.version>3.3.2</easyexcel.version>\n        <hutool.version>5.7.22</hutool.version>\n        <jwt.version>3.19.1</jwt.version>\n        <captcha.version>1.6.2</captcha.version>\n        <oshi.version>5.6.1</oshi.version>\n        <emoji.version>4.0.0</emoji.version>\n        <swagger.version>2.9.2</swagger.version>\n        <spring.checkstyle.plugin>0.0.29</spring.checkstyle.plugin>\n        <jsoup.version>1.14.3</jsoup.version>\n        <caffeine.version>3.1.1</caffeine.version>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>com.simplefanc</groupId>\n                <artifactId>voj-common</artifactId>\n                <version>${voj.common.version}</version>\n            </dependency>\n            <!--Springcloud的依赖-->\n            <dependency>\n                <groupId>org.springframework.cloud</groupId>\n                <artifactId>spring-cloud-dependencies</artifactId>\n                <version>${spring.cloud.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n            <!--spring cloud 阿里巴巴-->\n            <dependency>\n                <groupId>com.alibaba.cloud</groupId>\n                <artifactId>spring-cloud-alibaba-dependencies</artifactId>\n                <version>${spring.cloud.alibaba.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n            <!--Springboot依赖-->\n            <dependency>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-dependencies</artifactId>\n                <version>${spring.boot.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n            <!--excel-->\n            <dependency>\n                <groupId>com.alibaba</groupId>\n                <artifactId>easyexcel</artifactId>\n                <version>${easyexcel.version}</version>\n            </dependency>\n            <!--数据库-->\n            <dependency>\n                <groupId>mysql</groupId>\n                <artifactId>mysql-connector-java</artifactId>\n                <version>${mysql.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.alibaba</groupId>\n                <artifactId>druid</artifactId>\n                <version>${druid.version}</version>\n            </dependency>\n            <!--mybatis-plus-->\n            <dependency>\n                <groupId>com.baomidou</groupId>\n                <artifactId>mybatis-plus-boot-starter</artifactId>\n                <version>${mybatis.spring.boot.version}</version>\n            </dependency>\n            <!--shiro-->\n            <dependency>\n                <groupId>org.crazycake</groupId>\n                <artifactId>shiro-redis-spring-boot-starter</artifactId>\n                <version>${shiro.redis.boot.version}</version>\n            </dependency>\n            <!-- hutool工具类-->\n            <dependency>\n                <groupId>cn.hutool</groupId>\n                <artifactId>hutool-all</artifactId>\n                <version>${hutool.version}</version>\n            </dependency>\n            <!-- jwt -->\n            <dependency>\n                <groupId>com.auth0</groupId>\n                <artifactId>java-jwt</artifactId>\n                <version>${jwt.version}</version>\n            </dependency>\n            <!--生成验证码-->\n            <dependency>\n                <groupId>com.github.whvcse</groupId>\n                <artifactId>easy-captcha</artifactId>\n                <version>${captcha.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.github.oshi</groupId>\n                <artifactId>oshi-core</artifactId>\n                <version>${oshi.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.vdurmont</groupId>\n                <artifactId>emoji-java</artifactId>\n                <version>${emoji.version}</version>\n            </dependency>\n            <!--导入swagger-->\n            <dependency>\n                <groupId>io.springfox</groupId>\n                <artifactId>springfox-swagger2</artifactId>\n                <version>${swagger.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.springfox</groupId>\n                <artifactId>springfox-swagger-ui</artifactId>\n                <version>${swagger.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.jsoup</groupId>\n                <artifactId>jsoup</artifactId>\n                <version>${jsoup.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.github.ben-manes.caffeine</groupId>\n                <artifactId>caffeine</artifactId>\n                <version>${caffeine.version}</version>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <resources>\n            <resource>\n                <directory>src/main/resources</directory>\n                <!-- https://www.cnblogs.com/zuojl/p/14977544.html -->\n                <filtering>true</filtering>\n            </resource>\n            <resource>\n                <directory>src/main/java</directory>\n                <includes>\n                    <include>**/*.xml</include>\n                </includes>\n            </resource>\n        </resources>\n        <pluginManagement>\n            <plugins>\n                <plugin>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-maven-plugin</artifactId>\n                    <version>${spring.boot.version}</version>\n                    <executions>\n                        <execution>\n                            <goals>\n                                <!--可以把依赖的包都打包到生成的Jar包中-->\n                                <goal>repackage</goal>\n                            </goals>\n                        </execution>\n                    </executions>\n                </plugin>\n            </plugins>\n        </pluginManagement>\n        <plugins>\n            <!--代码格式插件，默认使用spring 规则-->\n            <plugin>\n                <groupId>io.spring.javaformat</groupId>\n                <artifactId>spring-javaformat-maven-plugin</artifactId>\n                <version>${spring.checkstyle.plugin}</version>\n            </plugin>\n        </plugins>\n    </build>\n\n    <profiles>\n        <!-- 开发 -->\n        <!-- mvn clean package -P dev -->\n        <profile>\n            <!-- profile的id -->\n            <id>dev</id>\n            <properties>\n                <profiles.active>dev</profiles.active>\n            </properties>\n        </profile>\n        <!-- 生产 -->\n        <profile>\n            <id>prod</id>\n            <properties>\n                <profiles.active>prod</profiles.active>\n            </properties>\n            <activation>\n                <!-- 默认环境 -->\n                <activeByDefault>true</activeByDefault>\n            </activation>\n        </profile>\n    </profiles>\n</project>\n"
  },
  {
    "path": "voj-backend/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>voj</artifactId>\n        <groupId>com.simplefanc</groupId>\n        <version>1.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>voj-backend</artifactId>\n    <version>1.0</version>\n    <name>voj-backend</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.simplefanc</groupId>\n            <artifactId>voj-common</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-mail</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.retry</groupId>\n            <artifactId>spring-retry</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>druid</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>easyexcel</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.crazycake</groupId>\n            <artifactId>shiro-redis-spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-all</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.github.oshi</groupId>\n            <artifactId>oshi-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.vdurmont</groupId>\n            <artifactId>emoji-java</artifactId>\n        </dependency>\n        <!--redis整合-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-redis</artifactId>\n        </dependency>\n        <!--JWT-->\n        <dependency>\n            <groupId>com.auth0</groupId>\n            <artifactId>java-jwt</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-thymeleaf</artifactId>\n        </dependency>\n        <!--生成验证码-->\n        <dependency>\n            <groupId>com.github.whvcse</groupId>\n            <artifactId>easy-captcha</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n        <!--单元测试-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.jsoup</groupId>\n            <artifactId>jsoup</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.github.ben-manes.caffeine</groupId>\n            <artifactId>caffeine</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java",
    "content": "//\n// Source code recreated from a .class file by IntelliJ IDEA\n// (powered by Fernflower decompiler)\n//\n\npackage com.alibaba.druid.pool;\n\nimport com.alibaba.druid.DruidRuntimeException;\nimport com.alibaba.druid.filter.Filter;\nimport com.alibaba.druid.filter.FilterChainImpl;\nimport com.alibaba.druid.filter.FilterManager;\nimport com.alibaba.druid.pool.vendor.NullExceptionSorter;\nimport com.alibaba.druid.proxy.jdbc.DataSourceProxy;\nimport com.alibaba.druid.proxy.jdbc.TransactionInfo;\nimport com.alibaba.druid.stat.JdbcDataSourceStat;\nimport com.alibaba.druid.stat.JdbcSqlStat;\nimport com.alibaba.druid.stat.JdbcStatManager;\nimport com.alibaba.druid.support.logging.Log;\nimport com.alibaba.druid.support.logging.LogFactory;\nimport com.alibaba.druid.util.*;\n\nimport javax.management.JMException;\nimport javax.management.ObjectName;\nimport javax.management.openmbean.CompositeDataSupport;\nimport javax.security.auth.callback.NameCallback;\nimport javax.security.auth.callback.PasswordCallback;\nimport javax.sql.DataSource;\nimport java.io.PrintWriter;\nimport java.io.Serializable;\nimport java.sql.*;\nimport java.util.Date;\nimport java.util.*;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.atomic.AtomicIntegerFieldUpdater;\nimport java.util.concurrent.atomic.AtomicLongFieldUpdater;\nimport java.util.concurrent.locks.Condition;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.logging.Logger;\n\npublic abstract class DruidAbstractDataSource extends WrapperAdapter\n        implements DruidAbstractDataSourceMBean, DataSource, DataSourceProxy, Serializable {\n\n    public static final int DEFAULT_INITIAL_SIZE = 0;\n\n    public static final int DEFAULT_MAX_ACTIVE_SIZE = 8;\n\n    public static final int DEFAULT_MAX_IDLE = 8;\n\n    public static final int DEFAULT_MIN_IDLE = 0;\n\n    public static final int DEFAULT_MAX_WAIT = -1;\n\n    public static final String DEFAULT_VALIDATION_QUERY = null;\n\n    public static final boolean DEFAULT_TEST_ON_BORROW = false;\n\n    public static final boolean DEFAULT_TEST_ON_RETURN = false;\n\n    public static final boolean DEFAULT_WHILE_IDLE = true;\n\n    public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60000L;\n\n    public static final long DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS = 500L;\n\n    public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3;\n\n    public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1800000L;\n\n    public static final long DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS = 25200000L;\n\n    public static final long DEFAULT_PHY_TIMEOUT_MILLIS = -1L;\n\n    protected static final Object PRESENT = new Object();\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> errorCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"errorCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> dupCloseCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"dupCloseCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> startTransactionCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"startTransactionCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> commitCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"commitCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> rollbackCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"rollbackCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementHitCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"cachedPreparedStatementHitCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> preparedStatementCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"preparedStatementCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> closedPreparedStatementCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"closedPreparedStatementCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"cachedPreparedStatementCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementDeleteCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"cachedPreparedStatementDeleteCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementMissCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"cachedPreparedStatementMissCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> executeQueryCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"executeQueryCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> executeUpdateCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"executeUpdateCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> executeBatchCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"executeBatchCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> executeCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"executeCount\");\n    static final AtomicIntegerFieldUpdater<DruidAbstractDataSource> createErrorCountUpdater = AtomicIntegerFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"createErrorCount\");\n    static final AtomicIntegerFieldUpdater<DruidAbstractDataSource> creatingCountUpdater = AtomicIntegerFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"creatingCount\");\n    static final AtomicIntegerFieldUpdater<DruidAbstractDataSource> directCreateCountUpdater = AtomicIntegerFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"directCreateCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> createCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"createCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> destroyCountUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"destroyCount\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> createStartNanosUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"createStartNanos\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> failContinuousTimeMillisUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"failContinuousTimeMillis\");\n    static final AtomicIntegerFieldUpdater<DruidAbstractDataSource> failContinuousUpdater = AtomicIntegerFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"failContinuous\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> connectionIdSeedUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"connectionIdSeed\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> statementIdSeedUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"statementIdSeed\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> resultSetIdSeedUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"resultSetIdSeed\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> transactionIdSeedUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"transactionIdSeed\");\n    static final AtomicLongFieldUpdater<DruidAbstractDataSource> metaDataIdSeedUpdater = AtomicLongFieldUpdater\n            .newUpdater(DruidAbstractDataSource.class, \"metaDataIdSeed\");\n\n    private static final long serialVersionUID = 1L;\n\n    private static final Log LOG = LogFactory.getLog(DruidAbstractDataSource.class);\n\n    protected final Map<DruidPooledConnection, Object> activeConnections;\n\n    protected final Date createdTime;\n\n    protected final Histogram transactionHistogram;\n\n    protected volatile boolean defaultAutoCommit = true;\n\n    protected volatile Boolean defaultReadOnly;\n\n    protected volatile Integer defaultTransactionIsolation;\n\n    protected volatile String defaultCatalog = null;\n\n    protected String name;\n\n    protected volatile String username;\n\n    protected volatile String password;\n\n    protected volatile String jdbcUrl;\n\n    protected volatile String driverClass;\n\n    protected volatile ClassLoader driverClassLoader;\n\n    protected volatile Properties connectProperties = new Properties();\n\n    protected volatile PasswordCallback passwordCallback;\n\n    protected volatile NameCallback userCallback;\n\n    protected volatile int initialSize = 0;\n\n    protected volatile int maxActive = 8;\n\n    protected volatile int minIdle = 0;\n\n    protected volatile int maxIdle = 8;\n\n    protected volatile long maxWait = -1L;\n\n    protected int notFullTimeoutRetryCount = 0;\n\n    protected volatile String validationQuery;\n\n    protected volatile int validationQueryTimeout;\n\n    protected volatile boolean testOnBorrow;\n\n    protected volatile boolean testOnReturn;\n\n    protected volatile boolean testWhileIdle;\n\n    protected volatile boolean poolPreparedStatements;\n\n    protected volatile boolean sharePreparedStatements;\n\n    protected volatile int maxPoolPreparedStatementPerConnectionSize;\n\n    protected volatile boolean inited;\n\n    protected volatile boolean initExceptionThrow;\n\n    protected PrintWriter logWriter;\n\n    protected List<Filter> filters;\n\n    protected volatile ExceptionSorter exceptionSorter;\n\n    protected Driver driver;\n\n    protected volatile int queryTimeout;\n\n    protected volatile int transactionQueryTimeout;\n\n    protected long createTimespan;\n\n    protected volatile int maxWaitThreadCount;\n\n    protected volatile boolean accessToUnderlyingConnectionAllowed;\n\n    protected volatile long timeBetweenEvictionRunsMillis;\n\n    protected volatile int numTestsPerEvictionRun;\n\n    protected volatile long minEvictableIdleTimeMillis;\n\n    protected volatile long maxEvictableIdleTimeMillis;\n\n    protected volatile long keepAliveBetweenTimeMillis;\n\n    protected volatile long phyTimeoutMillis;\n\n    protected volatile long phyMaxUseCount;\n\n    protected volatile boolean removeAbandoned;\n\n    protected volatile long removeAbandonedTimeoutMillis;\n\n    protected volatile boolean logAbandoned;\n\n    protected volatile int maxOpenPreparedStatements;\n\n    protected volatile List<String> connectionInitSqls;\n\n    protected volatile String dbType;\n\n    protected volatile long timeBetweenConnectErrorMillis;\n\n    protected volatile ValidConnectionChecker validConnectionChecker;\n\n    protected long id;\n\n    protected int connectionErrorRetryAttempts;\n\n    protected boolean breakAfterAcquireFailure;\n\n    protected long transactionThresholdMillis;\n\n    protected Date initedTime;\n\n    protected volatile long errorCount;\n\n    protected volatile long dupCloseCount;\n\n    protected volatile long startTransactionCount;\n\n    protected volatile long commitCount;\n\n    protected volatile long rollbackCount;\n\n    protected volatile long cachedPreparedStatementHitCount;\n\n    protected volatile long preparedStatementCount;\n\n    protected volatile long closedPreparedStatementCount;\n\n    protected volatile long cachedPreparedStatementCount;\n\n    protected volatile long cachedPreparedStatementDeleteCount;\n\n    protected volatile long cachedPreparedStatementMissCount;\n\n    protected volatile long executeCount;\n\n    protected volatile long executeQueryCount;\n\n    protected volatile long executeUpdateCount;\n\n    protected volatile long executeBatchCount;\n\n    protected volatile Throwable createError;\n\n    protected volatile Throwable lastError;\n\n    protected volatile long lastErrorTimeMillis;\n\n    protected volatile Throwable lastCreateError;\n\n    protected volatile long lastCreateErrorTimeMillis;\n\n    protected volatile long lastCreateStartTimeMillis;\n\n    protected boolean isOracle;\n\n    protected boolean isMySql;\n\n    protected boolean useOracleImplicitCache;\n\n    protected ReentrantLock lock;\n\n    protected Condition notEmpty;\n\n    protected Condition empty;\n\n    protected ReentrantLock activeConnectionLock;\n\n    protected volatile int createErrorCount;\n\n    protected volatile int creatingCount;\n\n    protected volatile int directCreateCount;\n\n    protected volatile long createCount;\n\n    protected volatile long destroyCount;\n\n    protected volatile long createStartNanos;\n\n    protected long timeBetweenLogStatsMillis;\n\n    protected DruidDataSourceStatLogger statLogger;\n\n    protected int maxCreateTaskCount;\n\n    protected boolean failFast;\n\n    protected volatile int failContinuous;\n\n    protected volatile long failContinuousTimeMillis;\n\n    protected ScheduledExecutorService destroyScheduler;\n\n    protected ScheduledExecutorService createScheduler;\n\n    protected boolean initVariants;\n\n    protected boolean initGlobalVariants;\n\n    protected volatile boolean onFatalError;\n\n    protected volatile int onFatalErrorMaxActive;\n\n    protected volatile int fatalErrorCount;\n\n    protected volatile int fatalErrorCountLastShrink;\n\n    protected volatile long lastFatalErrorTimeMillis;\n\n    protected volatile String lastFatalErrorSql;\n\n    protected volatile Throwable lastFatalError;\n\n    protected volatile long connectionIdSeed;\n\n    protected volatile long statementIdSeed;\n\n    protected volatile long resultSetIdSeed;\n\n    protected volatile long transactionIdSeed;\n\n    protected volatile long metaDataIdSeed;\n\n    private boolean clearFiltersEnable;\n\n    private boolean dupCloseLogEnable;\n\n    private ObjectName objectName;\n\n    private Boolean useUnfairLock;\n\n    private boolean useLocalSessionState;\n\n    private boolean asyncCloseConnectionEnable;\n\n    public DruidAbstractDataSource(boolean lockFair) {\n        this.validationQuery = DEFAULT_VALIDATION_QUERY;\n        this.validationQueryTimeout = -1;\n        this.testOnBorrow = false;\n        this.testOnReturn = false;\n        this.testWhileIdle = true;\n        this.poolPreparedStatements = false;\n        this.sharePreparedStatements = false;\n        this.maxPoolPreparedStatementPerConnectionSize = 10;\n        this.inited = false;\n        this.initExceptionThrow = true;\n        this.logWriter = new PrintWriter(System.out);\n        this.filters = new CopyOnWriteArrayList();\n        this.clearFiltersEnable = true;\n        this.exceptionSorter = null;\n        this.maxWaitThreadCount = -1;\n        this.accessToUnderlyingConnectionAllowed = true;\n        this.timeBetweenEvictionRunsMillis = 60000L;\n        this.numTestsPerEvictionRun = 3;\n        this.minEvictableIdleTimeMillis = 1800000L;\n        this.maxEvictableIdleTimeMillis = 25200000L;\n        this.keepAliveBetweenTimeMillis = 120000L;\n        this.phyTimeoutMillis = -1L;\n        this.phyMaxUseCount = -1L;\n        this.removeAbandonedTimeoutMillis = 300000L;\n        this.maxOpenPreparedStatements = -1;\n        this.timeBetweenConnectErrorMillis = 500L;\n        this.validConnectionChecker = null;\n        this.activeConnections = new IdentityHashMap();\n        this.connectionErrorRetryAttempts = 1;\n        this.breakAfterAcquireFailure = false;\n        this.transactionThresholdMillis = 0L;\n        this.createdTime = new Date();\n        this.errorCount = 0L;\n        this.dupCloseCount = 0L;\n        this.startTransactionCount = 0L;\n        this.commitCount = 0L;\n        this.rollbackCount = 0L;\n        this.cachedPreparedStatementHitCount = 0L;\n        this.preparedStatementCount = 0L;\n        this.closedPreparedStatementCount = 0L;\n        this.cachedPreparedStatementCount = 0L;\n        this.cachedPreparedStatementDeleteCount = 0L;\n        this.cachedPreparedStatementMissCount = 0L;\n        this.transactionHistogram = new Histogram(new long[]{1L, 10L, 100L, 1000L, 10000L, 100000L});\n        this.dupCloseLogEnable = false;\n        this.executeCount = 0L;\n        this.executeQueryCount = 0L;\n        this.executeUpdateCount = 0L;\n        this.executeBatchCount = 0L;\n        this.isOracle = false;\n        this.isMySql = false;\n        this.useOracleImplicitCache = true;\n        this.activeConnectionLock = new ReentrantLock();\n        this.createErrorCount = 0;\n        this.creatingCount = 0;\n        this.directCreateCount = 0;\n        this.createCount = 0L;\n        this.destroyCount = 0L;\n        this.createStartNanos = 0L;\n        this.useUnfairLock = null;\n        this.useLocalSessionState = true;\n        this.statLogger = new DruidDataSourceStatLoggerImpl();\n        this.asyncCloseConnectionEnable = false;\n        this.maxCreateTaskCount = 3;\n        this.failFast = false;\n        this.failContinuous = 0;\n        this.failContinuousTimeMillis = 0L;\n        this.initVariants = false;\n        this.initGlobalVariants = false;\n        this.onFatalError = false;\n        this.onFatalErrorMaxActive = 0;\n        this.fatalErrorCount = 0;\n        this.fatalErrorCountLastShrink = 0;\n        this.lastFatalErrorTimeMillis = 0L;\n        this.lastFatalErrorSql = null;\n        this.lastFatalError = null;\n        this.connectionIdSeed = 10000L;\n        this.statementIdSeed = 20000L;\n        this.resultSetIdSeed = 50000L;\n        this.transactionIdSeed = 60000L;\n        this.metaDataIdSeed = 80000L;\n        this.lock = new ReentrantLock(lockFair);\n        this.notEmpty = this.lock.newCondition();\n        this.empty = this.lock.newCondition();\n    }\n\n    public boolean isUseLocalSessionState() {\n        return this.useLocalSessionState;\n    }\n\n    public void setUseLocalSessionState(boolean useLocalSessionState) {\n        this.useLocalSessionState = useLocalSessionState;\n    }\n\n    public DruidDataSourceStatLogger getStatLogger() {\n        return this.statLogger;\n    }\n\n    public void setStatLogger(DruidDataSourceStatLogger statLogger) {\n        this.statLogger = statLogger;\n    }\n\n    public void setStatLoggerClassName(String className) {\n        try {\n            Class<?> clazz = Class.forName(className);\n            DruidDataSourceStatLogger statLogger = (DruidDataSourceStatLogger) clazz.newInstance();\n            this.setStatLogger(statLogger);\n        } catch (Exception var4) {\n            throw new IllegalArgumentException(className, var4);\n        }\n    }\n\n    public long getTimeBetweenLogStatsMillis() {\n        return this.timeBetweenLogStatsMillis;\n    }\n\n    public void setTimeBetweenLogStatsMillis(long timeBetweenLogStatsMillis) {\n        this.timeBetweenLogStatsMillis = timeBetweenLogStatsMillis;\n    }\n\n    public boolean isOracle() {\n        return this.isOracle;\n    }\n\n    public void setOracle(boolean isOracle) {\n        if (this.inited) {\n            throw new IllegalStateException();\n        } else {\n            this.isOracle = isOracle;\n        }\n    }\n\n    public boolean isUseUnfairLock() {\n        return this.lock.isFair();\n    }\n\n    public void setUseUnfairLock(boolean useUnfairLock) {\n        if (this.lock.isFair() != !useUnfairLock) {\n            if (!this.inited) {\n                ReentrantLock lock = this.lock;\n                lock.lock();\n\n                try {\n                    if (!this.inited) {\n                        this.lock = new ReentrantLock(!useUnfairLock);\n                        this.notEmpty = this.lock.newCondition();\n                        this.empty = this.lock.newCondition();\n                        this.useUnfairLock = useUnfairLock;\n                    }\n                } finally {\n                    lock.unlock();\n                }\n            }\n\n        }\n    }\n\n    public boolean isUseOracleImplicitCache() {\n        return this.useOracleImplicitCache;\n    }\n\n    public void setUseOracleImplicitCache(boolean useOracleImplicitCache) {\n        if (this.useOracleImplicitCache != useOracleImplicitCache) {\n            this.useOracleImplicitCache = useOracleImplicitCache;\n            boolean isOracleDriver10 = this.isOracle() && this.driver != null && this.driver.getMajorVersion() == 10;\n            if (isOracleDriver10 && useOracleImplicitCache) {\n                this.getConnectProperties().setProperty(\"oracle.jdbc.FreeMemoryOnEnterImplicitCache\", \"true\");\n            } else {\n                this.getConnectProperties().remove(\"oracle.jdbc.FreeMemoryOnEnterImplicitCache\");\n            }\n        }\n\n    }\n\n    public Throwable getLastCreateError() {\n        return this.lastCreateError;\n    }\n\n    public Throwable getLastError() {\n        return this.lastError;\n    }\n\n    public long getLastErrorTimeMillis() {\n        return this.lastErrorTimeMillis;\n    }\n\n    public Date getLastErrorTime() {\n        return this.lastErrorTimeMillis <= 0L ? null : new Date(this.lastErrorTimeMillis);\n    }\n\n    public long getLastCreateErrorTimeMillis() {\n        return this.lastCreateErrorTimeMillis;\n    }\n\n    public Date getLastCreateErrorTime() {\n        return this.lastCreateErrorTimeMillis <= 0L ? null : new Date(this.lastCreateErrorTimeMillis);\n    }\n\n    public int getTransactionQueryTimeout() {\n        return this.transactionQueryTimeout <= 0 ? this.queryTimeout : this.transactionQueryTimeout;\n    }\n\n    public void setTransactionQueryTimeout(int transactionQueryTimeout) {\n        this.transactionQueryTimeout = transactionQueryTimeout;\n    }\n\n    public long getExecuteCount() {\n        return this.executeCount + this.executeQueryCount + this.executeUpdateCount + this.executeBatchCount;\n    }\n\n    public long getExecuteUpdateCount() {\n        return this.executeUpdateCount;\n    }\n\n    public long getExecuteQueryCount() {\n        return this.executeQueryCount;\n    }\n\n    public long getExecuteBatchCount() {\n        return this.executeBatchCount;\n    }\n\n    public long getAndResetExecuteCount() {\n        return executeCountUpdater.getAndSet(this, 0L) + executeQueryCountUpdater.getAndSet(this, 0L)\n                + executeUpdateCountUpdater.getAndSet(this, 0L) + executeBatchCountUpdater.getAndSet(this, 0L);\n    }\n\n    public long getExecuteCount2() {\n        return this.executeCount;\n    }\n\n    public void incrementExecuteCount() {\n        executeCountUpdater.incrementAndGet(this);\n    }\n\n    public void incrementExecuteUpdateCount() {\n        ++this.executeUpdateCount;\n    }\n\n    public void incrementExecuteQueryCount() {\n        ++this.executeQueryCount;\n    }\n\n    public void incrementExecuteBatchCount() {\n        ++this.executeBatchCount;\n    }\n\n    public boolean isDupCloseLogEnable() {\n        return this.dupCloseLogEnable;\n    }\n\n    public void setDupCloseLogEnable(boolean dupCloseLogEnable) {\n        this.dupCloseLogEnable = dupCloseLogEnable;\n    }\n\n    public ObjectName getObjectName() {\n        return this.objectName;\n    }\n\n    public void setObjectName(ObjectName objectName) {\n        this.objectName = objectName;\n    }\n\n    public Histogram getTransactionHistogram() {\n        return this.transactionHistogram;\n    }\n\n    public void incrementCachedPreparedStatementCount() {\n        cachedPreparedStatementCountUpdater.incrementAndGet(this);\n    }\n\n    public void decrementCachedPreparedStatementCount() {\n        cachedPreparedStatementCountUpdater.decrementAndGet(this);\n    }\n\n    public void incrementCachedPreparedStatementDeleteCount() {\n        cachedPreparedStatementDeleteCountUpdater.incrementAndGet(this);\n    }\n\n    public void incrementCachedPreparedStatementMissCount() {\n        cachedPreparedStatementMissCountUpdater.incrementAndGet(this);\n    }\n\n    public long getCachedPreparedStatementMissCount() {\n        return this.cachedPreparedStatementMissCount;\n    }\n\n    public long getCachedPreparedStatementAccessCount() {\n        return this.cachedPreparedStatementMissCount + this.cachedPreparedStatementHitCount;\n    }\n\n    public long getCachedPreparedStatementDeleteCount() {\n        return this.cachedPreparedStatementDeleteCount;\n    }\n\n    public long getCachedPreparedStatementCount() {\n        return this.cachedPreparedStatementCount;\n    }\n\n    public void incrementClosedPreparedStatementCount() {\n        closedPreparedStatementCountUpdater.incrementAndGet(this);\n    }\n\n    public long getClosedPreparedStatementCount() {\n        return this.closedPreparedStatementCount;\n    }\n\n    public void incrementPreparedStatementCount() {\n        preparedStatementCountUpdater.incrementAndGet(this);\n    }\n\n    public long getPreparedStatementCount() {\n        return this.preparedStatementCount;\n    }\n\n    public void incrementCachedPreparedStatementHitCount() {\n        cachedPreparedStatementHitCountUpdater.incrementAndGet(this);\n    }\n\n    public long getCachedPreparedStatementHitCount() {\n        return this.cachedPreparedStatementHitCount;\n    }\n\n    public long getTransactionThresholdMillis() {\n        return this.transactionThresholdMillis;\n    }\n\n    public void setTransactionThresholdMillis(long transactionThresholdMillis) {\n        this.transactionThresholdMillis = transactionThresholdMillis;\n    }\n\n    public abstract void logTransaction(TransactionInfo var1);\n\n    public long[] getTransactionHistogramValues() {\n        return this.transactionHistogram.toArray();\n    }\n\n    public long[] getTransactionHistogramRanges() {\n        return this.transactionHistogram.getRanges();\n    }\n\n    public long getCommitCount() {\n        return this.commitCount;\n    }\n\n    public void incrementCommitCount() {\n        commitCountUpdater.incrementAndGet(this);\n    }\n\n    public long getRollbackCount() {\n        return this.rollbackCount;\n    }\n\n    public void incrementRollbackCount() {\n        rollbackCountUpdater.incrementAndGet(this);\n    }\n\n    public long getStartTransactionCount() {\n        return this.startTransactionCount;\n    }\n\n    public void incrementStartTransactionCount() {\n        startTransactionCountUpdater.incrementAndGet(this);\n    }\n\n    public boolean isBreakAfterAcquireFailure() {\n        return this.breakAfterAcquireFailure;\n    }\n\n    public void setBreakAfterAcquireFailure(boolean breakAfterAcquireFailure) {\n        this.breakAfterAcquireFailure = breakAfterAcquireFailure;\n    }\n\n    public int getConnectionErrorRetryAttempts() {\n        return this.connectionErrorRetryAttempts;\n    }\n\n    public void setConnectionErrorRetryAttempts(int connectionErrorRetryAttempts) {\n        this.connectionErrorRetryAttempts = connectionErrorRetryAttempts;\n    }\n\n    public long getDupCloseCount() {\n        return this.dupCloseCount;\n    }\n\n    public int getMaxPoolPreparedStatementPerConnectionSize() {\n        return this.maxPoolPreparedStatementPerConnectionSize;\n    }\n\n    public void setMaxPoolPreparedStatementPerConnectionSize(int maxPoolPreparedStatementPerConnectionSize) {\n        if (maxPoolPreparedStatementPerConnectionSize > 0) {\n            this.poolPreparedStatements = true;\n        } else {\n            this.poolPreparedStatements = false;\n        }\n\n        this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;\n    }\n\n    public boolean isSharePreparedStatements() {\n        return this.sharePreparedStatements;\n    }\n\n    public void setSharePreparedStatements(boolean sharePreparedStatements) {\n        this.sharePreparedStatements = sharePreparedStatements;\n    }\n\n    public void incrementDupCloseCount() {\n        dupCloseCountUpdater.incrementAndGet(this);\n    }\n\n    public ValidConnectionChecker getValidConnectionChecker() {\n        return this.validConnectionChecker;\n    }\n\n    public void setValidConnectionChecker(ValidConnectionChecker validConnectionChecker) {\n        this.validConnectionChecker = validConnectionChecker;\n    }\n\n    public String getValidConnectionCheckerClassName() {\n        return this.validConnectionChecker == null ? null : this.validConnectionChecker.getClass().getName();\n    }\n\n    public void setValidConnectionCheckerClassName(String validConnectionCheckerClass) throws Exception {\n        Class<?> clazz = Utils.loadClass(validConnectionCheckerClass);\n        ValidConnectionChecker validConnectionChecker = null;\n        if (clazz != null) {\n            validConnectionChecker = (ValidConnectionChecker) clazz.newInstance();\n            this.validConnectionChecker = validConnectionChecker;\n        } else {\n            LOG.error(\"load validConnectionCheckerClass error : \" + validConnectionCheckerClass);\n        }\n\n    }\n\n    public String getDbType() {\n        return this.dbType;\n    }\n\n    public void setDbType(String dbType) {\n        this.dbType = dbType;\n    }\n\n    public void addConnectionProperty(String name, String value) {\n        if (!StringUtils.equals(this.connectProperties.getProperty(name), value)) {\n            if (this.inited) {\n                throw new UnsupportedOperationException();\n            } else {\n                this.connectProperties.put(name, value);\n            }\n        }\n    }\n\n    public Collection<String> getConnectionInitSqls() {\n        Collection<String> result = this.connectionInitSqls;\n        return result == null ? Collections.emptyList() : result;\n    }\n\n    public void setConnectionInitSqls(Collection<? extends Object> connectionInitSqls) {\n        if (connectionInitSqls != null && connectionInitSqls.size() > 0) {\n            ArrayList<String> newVal = null;\n            Iterator var3 = connectionInitSqls.iterator();\n\n            while (var3.hasNext()) {\n                Object o = var3.next();\n                if (o != null) {\n                    String s = o.toString();\n                    s = s.trim();\n                    if (s.length() != 0) {\n                        if (newVal == null) {\n                            newVal = new ArrayList();\n                        }\n\n                        newVal.add(s);\n                    }\n                }\n            }\n\n            this.connectionInitSqls = newVal;\n        } else {\n            this.connectionInitSqls = null;\n        }\n\n    }\n\n    public long getTimeBetweenConnectErrorMillis() {\n        return this.timeBetweenConnectErrorMillis;\n    }\n\n    public void setTimeBetweenConnectErrorMillis(long timeBetweenConnectErrorMillis) {\n        this.timeBetweenConnectErrorMillis = timeBetweenConnectErrorMillis;\n    }\n\n    public int getMaxOpenPreparedStatements() {\n        return this.maxPoolPreparedStatementPerConnectionSize;\n    }\n\n    public void setMaxOpenPreparedStatements(int maxOpenPreparedStatements) {\n        this.setMaxPoolPreparedStatementPerConnectionSize(maxOpenPreparedStatements);\n    }\n\n    public boolean isLogAbandoned() {\n        return this.logAbandoned;\n    }\n\n    public void setLogAbandoned(boolean logAbandoned) {\n        this.logAbandoned = logAbandoned;\n    }\n\n    public int getRemoveAbandonedTimeout() {\n        return (int) (this.removeAbandonedTimeoutMillis / 1000L);\n    }\n\n    public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {\n        this.removeAbandonedTimeoutMillis = (long) removeAbandonedTimeout * 1000L;\n    }\n\n    public long getRemoveAbandonedTimeoutMillis() {\n        return this.removeAbandonedTimeoutMillis;\n    }\n\n    public void setRemoveAbandonedTimeoutMillis(long removeAbandonedTimeoutMillis) {\n        this.removeAbandonedTimeoutMillis = removeAbandonedTimeoutMillis;\n    }\n\n    public boolean isRemoveAbandoned() {\n        return this.removeAbandoned;\n    }\n\n    public void setRemoveAbandoned(boolean removeAbandoned) {\n        this.removeAbandoned = removeAbandoned;\n    }\n\n    public long getMinEvictableIdleTimeMillis() {\n        return this.minEvictableIdleTimeMillis;\n    }\n\n    public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {\n        if (minEvictableIdleTimeMillis < 30000L) {\n            LOG.error(\"minEvictableIdleTimeMillis should be greater than 30000\");\n        }\n\n        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;\n    }\n\n    public long getKeepAliveBetweenTimeMillis() {\n        return this.keepAliveBetweenTimeMillis;\n    }\n\n    public void setKeepAliveBetweenTimeMillis(long keepAliveBetweenTimeMillis) {\n        if (keepAliveBetweenTimeMillis < 30000L) {\n            LOG.error(\"keepAliveBetweenTimeMillis should be greater than 30000\");\n        }\n\n        this.keepAliveBetweenTimeMillis = keepAliveBetweenTimeMillis;\n    }\n\n    public long getMaxEvictableIdleTimeMillis() {\n        return this.maxEvictableIdleTimeMillis;\n    }\n\n    public void setMaxEvictableIdleTimeMillis(long maxEvictableIdleTimeMillis) {\n        if (maxEvictableIdleTimeMillis < 30000L) {\n            LOG.error(\"maxEvictableIdleTimeMillis should be greater than 30000\");\n        }\n\n        if (maxEvictableIdleTimeMillis < this.minEvictableIdleTimeMillis) {\n            throw new IllegalArgumentException(\n                    \"maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis\");\n        } else {\n            this.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis;\n        }\n    }\n\n    public long getPhyTimeoutMillis() {\n        return this.phyTimeoutMillis;\n    }\n\n    public void setPhyTimeoutMillis(long phyTimeoutMillis) {\n        this.phyTimeoutMillis = phyTimeoutMillis;\n    }\n\n    public long getPhyMaxUseCount() {\n        return this.phyMaxUseCount;\n    }\n\n    public void setPhyMaxUseCount(long phyMaxUseCount) {\n        this.phyMaxUseCount = phyMaxUseCount;\n    }\n\n    public int getNumTestsPerEvictionRun() {\n        return this.numTestsPerEvictionRun;\n    }\n\n    /**\n     * @deprecated\n     */\n    @Deprecated\n    public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {\n        this.numTestsPerEvictionRun = numTestsPerEvictionRun;\n    }\n\n    public long getTimeBetweenEvictionRunsMillis() {\n        return this.timeBetweenEvictionRunsMillis;\n    }\n\n    public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {\n        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;\n    }\n\n    public int getMaxWaitThreadCount() {\n        return this.maxWaitThreadCount;\n    }\n\n    public void setMaxWaitThreadCount(int maxWaithThreadCount) {\n        this.maxWaitThreadCount = maxWaithThreadCount;\n    }\n\n    public String getValidationQuery() {\n        return this.validationQuery;\n    }\n\n    public void setValidationQuery(String validationQuery) {\n        this.validationQuery = validationQuery;\n    }\n\n    public int getValidationQueryTimeout() {\n        return this.validationQueryTimeout;\n    }\n\n    public void setValidationQueryTimeout(int validationQueryTimeout) {\n        if (validationQueryTimeout < 0 && \"sqlserver\".equals(this.dbType)) {\n            LOG.error(\"validationQueryTimeout should be >= 0\");\n        }\n\n        this.validationQueryTimeout = validationQueryTimeout;\n    }\n\n    public boolean isAccessToUnderlyingConnectionAllowed() {\n        return this.accessToUnderlyingConnectionAllowed;\n    }\n\n    public void setAccessToUnderlyingConnectionAllowed(boolean accessToUnderlyingConnectionAllowed) {\n        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;\n    }\n\n    public boolean isTestOnBorrow() {\n        return this.testOnBorrow;\n    }\n\n    public void setTestOnBorrow(boolean testOnBorrow) {\n        this.testOnBorrow = testOnBorrow;\n    }\n\n    public boolean isTestOnReturn() {\n        return this.testOnReturn;\n    }\n\n    public void setTestOnReturn(boolean testOnReturn) {\n        this.testOnReturn = testOnReturn;\n    }\n\n    public boolean isTestWhileIdle() {\n        return this.testWhileIdle;\n    }\n\n    public void setTestWhileIdle(boolean testWhileIdle) {\n        this.testWhileIdle = testWhileIdle;\n    }\n\n    public boolean isDefaultAutoCommit() {\n        return this.defaultAutoCommit;\n    }\n\n    public void setDefaultAutoCommit(boolean defaultAutoCommit) {\n        this.defaultAutoCommit = defaultAutoCommit;\n    }\n\n    public Boolean getDefaultReadOnly() {\n        return this.defaultReadOnly;\n    }\n\n    public void setDefaultReadOnly(Boolean defaultReadOnly) {\n        this.defaultReadOnly = defaultReadOnly;\n    }\n\n    public Integer getDefaultTransactionIsolation() {\n        return this.defaultTransactionIsolation;\n    }\n\n    public void setDefaultTransactionIsolation(Integer defaultTransactionIsolation) {\n        this.defaultTransactionIsolation = defaultTransactionIsolation;\n    }\n\n    public String getDefaultCatalog() {\n        return this.defaultCatalog;\n    }\n\n    public void setDefaultCatalog(String defaultCatalog) {\n        this.defaultCatalog = defaultCatalog;\n    }\n\n    public PasswordCallback getPasswordCallback() {\n        return this.passwordCallback;\n    }\n\n    public void setPasswordCallback(PasswordCallback passwordCallback) {\n        this.passwordCallback = passwordCallback;\n    }\n\n    public void setPasswordCallbackClassName(String passwordCallbackClassName) throws Exception {\n        Class<?> clazz = Utils.loadClass(passwordCallbackClassName);\n        if (clazz != null) {\n            this.passwordCallback = (PasswordCallback) clazz.newInstance();\n        } else {\n            LOG.error(\"load passwordCallback error : \" + passwordCallbackClassName);\n            this.passwordCallback = null;\n        }\n\n    }\n\n    public NameCallback getUserCallback() {\n        return this.userCallback;\n    }\n\n    public void setUserCallback(NameCallback userCallback) {\n        this.userCallback = userCallback;\n    }\n\n    public boolean isInitVariants() {\n        return this.initVariants;\n    }\n\n    public void setInitVariants(boolean initVariants) {\n        this.initVariants = initVariants;\n    }\n\n    public boolean isInitGlobalVariants() {\n        return this.initGlobalVariants;\n    }\n\n    public void setInitGlobalVariants(boolean initGlobalVariants) {\n        this.initGlobalVariants = initGlobalVariants;\n    }\n\n    public int getQueryTimeout() {\n        return this.queryTimeout;\n    }\n\n    public void setQueryTimeout(int seconds) {\n        this.queryTimeout = seconds;\n    }\n\n    public String getName() {\n        return this.name != null ? this.name : \"DataSource-\" + System.identityHashCode(this);\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public boolean isPoolPreparedStatements() {\n        return this.poolPreparedStatements;\n    }\n\n    public abstract void setPoolPreparedStatements(boolean var1);\n\n    public long getMaxWait() {\n        return this.maxWait;\n    }\n\n    public void setMaxWait(long maxWaitMillis) {\n        if (maxWaitMillis != this.maxWait) {\n            if (maxWaitMillis > 0L && this.useUnfairLock == null && !this.inited) {\n                ReentrantLock lock = this.lock;\n                lock.lock();\n\n                try {\n                    if (!this.inited && !lock.isFair()) {\n                        this.lock = new ReentrantLock(true);\n                        this.notEmpty = this.lock.newCondition();\n                        this.empty = this.lock.newCondition();\n                    }\n                } finally {\n                    lock.unlock();\n                }\n            }\n\n            if (this.inited) {\n                LOG.error(\"maxWait changed : \" + this.maxWait + \" -> \" + maxWaitMillis);\n            }\n\n            this.maxWait = maxWaitMillis;\n        }\n    }\n\n    public int getNotFullTimeoutRetryCount() {\n        return this.notFullTimeoutRetryCount;\n    }\n\n    public void setNotFullTimeoutRetryCount(int notFullTimeoutRetryCount) {\n        this.notFullTimeoutRetryCount = notFullTimeoutRetryCount;\n    }\n\n    public int getMinIdle() {\n        return this.minIdle;\n    }\n\n    public void setMinIdle(int value) {\n        if (value != this.minIdle) {\n            if (this.inited && value > this.maxActive) {\n                throw new IllegalArgumentException(\n                        \"minIdle greater than maxActive, \" + this.maxActive + \" < \" + this.minIdle);\n            } else if (this.minIdle < 0) {\n                throw new IllegalArgumentException(\"minIdle must > 0\");\n            } else {\n                this.minIdle = value;\n            }\n        }\n    }\n\n    public int getMaxIdle() {\n        return this.maxIdle;\n    }\n\n    /**\n     * @deprecated\n     */\n    @Deprecated\n    public void setMaxIdle(int maxIdle) {\n        LOG.error(\"maxIdle is deprecated\");\n        this.maxIdle = maxIdle;\n    }\n\n    public int getInitialSize() {\n        return this.initialSize;\n    }\n\n    public void setInitialSize(int initialSize) {\n        if (this.initialSize != initialSize) {\n            if (this.inited) {\n                throw new UnsupportedOperationException();\n            } else {\n                this.initialSize = initialSize;\n            }\n        }\n    }\n\n    public long getCreateErrorCount() {\n        return (long) this.createErrorCount;\n    }\n\n    public int getMaxActive() {\n        return this.maxActive;\n    }\n\n    public abstract void setMaxActive(int var1);\n\n    public String getUsername() {\n        return this.username;\n    }\n\n    public void setUsername(String username) {\n        if (!StringUtils.equals(this.username, username)) {\n            // if (this.inited) {\n            // throw new UnsupportedOperationException();\n            // } else {\n            this.username = username;\n            // }\n        }\n    }\n\n    public String getPassword() {\n        return this.password;\n    }\n\n    public void setPassword(String password) {\n        if (!StringUtils.equals(this.password, password)) {\n            if (this.inited) {\n                LOG.info(\"password changed\");\n            }\n\n            this.password = password;\n        }\n    }\n\n    public Properties getConnectProperties() {\n        return this.connectProperties;\n    }\n\n    public abstract void setConnectProperties(Properties var1);\n\n    public void setConnectionProperties(String connectionProperties) {\n        if (connectionProperties != null && connectionProperties.trim().length() != 0) {\n            String[] entries = connectionProperties.split(\";\");\n            Properties properties = new Properties();\n\n            for (int i = 0; i < entries.length; ++i) {\n                String entry = entries[i];\n                if (entry.length() > 0) {\n                    int index = entry.indexOf(61);\n                    if (index > 0) {\n                        String name = entry.substring(0, index);\n                        String value = entry.substring(index + 1);\n                        properties.setProperty(name, value);\n                    } else {\n                        properties.setProperty(entry, \"\");\n                    }\n                }\n            }\n\n            this.setConnectProperties(properties);\n        } else {\n            this.setConnectProperties((Properties) null);\n        }\n    }\n\n    public String getUrl() {\n        return this.jdbcUrl;\n    }\n\n    public void setUrl(String jdbcUrl) {\n        if (!StringUtils.equals(this.jdbcUrl, jdbcUrl)) {\n            // if (this.inited) {\n            // throw new UnsupportedOperationException();\n            // } else {\n            if (jdbcUrl != null) {\n                jdbcUrl = jdbcUrl.trim();\n            }\n\n            this.jdbcUrl = jdbcUrl;\n            // }\n        }\n    }\n\n    public String getRawJdbcUrl() {\n        return this.jdbcUrl;\n    }\n\n    public String getDriverClassName() {\n        return this.driverClass;\n    }\n\n    public void setDriverClassName(String driverClass) {\n        if (driverClass != null && driverClass.length() > 256) {\n            throw new IllegalArgumentException(\"driverClassName length > 256.\");\n        } else {\n            if (\"oracle.jdbc.driver.OracleDriver\".equalsIgnoreCase(driverClass)) {\n                driverClass = \"oracle.jdbc.OracleDriver\";\n                LOG.warn(\"oracle.jdbc.driver.OracleDriver is deprecated.Having use oracle.jdbc.OracleDriver.\");\n            }\n\n            if (this.inited) {\n                if (!StringUtils.equals(this.driverClass, driverClass)) {\n                    throw new UnsupportedOperationException();\n                }\n            } else {\n                this.driverClass = driverClass;\n            }\n        }\n    }\n\n    public ClassLoader getDriverClassLoader() {\n        return this.driverClassLoader;\n    }\n\n    public void setDriverClassLoader(ClassLoader driverClassLoader) {\n        this.driverClassLoader = driverClassLoader;\n    }\n\n    public PrintWriter getLogWriter() {\n        return this.logWriter;\n    }\n\n    public void setLogWriter(PrintWriter out) throws SQLException {\n        this.logWriter = out;\n    }\n\n    public int getLoginTimeout() {\n        return DriverManager.getLoginTimeout();\n    }\n\n    public void setLoginTimeout(int seconds) {\n        DriverManager.setLoginTimeout(seconds);\n    }\n\n    public Driver getDriver() {\n        return this.driver;\n    }\n\n    public void setDriver(Driver driver) {\n        this.driver = driver;\n    }\n\n    public int getDriverMajorVersion() {\n        return this.driver == null ? -1 : this.driver.getMajorVersion();\n    }\n\n    public int getDriverMinorVersion() {\n        return this.driver == null ? -1 : this.driver.getMinorVersion();\n    }\n\n    public ExceptionSorter getExceptionSorter() {\n        return this.exceptionSorter;\n    }\n\n    public void setExceptionSorter(ExceptionSorter exceptionSoter) {\n        this.exceptionSorter = exceptionSoter;\n    }\n\n    public void setExceptionSorter(String exceptionSorter) throws SQLException {\n        if (exceptionSorter == null) {\n            this.exceptionSorter = NullExceptionSorter.getInstance();\n        } else {\n            exceptionSorter = exceptionSorter.trim();\n            if (exceptionSorter.length() == 0) {\n                this.exceptionSorter = NullExceptionSorter.getInstance();\n            } else {\n                Class<?> clazz = Utils.loadClass(exceptionSorter);\n                if (clazz == null) {\n                    LOG.error(\"load exceptionSorter error : \" + exceptionSorter);\n                } else {\n                    try {\n                        this.exceptionSorter = (ExceptionSorter) clazz.newInstance();\n                    } catch (Exception var4) {\n                        throw new SQLException(\"create exceptionSorter error\", var4);\n                    }\n                }\n\n            }\n        }\n    }\n\n    public String getExceptionSorterClassName() {\n        return this.exceptionSorter == null ? null : this.exceptionSorter.getClass().getName();\n    }\n\n    public void setExceptionSorterClassName(String exceptionSorter) throws Exception {\n        this.setExceptionSorter(exceptionSorter);\n    }\n\n    public List<Filter> getProxyFilters() {\n        return this.filters;\n    }\n\n    public void setProxyFilters(List<Filter> filters) {\n        if (filters != null) {\n            this.filters.addAll(filters);\n        }\n\n    }\n\n    public String[] getFilterClasses() {\n        List<Filter> filterConfigList = this.getProxyFilters();\n        List<String> classes = new ArrayList();\n        Iterator var3 = filterConfigList.iterator();\n\n        while (var3.hasNext()) {\n            Filter filter = (Filter) var3.next();\n            classes.add(filter.getClass().getName());\n        }\n\n        return (String[]) classes.toArray(new String[classes.size()]);\n    }\n\n    public void setFilters(String filters) throws SQLException {\n        if (filters != null && filters.startsWith(\"!\")) {\n            filters = filters.substring(1);\n            this.clearFilters();\n        }\n\n        this.addFilters(filters);\n    }\n\n    public void addFilters(String filters) throws SQLException {\n        if (filters != null && filters.length() != 0) {\n            String[] filterArray = filters.split(\"\\\\,\");\n            String[] var3 = filterArray;\n            int var4 = filterArray.length;\n\n            for (int var5 = 0; var5 < var4; ++var5) {\n                String item = var3[var5];\n                FilterManager.loadFilter(this.filters, item.trim());\n            }\n\n        }\n    }\n\n    public void clearFilters() {\n        if (this.isClearFiltersEnable()) {\n            this.filters.clear();\n        }\n    }\n\n    public void validateConnection(Connection conn) throws SQLException {\n        String query = this.getValidationQuery();\n        if (conn.isClosed()) {\n            throw new SQLException(\"validateConnection: connection closed\");\n        } else if (this.validConnectionChecker != null) {\n            boolean result = true;\n            Exception error = null;\n\n            try {\n                result = this.validConnectionChecker.isValidConnection(conn, this.validationQuery,\n                        this.validationQueryTimeout);\n                if (result && this.onFatalError) {\n                    this.lock.lock();\n\n                    try {\n                        if (this.onFatalError) {\n                            this.onFatalError = false;\n                        }\n                    } finally {\n                        this.lock.unlock();\n                    }\n                }\n            } catch (SQLException var24) {\n                throw var24;\n            } catch (Exception var25) {\n                error = var25;\n            }\n\n            if (!result) {\n                SQLException sqlError = error != null ? new SQLException(\"validateConnection false\", error)\n                        : new SQLException(\"validateConnection false\");\n                throw sqlError;\n            }\n        } else {\n            if (null != query) {\n                Statement stmt = null;\n                ResultSet rs = null;\n\n                try {\n                    stmt = conn.createStatement();\n                    if (this.getValidationQueryTimeout() > 0) {\n                        stmt.setQueryTimeout(this.getValidationQueryTimeout());\n                    }\n\n                    rs = stmt.executeQuery(query);\n                    if (!rs.next()) {\n                        throw new SQLException(\"validationQuery didn't return a row\");\n                    }\n\n                    if (this.onFatalError) {\n                        this.lock.lock();\n\n                        try {\n                            if (this.onFatalError) {\n                                this.onFatalError = false;\n                            }\n                        } finally {\n                            this.lock.unlock();\n                        }\n                    }\n                } finally {\n                    JdbcUtils.close(rs);\n                    JdbcUtils.close(stmt);\n                }\n            }\n\n        }\n    }\n\n    /**\n     * @deprecated\n     */\n    protected boolean testConnectionInternal(Connection conn) {\n        return this.testConnectionInternal((DruidConnectionHolder) null, conn);\n    }\n\n    protected boolean testConnectionInternal(DruidConnectionHolder holder, Connection conn) {\n        String sqlFile = JdbcSqlStat.getContextSqlFile();\n        String sqlName = JdbcSqlStat.getContextSqlName();\n        if (sqlFile != null) {\n            JdbcSqlStat.setContextSqlFile((String) null);\n        }\n\n        if (sqlName != null) {\n            JdbcSqlStat.setContextSqlName((String) null);\n        }\n\n        try {\n            boolean valid;\n            if (this.validConnectionChecker == null) {\n                if (conn.isClosed()) {\n                    valid = false;\n                    return valid;\n                } else if (null == this.validationQuery) {\n                    valid = true;\n                    return valid;\n                } else {\n                    Statement stmt = null;\n                    ResultSet rset = null;\n\n                    boolean var7;\n                    try {\n                        stmt = conn.createStatement();\n                        if (this.getValidationQueryTimeout() > 0) {\n                            stmt.setQueryTimeout(this.validationQueryTimeout);\n                        }\n\n                        rset = stmt.executeQuery(this.validationQuery);\n                        if (!rset.next()) {\n                            var7 = false;\n                            return var7;\n                        }\n                    } finally {\n                        JdbcUtils.close(rset);\n                        JdbcUtils.close(stmt);\n                    }\n\n                    if (this.onFatalError) {\n                        this.lock.lock();\n\n                        try {\n                            if (this.onFatalError) {\n                                this.onFatalError = false;\n                            }\n                        } finally {\n                            this.lock.unlock();\n                        }\n                    }\n\n                    var7 = true;\n                    return var7;\n                }\n            } else {\n                valid = this.validConnectionChecker.isValidConnection(conn, this.validationQuery,\n                        this.validationQueryTimeout);\n                long currentTimeMillis = System.currentTimeMillis();\n                if (holder != null) {\n                    holder.lastValidTimeMillis = currentTimeMillis;\n                }\n\n                if (valid && this.isMySql) {\n                    long lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn);\n                    if (lastPacketReceivedTimeMs > 0L) {\n                        long mysqlIdleMillis = currentTimeMillis - lastPacketReceivedTimeMs;\n                        if (lastPacketReceivedTimeMs > 0L && mysqlIdleMillis >= this.timeBetweenEvictionRunsMillis) {\n                            this.discardConnection(conn);\n                            String errorMsg = \"discard long time none received connection. , jdbcUrl : \" + this.jdbcUrl\n                                    + \", jdbcUrl : \" + this.jdbcUrl + \", lastPacketReceivedIdleMillis : \"\n                                    + mysqlIdleMillis;\n                            LOG.error(errorMsg);\n                            boolean var13 = false;\n                            return var13;\n                        }\n                    }\n                }\n\n                if (valid && this.onFatalError) {\n                    this.lock.lock();\n\n                    try {\n                        if (this.onFatalError) {\n                            this.onFatalError = false;\n                        }\n                    } finally {\n                        this.lock.unlock();\n                    }\n                }\n\n                boolean var46 = valid;\n                return var46;\n            }\n        } catch (Throwable var41) {\n            boolean var6 = false;\n            return var6;\n        } finally {\n            if (sqlFile != null) {\n                JdbcSqlStat.setContextSqlFile(sqlFile);\n            }\n\n            if (sqlName != null) {\n                JdbcSqlStat.setContextSqlName(sqlName);\n            }\n\n        }\n    }\n\n    public Set<DruidPooledConnection> getActiveConnections() {\n        this.activeConnectionLock.lock();\n\n        HashSet var1;\n        try {\n            var1 = new HashSet(this.activeConnections.keySet());\n        } finally {\n            this.activeConnectionLock.unlock();\n        }\n\n        return var1;\n    }\n\n    public List<String> getActiveConnectionStackTrace() {\n        List<String> list = new ArrayList();\n        Iterator var2 = this.getActiveConnections().iterator();\n\n        while (var2.hasNext()) {\n            DruidPooledConnection conn = (DruidPooledConnection) var2.next();\n            list.add(Utils.toString(conn.getConnectStackTrace()));\n        }\n\n        return list;\n    }\n\n    public long getCreateTimespanNano() {\n        return this.createTimespan;\n    }\n\n    public long getCreateTimespanMillis() {\n        return this.createTimespan / 1000000L;\n    }\n\n    public Driver getRawDriver() {\n        return this.driver;\n    }\n\n    public boolean isClearFiltersEnable() {\n        return this.clearFiltersEnable;\n    }\n\n    public void setClearFiltersEnable(boolean clearFiltersEnable) {\n        this.clearFiltersEnable = clearFiltersEnable;\n    }\n\n    public long createConnectionId() {\n        return connectionIdSeedUpdater.incrementAndGet(this);\n    }\n\n    public long createStatementId() {\n        return statementIdSeedUpdater.getAndIncrement(this);\n    }\n\n    public long createMetaDataId() {\n        return metaDataIdSeedUpdater.getAndIncrement(this);\n    }\n\n    public long createResultSetId() {\n        return resultSetIdSeedUpdater.getAndIncrement(this);\n    }\n\n    public long createTransactionId() {\n        return transactionIdSeedUpdater.getAndIncrement(this);\n    }\n\n    void initStatement(DruidPooledConnection conn, Statement stmt) throws SQLException {\n        boolean transaction = !conn.getConnectionHolder().underlyingAutoCommit;\n        int queryTimeout = transaction ? this.getTransactionQueryTimeout() : this.getQueryTimeout();\n        if (queryTimeout > 0) {\n            stmt.setQueryTimeout(queryTimeout);\n        }\n\n    }\n\n    public void handleConnectionException(DruidPooledConnection conn, Throwable t) throws SQLException {\n        this.handleConnectionException(conn, t, (String) null);\n    }\n\n    public abstract void handleConnectionException(DruidPooledConnection var1, Throwable var2, String var3)\n            throws SQLException;\n\n    protected abstract void recycle(DruidPooledConnection var1) throws SQLException;\n\n    public Connection createPhysicalConnection(String url, Properties info) throws SQLException {\n        Object conn;\n        if (this.getProxyFilters().size() == 0) {\n            conn = this.getDriver().connect(url, info);\n        } else {\n            conn = (new FilterChainImpl(this)).connection_connect(info);\n        }\n\n        createCountUpdater.incrementAndGet(this);\n        return (Connection) conn;\n    }\n\n    public DruidAbstractDataSource.PhysicalConnectionInfo createPhysicalConnection() throws SQLException {\n        String url = this.getUrl();\n        Properties connectProperties = this.getConnectProperties();\n        String user;\n        if (this.getUserCallback() != null) {\n            user = this.getUserCallback().getName();\n        } else {\n            user = this.getUsername();\n        }\n\n        String password = this.getPassword();\n        PasswordCallback passwordCallback = this.getPasswordCallback();\n        if (passwordCallback != null) {\n            if (passwordCallback instanceof DruidPasswordCallback) {\n                DruidPasswordCallback druidPasswordCallback = (DruidPasswordCallback) passwordCallback;\n                druidPasswordCallback.setUrl(url);\n                druidPasswordCallback.setProperties(connectProperties);\n            }\n\n            char[] chars = passwordCallback.getPassword();\n            if (chars != null) {\n                password = new String(chars);\n            }\n        }\n\n        Properties physicalConnectProperties = new Properties();\n        if (connectProperties != null) {\n            physicalConnectProperties.putAll(connectProperties);\n        }\n\n        if (user != null && user.length() != 0) {\n            physicalConnectProperties.put(\"user\", user);\n        }\n\n        if (password != null && password.length() != 0) {\n            physicalConnectProperties.put(\"password\", password);\n        }\n\n        Connection conn = null;\n        long connectStartNanos = System.nanoTime();\n        Map<String, Object> variables = this.initVariants ? new HashMap() : null;\n        Map<String, Object> globalVariables = this.initGlobalVariants ? new HashMap() : null;\n        createStartNanosUpdater.set(this, connectStartNanos);\n        creatingCountUpdater.incrementAndGet(this);\n        boolean var27 = false;\n\n        long connectedNanos;\n        long initedNanos;\n        long validatedNanos;\n        try {\n            var27 = true;\n            conn = this.createPhysicalConnection(url, physicalConnectProperties);\n            connectedNanos = System.nanoTime();\n            if (conn == null) {\n                throw new SQLException(\"connect error, url \" + url + \", driverClass \" + this.driverClass);\n            }\n\n            this.initPhysicalConnection(conn, variables, globalVariables);\n            initedNanos = System.nanoTime();\n            this.validateConnection(conn);\n            validatedNanos = System.nanoTime();\n            this.setFailContinuous(false);\n            this.setCreateError((Throwable) null);\n            var27 = false;\n        } catch (SQLException var28) {\n            this.setCreateError(var28);\n            JdbcUtils.close(conn);\n            throw var28;\n        } catch (RuntimeException var29) {\n            this.setCreateError(var29);\n            JdbcUtils.close(conn);\n            throw var29;\n        } catch (Error var30) {\n            createErrorCountUpdater.incrementAndGet(this);\n            this.setCreateError(var30);\n            JdbcUtils.close(conn);\n            throw var30;\n        } finally {\n            if (var27) {\n                long nano = System.nanoTime() - connectStartNanos;\n                this.createTimespan += nano;\n                creatingCountUpdater.decrementAndGet(this);\n            }\n        }\n\n        long nano = System.nanoTime() - connectStartNanos;\n        this.createTimespan += nano;\n        creatingCountUpdater.decrementAndGet(this);\n        return new DruidAbstractDataSource.PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos,\n                validatedNanos, variables, globalVariables);\n    }\n\n    protected void setCreateError(Throwable ex) {\n        if (ex == null) {\n            this.lock.lock();\n\n            try {\n                if (this.createError != null) {\n                    this.createError = null;\n                }\n            } finally {\n                this.lock.unlock();\n            }\n\n        } else {\n            createErrorCountUpdater.incrementAndGet(this);\n            long now = System.currentTimeMillis();\n            this.lock.lock();\n\n            try {\n                this.createError = ex;\n                this.lastCreateError = ex;\n                this.lastCreateErrorTimeMillis = now;\n            } finally {\n                this.lock.unlock();\n            }\n\n        }\n    }\n\n    public boolean isFailContinuous() {\n        return failContinuousUpdater.get(this) == 1;\n    }\n\n    protected void setFailContinuous(boolean fail) {\n        if (fail) {\n            failContinuousTimeMillisUpdater.set(this, System.currentTimeMillis());\n        } else {\n            failContinuousTimeMillisUpdater.set(this, 0L);\n        }\n\n        boolean currentState = failContinuousUpdater.get(this) == 1;\n        if (currentState != fail) {\n            if (fail) {\n                failContinuousUpdater.set(this, 1);\n                if (LOG.isInfoEnabled()) {\n                    LOG.info(\"{dataSource-\" + this.getID() + \"} failContinuous is true\");\n                }\n            } else {\n                failContinuousUpdater.set(this, 0);\n                if (LOG.isInfoEnabled()) {\n                    LOG.info(\"{dataSource-\" + this.getID() + \"} failContinuous is false\");\n                }\n            }\n\n        }\n    }\n\n    public void initPhysicalConnection(Connection conn) throws SQLException {\n        this.initPhysicalConnection(conn, (Map) null, (Map) null);\n    }\n\n    public void initPhysicalConnection(Connection conn, Map<String, Object> variables,\n                                       Map<String, Object> globalVariables) throws SQLException {\n        if (conn.getAutoCommit() != this.defaultAutoCommit) {\n            conn.setAutoCommit(this.defaultAutoCommit);\n        }\n\n        if (this.defaultReadOnly != null && conn.isReadOnly() != this.defaultReadOnly) {\n            conn.setReadOnly(this.defaultReadOnly);\n        }\n\n        if (this.getDefaultTransactionIsolation() != null\n                && conn.getTransactionIsolation() != this.getDefaultTransactionIsolation()) {\n            conn.setTransactionIsolation(this.getDefaultTransactionIsolation());\n        }\n\n        if (this.getDefaultCatalog() != null && this.getDefaultCatalog().length() != 0) {\n            conn.setCatalog(this.getDefaultCatalog());\n        }\n\n        Collection<String> initSqls = this.getConnectionInitSqls();\n        if (initSqls.size() != 0 || variables != null || globalVariables != null) {\n            Statement stmt = null;\n\n            try {\n                stmt = conn.createStatement();\n                Iterator var6 = initSqls.iterator();\n\n                String name;\n                while (var6.hasNext()) {\n                    name = (String) var6.next();\n                    if (name != null) {\n                        stmt.execute(name);\n                    }\n                }\n\n                if (\"mysql\".equals(this.dbType) || \"aliyun_ads\".equals(this.dbType)) {\n                    ResultSet rs;\n                    Object value;\n                    if (variables != null) {\n                        rs = null;\n\n                        try {\n                            rs = stmt.executeQuery(\"show variables\");\n\n                            while (rs.next()) {\n                                name = rs.getString(1);\n                                value = rs.getObject(2);\n                                variables.put(name, value);\n                            }\n                        } finally {\n                            JdbcUtils.close(rs);\n                        }\n                    }\n\n                    if (globalVariables != null) {\n                        rs = null;\n\n                        try {\n                            rs = stmt.executeQuery(\"show global variables\");\n\n                            while (rs.next()) {\n                                name = rs.getString(1);\n                                value = rs.getObject(2);\n                                globalVariables.put(name, value);\n                            }\n                        } finally {\n                            JdbcUtils.close(rs);\n                        }\n                    }\n                }\n            } finally {\n                JdbcUtils.close(stmt);\n            }\n\n        }\n    }\n\n    public abstract int getActivePeak();\n\n    public CompositeDataSupport getCompositeData() throws JMException {\n        JdbcDataSourceStat stat = this.getDataSourceStat();\n        Map<String, Object> map = new HashMap();\n        map.put(\"ID\", this.getID());\n        map.put(\"URL\", this.getUrl());\n        map.put(\"Name\", this.getName());\n        map.put(\"FilterClasses\", this.getFilterClasses());\n        map.put(\"CreatedTime\", this.getCreatedTime());\n        map.put(\"RawDriverClassName\", this.getDriverClassName());\n        map.put(\"RawUrl\", this.getUrl());\n        map.put(\"RawDriverMajorVersion\", this.getRawDriverMajorVersion());\n        map.put(\"RawDriverMinorVersion\", this.getRawDriverMinorVersion());\n        map.put(\"Properties\", this.getProperties());\n        map.put(\"ConnectionActiveCount\", (long) this.getActiveCount());\n        map.put(\"ConnectionActiveCountMax\", this.getActivePeak());\n        map.put(\"ConnectionCloseCount\", this.getCloseCount());\n        map.put(\"ConnectionCommitCount\", this.getCommitCount());\n        map.put(\"ConnectionRollbackCount\", this.getRollbackCount());\n        map.put(\"ConnectionConnectLastTime\", stat.getConnectionStat().getConnectLastTime());\n        map.put(\"ConnectionConnectErrorCount\", this.getCreateCount());\n        if (this.createError != null) {\n            map.put(\"ConnectionConnectErrorLastTime\", this.getLastCreateErrorTime());\n            map.put(\"ConnectionConnectErrorLastMessage\", this.createError.getMessage());\n            map.put(\"ConnectionConnectErrorLastStackTrace\", Utils.getStackTrace(this.createError));\n        } else {\n            map.put(\"ConnectionConnectErrorLastTime\", (Object) null);\n            map.put(\"ConnectionConnectErrorLastMessage\", (Object) null);\n            map.put(\"ConnectionConnectErrorLastStackTrace\", (Object) null);\n        }\n\n        map.put(\"StatementCreateCount\", stat.getStatementStat().getCreateCount());\n        map.put(\"StatementPrepareCount\", stat.getStatementStat().getPrepareCount());\n        map.put(\"StatementPreCallCount\", stat.getStatementStat().getPrepareCallCount());\n        map.put(\"StatementExecuteCount\", stat.getStatementStat().getExecuteCount());\n        map.put(\"StatementRunningCount\", stat.getStatementStat().getRunningCount());\n        map.put(\"StatementConcurrentMax\", stat.getStatementStat().getConcurrentMax());\n        map.put(\"StatementCloseCount\", stat.getStatementStat().getCloseCount());\n        map.put(\"StatementErrorCount\", stat.getStatementStat().getErrorCount());\n        map.put(\"StatementLastErrorTime\", (Object) null);\n        map.put(\"StatementLastErrorMessage\", (Object) null);\n        map.put(\"StatementLastErrorStackTrace\", (Object) null);\n        map.put(\"StatementExecuteMillisTotal\", stat.getStatementStat().getMillisTotal());\n        map.put(\"StatementExecuteLastTime\", stat.getStatementStat().getExecuteLastTime());\n        map.put(\"ConnectionConnectingCount\", stat.getConnectionStat().getConnectingCount());\n        map.put(\"ResultSetCloseCount\", stat.getResultSetStat().getCloseCount());\n        map.put(\"ResultSetOpenCount\", stat.getResultSetStat().getOpenCount());\n        map.put(\"ResultSetOpenningCount\", stat.getResultSetStat().getOpeningCount());\n        map.put(\"ResultSetOpenningMax\", stat.getResultSetStat().getOpeningMax());\n        map.put(\"ResultSetFetchRowCount\", stat.getResultSetStat().getFetchRowCount());\n        map.put(\"ResultSetLastOpenTime\", stat.getResultSetStat().getLastOpenTime());\n        map.put(\"ResultSetErrorCount\", stat.getResultSetStat().getErrorCount());\n        map.put(\"ResultSetOpenningMillisTotal\", stat.getResultSetStat().getAliveMillisTotal());\n        map.put(\"ResultSetLastErrorTime\", stat.getResultSetStat().getLastErrorTime());\n        map.put(\"ResultSetLastErrorMessage\", (Object) null);\n        map.put(\"ResultSetLastErrorStackTrace\", (Object) null);\n        map.put(\"ConnectionConnectCount\", this.getConnectCount());\n        if (this.createError != null) {\n            map.put(\"ConnectionErrorLastMessage\", this.createError.getMessage());\n            map.put(\"ConnectionErrorLastStackTrace\", Utils.getStackTrace(this.createError));\n        } else {\n            map.put(\"ConnectionErrorLastMessage\", (Object) null);\n            map.put(\"ConnectionErrorLastStackTrace\", (Object) null);\n        }\n\n        map.put(\"ConnectionConnectMillisTotal\", stat.getConnectionStat().getConnectMillis());\n        map.put(\"ConnectionConnectingCountMax\", stat.getConnectionStat().getConnectingMax());\n        map.put(\"ConnectionConnectMillisMax\", stat.getConnectionStat().getConnectMillisMax());\n        map.put(\"ConnectionErrorLastTime\", stat.getConnectionStat().getErrorLastTime());\n        map.put(\"ConnectionAliveMillisMax\", stat.getConnectionConnectAliveMillisMax());\n        map.put(\"ConnectionAliveMillisMin\", stat.getConnectionConnectAliveMillisMin());\n        map.put(\"ConnectionHistogram\", stat.getConnectionHistogramValues());\n        map.put(\"StatementHistogram\", stat.getStatementStat().getHistogramValues());\n        return new CompositeDataSupport(JdbcStatManager.getDataSourceCompositeType(), map);\n    }\n\n    public long getID() {\n        return this.id;\n    }\n\n    public Date getCreatedTime() {\n        return this.createdTime;\n    }\n\n    public abstract int getRawDriverMajorVersion();\n\n    public abstract int getRawDriverMinorVersion();\n\n    public abstract String getProperties();\n\n    public Logger getParentLogger() throws SQLFeatureNotSupportedException {\n        throw new SQLFeatureNotSupportedException();\n    }\n\n    public void closePreapredStatement(PreparedStatementHolder stmtHolder) {\n        if (stmtHolder != null) {\n            closedPreparedStatementCountUpdater.incrementAndGet(this);\n            this.decrementCachedPreparedStatementCount();\n            this.incrementCachedPreparedStatementDeleteCount();\n            JdbcUtils.close(stmtHolder.statement);\n        }\n    }\n\n    protected void cloneTo(DruidAbstractDataSource to) {\n        to.defaultAutoCommit = this.defaultAutoCommit;\n        to.defaultReadOnly = this.defaultReadOnly;\n        to.defaultTransactionIsolation = this.defaultTransactionIsolation;\n        to.defaultCatalog = this.defaultCatalog;\n        to.name = this.name;\n        to.username = this.username;\n        to.password = this.password;\n        to.jdbcUrl = this.jdbcUrl;\n        to.driverClass = this.driverClass;\n        to.connectProperties = this.connectProperties;\n        to.passwordCallback = this.passwordCallback;\n        to.userCallback = this.userCallback;\n        to.initialSize = this.initialSize;\n        to.maxActive = this.maxActive;\n        to.minIdle = this.minIdle;\n        to.maxIdle = this.maxIdle;\n        to.maxWait = this.maxWait;\n        to.validationQuery = this.validationQuery;\n        to.validationQueryTimeout = this.validationQueryTimeout;\n        to.testOnBorrow = this.testOnBorrow;\n        to.testOnReturn = this.testOnReturn;\n        to.testWhileIdle = this.testWhileIdle;\n        to.poolPreparedStatements = this.poolPreparedStatements;\n        to.sharePreparedStatements = this.sharePreparedStatements;\n        to.maxPoolPreparedStatementPerConnectionSize = this.maxPoolPreparedStatementPerConnectionSize;\n        to.logWriter = this.logWriter;\n        if (this.filters != null) {\n            to.filters = new ArrayList(this.filters);\n        }\n\n        to.exceptionSorter = this.exceptionSorter;\n        to.driver = this.driver;\n        to.queryTimeout = this.queryTimeout;\n        to.transactionQueryTimeout = this.transactionQueryTimeout;\n        to.accessToUnderlyingConnectionAllowed = this.accessToUnderlyingConnectionAllowed;\n        to.timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;\n        to.numTestsPerEvictionRun = this.numTestsPerEvictionRun;\n        to.minEvictableIdleTimeMillis = this.minEvictableIdleTimeMillis;\n        to.removeAbandoned = this.removeAbandoned;\n        to.removeAbandonedTimeoutMillis = this.removeAbandonedTimeoutMillis;\n        to.logAbandoned = this.logAbandoned;\n        to.maxOpenPreparedStatements = this.maxOpenPreparedStatements;\n        if (this.connectionInitSqls != null) {\n            to.connectionInitSqls = new ArrayList(this.connectionInitSqls);\n        }\n\n        to.dbType = this.dbType;\n        to.timeBetweenConnectErrorMillis = this.timeBetweenConnectErrorMillis;\n        to.validConnectionChecker = this.validConnectionChecker;\n        to.connectionErrorRetryAttempts = this.connectionErrorRetryAttempts;\n        to.breakAfterAcquireFailure = this.breakAfterAcquireFailure;\n        to.transactionThresholdMillis = this.transactionThresholdMillis;\n        to.dupCloseLogEnable = this.dupCloseLogEnable;\n        to.isOracle = this.isOracle;\n        to.useOracleImplicitCache = this.useOracleImplicitCache;\n        to.asyncCloseConnectionEnable = this.asyncCloseConnectionEnable;\n        to.createScheduler = this.createScheduler;\n        to.destroyScheduler = this.destroyScheduler;\n    }\n\n    public abstract void discardConnection(Connection var1);\n\n    public boolean isAsyncCloseConnectionEnable() {\n        return this.isRemoveAbandoned() ? true : this.asyncCloseConnectionEnable;\n    }\n\n    public void setAsyncCloseConnectionEnable(boolean asyncCloseConnectionEnable) {\n        this.asyncCloseConnectionEnable = asyncCloseConnectionEnable;\n    }\n\n    public ScheduledExecutorService getCreateScheduler() {\n        return this.createScheduler;\n    }\n\n    public void setCreateScheduler(ScheduledExecutorService createScheduler) {\n        if (this.isInited()) {\n            throw new DruidRuntimeException(\"dataSource inited.\");\n        } else {\n            this.createScheduler = createScheduler;\n        }\n    }\n\n    public ScheduledExecutorService getDestroyScheduler() {\n        return this.destroyScheduler;\n    }\n\n    public void setDestroyScheduler(ScheduledExecutorService destroyScheduler) {\n        if (this.isInited()) {\n            throw new DruidRuntimeException(\"dataSource inited.\");\n        } else {\n            this.destroyScheduler = destroyScheduler;\n        }\n    }\n\n    public boolean isInited() {\n        return this.inited;\n    }\n\n    public int getMaxCreateTaskCount() {\n        return this.maxCreateTaskCount;\n    }\n\n    public void setMaxCreateTaskCount(int maxCreateTaskCount) {\n        if (maxCreateTaskCount < 1) {\n            throw new IllegalArgumentException();\n        } else {\n            this.maxCreateTaskCount = maxCreateTaskCount;\n        }\n    }\n\n    public boolean isFailFast() {\n        return this.failFast;\n    }\n\n    public void setFailFast(boolean failFast) {\n        this.failFast = failFast;\n    }\n\n    public int getOnFatalErrorMaxActive() {\n        return this.onFatalErrorMaxActive;\n    }\n\n    public void setOnFatalErrorMaxActive(int onFatalErrorMaxActive) {\n        this.onFatalErrorMaxActive = onFatalErrorMaxActive;\n    }\n\n    public boolean isOnFatalError() {\n        return this.onFatalError;\n    }\n\n    public boolean isInitExceptionThrow() {\n        return this.initExceptionThrow;\n    }\n\n    public void setInitExceptionThrow(boolean initExceptionThrow) {\n        this.initExceptionThrow = initExceptionThrow;\n    }\n\n    public static class PhysicalConnectionInfo {\n\n        long createTaskId;\n\n        private Connection connection;\n\n        private long connectStartNanos;\n\n        private long connectedNanos;\n\n        private long initedNanos;\n\n        private long validatedNanos;\n\n        private Map<String, Object> vairiables;\n\n        private Map<String, Object> globalVairiables;\n\n        public PhysicalConnectionInfo(Connection connection, long connectStartNanos, long connectedNanos,\n                                      long initedNanos, long validatedNanos) {\n            this(connection, connectStartNanos, connectedNanos, initedNanos, validatedNanos, (Map) null, (Map) null);\n        }\n\n        public PhysicalConnectionInfo(Connection connection, long connectStartNanos, long connectedNanos,\n                                      long initedNanos, long validatedNanos, Map<String, Object> vairiables,\n                                      Map<String, Object> globalVairiables) {\n            this.connection = connection;\n            this.connectStartNanos = connectStartNanos;\n            this.connectedNanos = connectedNanos;\n            this.initedNanos = initedNanos;\n            this.validatedNanos = validatedNanos;\n            this.vairiables = vairiables;\n            this.globalVairiables = globalVairiables;\n        }\n\n        public Connection getPhysicalConnection() {\n            return this.connection;\n        }\n\n        public long getConnectStartNanos() {\n            return this.connectStartNanos;\n        }\n\n        public long getConnectedNanos() {\n            return this.connectedNanos;\n        }\n\n        public long getInitedNanos() {\n            return this.initedNanos;\n        }\n\n        public long getValidatedNanos() {\n            return this.validatedNanos;\n        }\n\n        public long getConnectNanoSpan() {\n            return this.connectedNanos - this.connectStartNanos;\n        }\n\n        public Map<String, Object> getVairiables() {\n            return this.vairiables;\n        }\n\n        public Map<String, Object> getGlobalVairiables() {\n            return this.globalVairiables;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/BackendApplication.java",
    "content": "package com.simplefanc.voj.backend;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.retry.annotation.EnableRetry;\nimport org.springframework.scheduling.annotation.EnableAsync;\nimport org.springframework.scheduling.annotation.EnableScheduling;\nimport org.springframework.transaction.annotation.EnableTransactionManagement;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/22 23:25\n * @Description:\n */\n@EnableRetry\n@EnableScheduling // 开启定时任务\n@EnableDiscoveryClient // 开启服务注册发现功能\n@SpringBootApplication\n@EnableAsync(proxyTargetClass = true) // 开启异步注解\n//@EnableCaching\n@EnableTransactionManagement\npublic class BackendApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(BackendApplication.class, args);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/cache/CacheTypeManager.java",
    "content": "package com.simplefanc.voj.backend.cache;\n\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport lombok.AllArgsConstructor;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author chenfan\n * @date 2022/10/6 16:06\n **/\npublic class CacheTypeManager {\n    public static final Map<String, CacheType> CACHE_TYPE_MAP = new HashMap<>(){\n        {\n            put(RedisConstant.OI_CONTEST_RANK_CACHE, new CacheType(RedisConstant.OI_CONTEST_RANK_CACHE, 3600, 2 * 3600));\n            put(RedisConstant.CONTEST_RANK_CAL_RESULT_CACHE, new CacheType(RedisConstant.CONTEST_RANK_CAL_RESULT_CACHE, 8, 16));\n            put(RedisConstant.SUPER_ADMIN_UID_LIST_CACHE, new CacheType(RedisConstant.SUPER_ADMIN_UID_LIST_CACHE, 6 * 3600, 12 * 3600));\n            put(RedisConstant.ACM_RANK_CACHE, new CacheType(RedisConstant.ACM_RANK_CACHE, 30, 60));\n            put(RedisConstant.OI_RANK_CACHE, new CacheType(RedisConstant.OI_RANK_CACHE, 30, 60));\n        }\n    };\n\n    @AllArgsConstructor\n    public static class CacheType {\n        public String key;\n        public final int ttl1;\n        public final int ttl2;\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/cache/DoubleCache.java",
    "content": "package com.simplefanc.voj.backend.cache;\n\nimport com.github.benmanes.caffeine.cache.Cache;\nimport com.simplefanc.voj.backend.config.property.DoubleCacheProperties;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.support.AbstractValueAdaptingCache;\nimport org.springframework.data.redis.core.RedisTemplate;\n\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * @program: double-cache\n * @author: chenfan\n * @create: 2022-10-6 10:07\n **/\n@Slf4j\npublic class DoubleCache extends AbstractValueAdaptingCache {\n    private String cacheName;\n    private RedisTemplate<Object, Object> redisTemplate;\n    private Cache<Object, Object> caffeineCache;\n    private DoubleCacheProperties cacheConfig;\n\n    protected DoubleCache(boolean allowNullValues) {\n        super(allowNullValues);\n    }\n\n    public DoubleCache(String cacheName, RedisTemplate<Object, Object> redisTemplate,\n                       Cache<Object, Object> caffeineCache,\n                       DoubleCacheProperties cacheConfig) {\n        super(cacheConfig.getAllowNull());\n        this.cacheName = cacheName;\n        this.redisTemplate = redisTemplate;\n        this.caffeineCache = caffeineCache;\n        this.cacheConfig = cacheConfig;\n    }\n\n    /**\n     * 在缓存中实际执行查找的操作，父类的get()方法会调用这个方法\n     * @param key\n     * @return\n     */\n    @Override\n    protected Object lookup(Object key) {\n        // 先从caffeine中查找\n        Object obj = caffeineCache.getIfPresent(key);\n        if (Objects.nonNull(obj)) {\n            log.info(\"get data from caffeine\");\n            // 不用fromStoreValue，否则返回的是null，会再查数据库\n            return obj;\n        }\n\n        // 再从redis中查找\n        String redisKey = this.cacheName + \":\" + key;\n        obj = redisTemplate.opsForValue().get(redisKey);\n        if (Objects.nonNull(obj)) {\n            log.info(\"get data from redis\");\n            caffeineCache.put(key, obj);\n        }\n        return obj;\n    }\n\n    /**\n     * 如果只是使用注解来管理缓存的话，那么这个方法不会被调用到（实际走父类的get方法）\n     * @param key\n     * @param valueLoader\n     * @param <T>\n     * @return\n     */\n    @Override\n    public <T> T get(Object key, Callable<T> valueLoader) {\n        ReentrantLock lock = new ReentrantLock();\n        lock.lock();\n        try {\n            Object obj = lookup(key);\n            if (Objects.nonNull(obj)) {\n                return (T) obj;\n            }\n            // 没有找到\n            obj = valueLoader.call();\n            // 放入缓存\n            put(key, obj);\n            return (T) obj;\n        } catch (Exception e) {\n            log.error(e.getMessage());\n        } finally {\n            lock.unlock();\n        }\n        return null;\n    }\n\n    /**\n     * 将数据放入缓存中\n     * @param key\n     * @param value\n     */\n    @Override\n    public void put(Object key, Object value) {\n        if (!isAllowNullValues() && Objects.isNull(value)) {\n            log.error(\"the value NULL will not be cached\");\n            return;\n        }\n\n        // 使用 toStoreValue(value) 包装，解决caffeine不能存null的问题\n        caffeineCache.put(key, toStoreValue(value));\n\n        // null对象只存在caffeine中一份就够了，不用存redis了\n        if (Objects.isNull(value)) {\n            return;\n        }\n        String redisKey = this.cacheName + \":\" + key;\n        final CacheTypeManager.CacheType cacheType = CacheTypeManager.CACHE_TYPE_MAP.get(this.cacheName);\n        if(cacheType != null) {\n            redisTemplate.opsForValue().set(redisKey, toStoreValue(value),\n                    cacheType.ttl2, TimeUnit.SECONDS);\n        } else {\n            Optional<Long> expireOpt = Optional.ofNullable(cacheConfig)\n                    .map(DoubleCacheProperties::getRedisExpire);\n            if (expireOpt.isPresent()) {\n                redisTemplate.opsForValue().set(redisKey, toStoreValue(value),\n                        expireOpt.get(), TimeUnit.SECONDS);\n            } else {\n                redisTemplate.opsForValue().set(redisKey, toStoreValue(value));\n            }\n        }\n    }\n\n    /**\n     * 删除缓存\n     * @param key\n     */\n    @Override\n    public void evict(Object key) {\n        redisTemplate.delete(this.cacheName + \":\" + key);\n        caffeineCache.invalidate(key);\n    }\n\n    /**\n     * 清空缓存中所有数据\n     */\n    @Override\n    public void clear() {\n        // 如果是正式环境，避免使用keys命令\n        Set<Object> keys = redisTemplate.keys(this.cacheName.concat(\":*\"));\n        for (Object key : keys) {\n            redisTemplate.delete(String.valueOf(key));\n        }\n        caffeineCache.invalidateAll();\n    }\n\n    /**\n     * 获取缓存名称，一般在CacheManager创建时指定\n     * @return\n     */\n    @Override\n    public String getName() {\n        return this.cacheName;\n    }\n\n    /**\n     * 获取实际使用的缓存\n     * @return\n     */\n    @Override\n    public Object getNativeCache() {\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/cache/DoubleCacheManager.java",
    "content": "package com.simplefanc.voj.backend.cache;\n\nimport com.github.benmanes.caffeine.cache.Caffeine;\nimport com.simplefanc.voj.backend.config.property.DoubleCacheProperties;\nimport org.springframework.cache.Cache;\nimport org.springframework.cache.CacheManager;\nimport org.springframework.data.redis.core.RedisTemplate;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @program: 缓存管理器 管理 DoubleCache 作为spring中的缓存使用\n * @author: chenfan\n * @create: 2022-10-6 10:07\n **/\npublic class DoubleCacheManager implements CacheManager {\n    Map<String, Cache> cacheMap = new ConcurrentHashMap<>();\n    private RedisTemplate<Object, Object> redisTemplate;\n    private DoubleCacheProperties cacheConfig;\n\n    public DoubleCacheManager(RedisTemplate<Object, Object> redisTemplate,\n                              DoubleCacheProperties doubleCacheConfig) {\n        this.redisTemplate = redisTemplate;\n        this.cacheConfig = doubleCacheConfig;\n    }\n\n    /**\n     * 根据cacheName获取Cache实例，不存在时进行创建\n     * @param name\n     * @return\n     */\n    @Override\n    public Cache getCache(String name) {\n        Cache cache = cacheMap.get(name);\n        if (Objects.nonNull(cache)) {\n            return cache;\n        }\n        cache = new DoubleCache(name, redisTemplate, createCaffeineCache(name), cacheConfig);\n        // 使用 ConcurrentHashMap的putIfAbsent()方法放入，避免重复创建Cache以及造成Cache内数据的丢失\n        Cache oldCache = cacheMap.putIfAbsent(name, cache);\n        return oldCache == null ? cache : oldCache;\n    }\n\n    /**\n     * 返回管理的所有cacheName\n     * @return\n     */\n    @Override\n    public Collection<String> getCacheNames() {\n        return cacheMap.keySet();\n    }\n\n    /**\n     * 根据项目配置文件中的具体参数进行初始化\n     * @return\n     */\n    private com.github.benmanes.caffeine.cache.Cache<Object, Object> createCaffeineCache(String name) {\n        Caffeine<Object, Object> caffeineBuilder = Caffeine.newBuilder();\n        Optional<DoubleCacheProperties> cacheConfig = Optional.ofNullable(this.cacheConfig);\n        cacheConfig.map(DoubleCacheProperties::getInitialCapacity)\n                .ifPresent(caffeineBuilder::initialCapacity);\n        cacheConfig.map(DoubleCacheProperties::getMaximumSize)\n                .ifPresent(caffeineBuilder::maximumSize);\n        final CacheTypeManager.CacheType cacheType = CacheTypeManager.CACHE_TYPE_MAP.get(name);\n        if(cacheType != null) {\n            caffeineBuilder.expireAfterWrite(cacheType.ttl1, TimeUnit.SECONDS);\n        } else {\n            cacheConfig.map(DoubleCacheProperties::getExpireAfterWrite)\n                    .ifPresent(eaw -> caffeineBuilder.expireAfterWrite(eaw, TimeUnit.SECONDS));\n        }\n        cacheConfig.map(DoubleCacheProperties::getExpireAfterAccess)\n                .ifPresent(eaa -> caffeineBuilder.expireAfterAccess(eaa, TimeUnit.SECONDS));\n        cacheConfig.map(DoubleCacheProperties::getRefreshAfterWrite)\n                .ifPresent(raw -> caffeineBuilder.refreshAfterWrite(raw, TimeUnit.SECONDS));\n        return caffeineBuilder.build();\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/annotation/Access.java",
    "content": "package com.simplefanc.voj.backend.common.annotation;\n\nimport com.simplefanc.voj.backend.common.constants.AccessEnum;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * @Author chenfan\n * @Date 2022/9/25\n */\n@Target({ElementType.TYPE, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Access {\n    AccessEnum[] value() default {};\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/AccessEnum.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\n/**\n * @Author chenfan\n * @Date 2022/9/25\n */\npublic enum AccessEnum {\n    /**\n     * 公共讨论区\n     */\n    PUBLIC_DISCUSSION,\n\n    /**\n     * 比赛评论\n     */\n    CONTEST_COMMENT,\n\n    /**\n     * 公共评测\n     */\n    PUBLIC_JUDGE,\n\n    /**\n     * 比赛评测\n     */\n    CONTEST_JUDGE,\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/CallJudgerType.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\n/**\n * @author chenfan\n */\npublic enum CallJudgerType {\n\n    JUDGE,\n    COMPILE\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/Constant.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\n/**\n * @author chenfan\n * @date 2022/5/7 22:46\n **/\npublic interface Constant {\n    String GENERATE_USER_INFO_LIST = \"GENERATE_USER_INFO_LIST\";\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/EmailConstant.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\n/**\n * @Description 邮件任务的一些常量\n * @Since 2021/1/14\n */\npublic interface EmailConstant {\n\n    String OJ_URL = \"OJ_UR\";\n\n    String OJ_NAME = \"OJ_NAME\";\n\n    String OJ_SHORT_NAME = \"OJ_SHORT_NAME\";\n\n    String EMAIL_FROM = \"EMAIL_FROM\";\n\n    String EMAIL_BACKGROUND_IMG = \"EMAIL_BACKGROUND_IMG\";\n\n    String REGISTER_KEY_PREFIX = \"register-user:\";\n\n    String RESET_PASSWORD_KEY_PREFIX = \"reset-password:\";\n\n    String RESET_EMAIL_LOCK = \"reset-email-lock:\";\n\n    String REGISTER_EMAIL_LOCK = \"register-email-lock:\";\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/FileConstant.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\n/**\n * @Description 文件操作的一些常量\n * @Since 2021/1/10\n */\npublic interface FileConstant {\n\n    String USER_AVATAR_FOLDER = \"/voj/file/avatar\";\n\n    String HOME_CAROUSEL_FOLDER = \"/voj/file/carousel\";\n\n    String MARKDOWN_FILE_FOLDER = \"/voj/file/md\";\n\n    String PROBLEM_FILE_FOLDER = \"/voj/file/problem\";\n\n    String CONTEST_TEXT_PRINT_FOLDER = \"/voj/file/contest_print\";\n\n    String IMG_API = \"/api/public/img/\";\n\n    String FILE_API = \"/api/public/file/\";\n\n    String TESTCASE_TMP_FOLDER = \"/voj/file/zip\";\n\n    String TESTCASE_BASE_FOLDER = \"/voj/testcase\";\n\n    String FILE_DOWNLOAD_TMP_FOLDER = \"/voj/file/zip/download\";\n\n    String CONTEST_AC_SUBMISSION_TMP_FOLDER = \"/voj/file/zip/contest_ac\";\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/FileTypeEnum.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author chenfan\n * @date 2022/5/7 22:41\n **/\n@Getter\n@AllArgsConstructor\npublic enum FileTypeEnum {\n    AVATAR(\"avatar\"),\n\n    CAROUSEL(\"carousel\"),\n\n    MARKDOWN(\"md\");\n\n    private final String type;\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/QueueConstant.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\n/**\n * 等待判题的redis队列\n *\n * @Since 2021/12/22\n */\npublic interface QueueConstant {\n\n    String CONTEST_JUDGE_WAITING = \"Contest_Waiting_Handle_Queue\";\n\n    String GENERAL_JUDGE_WAITING = \"General_Waiting_Handle_Queue\";\n\n    String CONTEST_REMOTE_JUDGE_WAITING_HANDLE = \"Contest_Remote_Waiting_Handle_Queue\";\n\n    String GENERAL_REMOTE_JUDGE_WAITING_HANDLE = \"General_Remote_Waiting_Handle_Queue\";\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/RoleEnum.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n@Getter\n@AllArgsConstructor\npublic enum RoleEnum {\n    ROOT(1000L, \"root\"),\n    ADMIN(1001L, \"admin\"),\n    DEFAULT_USER(1002L, \"default_user\"),\n    NO_SUBMIT_USER(1003L, \"no_submit_user\"),\n    NO_DISCUSS_USER(1004L, \"no_discuss_user\"),\n    MUTE_USER(1005L, \"mute_user\"),\n    NO_SUBMIT_NO_DISCUSS_USER(1006L, \"no_submit_no_discuss_user\"),\n    NO_SUBMIT_MUTE_USER(1007L, \"no_submit_mute_user\"),\n    PROBLEM_ADMIN(1008L, \"problem_admin\");\n\n    private final Long id;\n    private final String name;\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/ScheduleConstant.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\n/**\n * @author chenfan\n * @date 2022/4/18 16:23\n **/\npublic interface ScheduleConstant {\n\n    String RECENT_OTHER_CONTEST = \"recent-other-contest\";\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/TrainingEnum.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @Description 训练题单的一些常量\n * @Since 2021/11/20\n */\n@Getter\n@AllArgsConstructor\npublic enum TrainingEnum {\n\n    AUTH_PRIVATE(\"Private\"),\n    AUTH_PUBLIC(\"Public\");\n\n    private final String value;\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/constants/UserStatusEnum.java",
    "content": "package com.simplefanc.voj.backend.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @Author chenfan\n * @Date 2022/9/28\n */\n@Getter\n@AllArgsConstructor\npublic enum UserStatusEnum {\n    NORMAL(0),\n    FORBID(1),\n    APPLYING(2);\n\n    private final Integer status;\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/exception/StatusAccessDeniedException.java",
    "content": "package com.simplefanc.voj.backend.common.exception;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 10:30\n * @Description:\n */\npublic class StatusAccessDeniedException extends RuntimeException {\n\n    public StatusAccessDeniedException() {\n    }\n\n    public StatusAccessDeniedException(String message) {\n        super(message);\n    }\n\n    public StatusAccessDeniedException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public StatusAccessDeniedException(Throwable cause) {\n        super(cause);\n    }\n\n    public StatusAccessDeniedException(String message, Throwable cause, boolean enableSuppression,\n                                       boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/exception/StatusFailException.java",
    "content": "package com.simplefanc.voj.backend.common.exception;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 10:27\n * @Description:\n */\npublic class StatusFailException extends RuntimeException {\n\n    public StatusFailException() {\n    }\n\n    public StatusFailException(String message) {\n        super(message);\n    }\n\n    public StatusFailException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public StatusFailException(Throwable cause) {\n        super(cause);\n    }\n\n    public StatusFailException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/exception/StatusForbiddenException.java",
    "content": "package com.simplefanc.voj.backend.common.exception;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 10:29\n * @Description:\n */\npublic class StatusForbiddenException extends RuntimeException {\n\n    public StatusForbiddenException() {\n    }\n\n    public StatusForbiddenException(String message) {\n        super(message);\n    }\n\n    public StatusForbiddenException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public StatusForbiddenException(Throwable cause) {\n        super(cause);\n    }\n\n    public StatusForbiddenException(String message, Throwable cause, boolean enableSuppression,\n                                    boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/exception/StatusNotFoundException.java",
    "content": "package com.simplefanc.voj.backend.common.exception;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 10:30\n * @Description:\n */\npublic class StatusNotFoundException extends RuntimeException {\n\n    public StatusNotFoundException() {\n    }\n\n    public StatusNotFoundException(String message) {\n        super(message);\n    }\n\n    public StatusNotFoundException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public StatusNotFoundException(Throwable cause) {\n        super(cause);\n    }\n\n    public StatusNotFoundException(String message, Throwable cause, boolean enableSuppression,\n                                   boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/exception/StatusSystemErrorException.java",
    "content": "package com.simplefanc.voj.backend.common.exception;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:33\n * @Description:\n */\npublic class StatusSystemErrorException extends RuntimeException {\n\n    public StatusSystemErrorException() {\n    }\n\n    public StatusSystemErrorException(String message) {\n        super(message);\n    }\n\n    public StatusSystemErrorException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public StatusSystemErrorException(Throwable cause) {\n        super(cause);\n    }\n\n    public StatusSystemErrorException(String message, Throwable cause, boolean enableSuppression,\n                                      boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/exception/advice/GlobalExceptionAdvice.java",
    "content": "package com.simplefanc.voj.backend.common.exception.advice;\n\nimport com.google.protobuf.ServiceException;\nimport com.simplefanc.voj.backend.common.exception.*;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport com.simplefanc.voj.common.result.ResultStatus;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.ibatis.exceptions.PersistenceException;\nimport org.apache.shiro.ShiroException;\nimport org.apache.shiro.authc.AuthenticationException;\nimport org.apache.shiro.authz.AuthorizationException;\nimport org.apache.shiro.authz.UnauthenticatedException;\nimport org.springframework.dao.DataIntegrityViolationException;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.converter.HttpMessageNotReadableException;\nimport org.springframework.validation.BindException;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.FieldError;\nimport org.springframework.validation.ObjectError;\nimport org.springframework.web.HttpMediaTypeNotSupportedException;\nimport org.springframework.web.HttpRequestMethodNotSupportedException;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.MissingServletRequestParameterException;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.mail.MessagingException;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.validation.ConstraintViolation;\nimport javax.validation.ConstraintViolationException;\nimport javax.validation.ValidationException;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.sql.SQLException;\nimport java.util.Set;\n\n/**\n * 全局异常处理\n */\n@Slf4j(topic = \"voj\")\n@RestControllerAdvice\npublic class GlobalExceptionAdvice {\n\n    /**\n     * 打印异常信息\n     */\n    public static String getMessage(Exception e) {\n        String swStr = null;\n        try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {\n            e.printStackTrace(pw);\n            pw.flush();\n            sw.flush();\n            swStr = sw.toString();\n        } catch (IOException ex) {\n            ex.printStackTrace();\n            log.error(ex.getMessage());\n        }\n        return swStr;\n    }\n\n    /**\n     * 400 - Internal Server Error 自定义通用异常\n     */\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler(value = {StatusForbiddenException.class, StatusAccessDeniedException.class,\n            StatusFailException.class, StatusNotFoundException.class, StatusSystemErrorException.class})\n    public CommonResult<Void> handleCustomException(Exception e) {\n        return CommonResult.errorResponse(e.getMessage(), ResultStatus.FAIL);\n    }\n\n    /**\n     * 401 -UnAuthorized 处理AuthenticationException,token相关异常 即是认证出错 可能无法处理！\n     */\n    @ResponseStatus(HttpStatus.UNAUTHORIZED)\n    @ExceptionHandler(value = AuthenticationException.class)\n    public CommonResult<Void> handleAuthenticationException(AuthenticationException e, HttpServletRequest httpRequest,\n                                                            HttpServletResponse httpResponse) {\n        // 为了前端能区别请求来源\n        httpResponse.setHeader(\"Url-Type\", httpRequest.getHeader(\"Url-Type\"));\n        return CommonResult.errorResponse(e.getMessage(), ResultStatus.ACCESS_DENIED);\n    }\n\n    /**\n     * 401 -UnAuthorized UnauthenticatedException,token相关异常 即是认证出错 可能无法处理！\n     * 没有登录（没有token），访问有@RequiresAuthentication的请求路径会报这个异常\n     */\n    @ResponseStatus(HttpStatus.UNAUTHORIZED)\n    @ExceptionHandler(value = UnauthenticatedException.class)\n    public CommonResult<Void> handleUnauthenticatedException(UnauthenticatedException e, HttpServletRequest httpRequest,\n                                                             HttpServletResponse httpResponse) {\n        // 为了前端能区别请求来源\n        httpResponse.setHeader(\"Url-Type\", httpRequest.getHeader(\"Url-Type\"));\n        return CommonResult.errorResponse(\"请您先登录！\", ResultStatus.ACCESS_DENIED);\n    }\n\n    /**\n     * 403 -FORBIDDEN AuthorizationException异常 即是授权认证出错 可能无法处理！\n     */\n    @ResponseStatus(HttpStatus.FORBIDDEN)\n    @ExceptionHandler(value = AuthorizationException.class)\n    public CommonResult<Void> handleAuthenticationException(AuthorizationException e, HttpServletRequest httpRequest,\n                                                            HttpServletResponse httpResponse) {\n        // 为了前端能区别请求来源\n        httpResponse.setHeader(\"Url-Type\", httpRequest.getHeader(\"Url-Type\"));\n        return CommonResult.errorResponse(\"对不起，您无权限进行此操作！\", ResultStatus.FORBIDDEN);\n    }\n\n    /**\n     * 403 -FORBIDDEN 处理shiro的异常 无法处理！ 未能走到controller层\n     */\n    @ResponseStatus(HttpStatus.FORBIDDEN)\n    @ExceptionHandler(value = ShiroException.class)\n    public CommonResult<Void> handleShiroException(ShiroException e, HttpServletRequest httpRequest,\n                                                   HttpServletResponse httpResponse) {\n        // 为了前端能区别请求来源\n        httpResponse.setHeader(\"Url-Type\", httpRequest.getHeader(\"Url-Type\"));\n        return CommonResult.errorResponse(\"对不起，您无权限进行此操作，请先登录进行授权认证\", ResultStatus.FORBIDDEN);\n    }\n\n    /**\n     * 400 - Bad Request 处理Assert的异常 断言的异常！\n     */\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler(value = IllegalArgumentException.class)\n    public CommonResult<Void> handler(IllegalArgumentException e) {\n        return CommonResult.errorResponse(e.getMessage(), ResultStatus.FAIL);\n    }\n\n    /**\n     * 400 - Bad Request @Validated 校验错误异常处理\n     */\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler(value = MethodArgumentNotValidException.class)\n    public CommonResult<Void> handlerMethodArgumentNotValidException(MethodArgumentNotValidException e) {\n        BindingResult bindingResult = e.getBindingResult();\n        ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();\n        return CommonResult.errorResponse(objectError.getDefaultMessage(), ResultStatus.FAIL);\n    }\n\n    /**\n     * 400 - Bad Request 处理缺少请求参数\n     */\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler(MissingServletRequestParameterException.class)\n    public CommonResult<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {\n        return CommonResult.errorResponse(\"The required request parameters are missing：\" + e.getMessage(),\n                ResultStatus.FAIL);\n    }\n\n    /**\n     * 400 - Bad Request 参数解析失败\n     */\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler(HttpMessageNotReadableException.class)\n    public CommonResult<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {\n        return CommonResult.errorResponse(\"Failed to parse parameter format!\", ResultStatus.FAIL);\n    }\n\n    /**\n     * 400 - Bad Request 参数绑定失败\n     */\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler(BindException.class)\n    public CommonResult<Void> handleBindException(BindException e) {\n        BindingResult result = e.getBindingResult();\n        FieldError error = result.getFieldError();\n        String field = error.getField();\n        String code = error.getDefaultMessage();\n        String message = String.format(\"%s:%s\", field, code);\n        return CommonResult.errorResponse(message, ResultStatus.FAIL);\n    }\n\n    /**\n     * 400 - Bad Request 参数验证失败\n     */\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler(ConstraintViolationException.class)\n    public CommonResult<Void> handleServiceException(ConstraintViolationException e) {\n        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();\n        ConstraintViolation<?> violation = violations.iterator().next();\n        String message = violation.getMessage();\n        return CommonResult.errorResponse(\"[参数验证失败]parameter:\" + message, ResultStatus.FAIL);\n    }\n\n    /**\n     * 400 - Bad Request 实体校验失败\n     */\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler(ValidationException.class)\n    public CommonResult<Void> handleValidationException(ValidationException e) {\n        return CommonResult.errorResponse(\"Entity verification failed. The request parameters are incorrect!\",\n                ResultStatus.FAIL);\n    }\n\n    /**\n     * 405 - Method Not Allowed 不支持当前请求方法\n     */\n    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)\n    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)\n    public CommonResult<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {\n        return CommonResult.errorResponse(\"The request method is not supported!\", ResultStatus.FAIL);\n    }\n\n    /**\n     * 415 - Unsupported Media Type 不支持当前媒体类型\n     */\n    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)\n    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)\n    public CommonResult<Void> handleHttpMediaTypeNotSupportedException(Exception e) {\n        return CommonResult.errorResponse(\"The media type is not supported!\", ResultStatus.FAIL);\n    }\n\n    /**\n     * 500 - Internal Server Error 处理邮件发送出现的异常\n     */\n    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    @ExceptionHandler(value = MessagingException.class)\n    public CommonResult<Void> handler(MessagingException e) {\n        log.error(\"邮箱系统异常-------------->{}\", getMessage(e));\n        return CommonResult.errorResponse(\"Server Error! Please try Again later!\", ResultStatus.SYSTEM_ERROR);\n    }\n\n    /**\n     * 500 - Internal Server Error 业务逻辑异常\n     */\n    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    @ExceptionHandler(ServiceException.class)\n    public CommonResult<Void> handleServiceException(ServiceException e) {\n        log.error(\"业务逻辑异常-------------->{}\", getMessage(e));\n        return CommonResult.errorResponse(\"Server Error! Please try Again later!\", ResultStatus.SYSTEM_ERROR);\n    }\n\n    /**\n     * 500 - Internal Server Error 操作数据库出现异常:名称重复，外键关联\n     */\n    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    @ExceptionHandler(DataIntegrityViolationException.class)\n    public CommonResult<Void> handleDataIntegrityViolationException(DataIntegrityViolationException e) {\n        log.error(\"操作数据库出现异常-------------->{}\", getMessage(e));\n        return CommonResult.errorResponse(\"Server Error! Please try Again later!\", ResultStatus.SYSTEM_ERROR);\n    }\n\n    /**\n     * 500 - Internal Server Error 操作数据库出现异常\n     */\n    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    @ExceptionHandler(SQLException.class)\n    public CommonResult<Void> handleSQLException(SQLException e) {\n        log.error(\"操作数据库出现异常-------------->{}\", getMessage(e));\n        return CommonResult.errorResponse(\"Operation failed! Error message: \" + e.getMessage(),\n                ResultStatus.SYSTEM_ERROR);\n    }\n\n    /**\n     * 500 - Internal Server Error 批量操作数据库出现异常:名称重复，外键关联\n     */\n    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    @ExceptionHandler(PersistenceException.class)\n    public CommonResult<Void> handleBatchUpdateException(PersistenceException e) {\n        log.error(\"操作数据库出现异常-------------->{}\", getMessage(e));\n        return CommonResult.errorResponse(\"请检查数据是否准确！可能原因：数据库中已有相同的数据导致重复冲突!\", ResultStatus.SYSTEM_ERROR);\n    }\n\n    /**\n     * 500 - Internal Server Error 系统通用异常\n     */\n    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    @ExceptionHandler(Exception.class)\n    public CommonResult<Void> handleException(Exception e) {\n        log.error(\"系统通用异常-------------->{}\", getMessage(e));\n        return CommonResult.errorResponse(\"Server Error!\", ResultStatus.SYSTEM_ERROR);\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/utils/ConfigUtil.java",
    "content": "package com.simplefanc.voj.backend.common.utils;\n\nimport com.simplefanc.voj.backend.config.ConfigVO;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/2 23:17\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class ConfigUtil {\n\n    private final ConfigVO configVO;\n\n    public String getConfigContent() {\n        return buildYamlStr(configVO);\n    }\n\n    public String buildYamlStr(ConfigVO configVO) {\n        return \"voj:\\n\" +\n                \"  jwt:\\n\" +\n                \"    # 加密秘钥\\n\" +\n                \"    secret: \" + configVO.getTokenSecret() + \"\\n\" +\n                \"    # token有效时长，1天，单位秒\\n\" +\n                \"    expire: \" + configVO.getTokenExpire() + \"\\n\" +\n                \"    checkRefreshExpire: \" + configVO.getCheckRefreshExpire() + \"\\n\" +\n                \"    header: token\\n\" +\n                \"  judge:\\n\" +\n                \"    # 调用判题服务器的token\\n\" +\n                \"    token: \" + configVO.getJudgeToken() + \"\\n\" +\n                \"  db:\\n\" +\n                \"    host: \" + configVO.getMysqlHost() + \"\\n\" +\n                \"    public-host: \" + configVO.getMysqlPublicHost() + \"\\n\" +\n                \"    port: \" + configVO.getMysqlPort() + \"\\n\" +\n                \"    name: \" + configVO.getMysqlDbName() + \"\\n\" +\n                \"    username: \" + configVO.getMysqlUsername() + \"\\n\" +\n                \"    password: \" + configVO.getMysqlPassword() + \"\\n\" +\n                \"  mail:\\n\" +\n                \"    ssl: \" + configVO.getEmailSsl() + \"\\n\" +\n                \"    username: \" + configVO.getEmailUsername() + \"\\n\" +\n                \"    password: \" + configVO.getEmailPassword() + \"\\n\" +\n                \"    host: \" + configVO.getEmailHost() + \"\\n\" +\n                \"    port: \" + configVO.getEmailPort() + \"\\n\" +\n                \"  redis:\\n\" +\n                \"    host: \" + configVO.getRedisHost() + \"\\n\" +\n                \"    port: \" + configVO.getRedisPort() + \"\\n\" +\n                \"    password: \" + configVO.getRedisPassword() + \"\\n\" +\n                \"  switch:\\n\" +\n                \"    problem: \" + configVO.getProblem() + \"\\n\" +\n                \"    training: \" + configVO.getTraining() + \"\\n\" +\n                \"    contest: \" + configVO.getContest() + \"\\n\" +\n                \"    status: \" + configVO.getStatus() + \"\\n\" +\n                \"    rank: \" + configVO.getRank() + \"\\n\" +\n                \"    discussion: \" + configVO.getDiscussion() + \"\\n\" +\n                \"    introduction: \" + configVO.getIntroduction() + \"\\n\" +\n                \"    register: \" + configVO.getRegister() + \"\\n\" +\n                \"    judge:\\n\" +\n                \"      public: \"  + configVO.getOpenPublicJudge() + \"\\n\" +\n                \"      contest: \"  + configVO.getOpenContestJudge() + \"\\n\" +\n                \"      submit-interval: \"  + configVO.getDefaultSubmitInterval() + \"\\n\" +\n                \"      code-visible-start-time: \"  + configVO.getCodeVisibleStartTime() + \"\\n\" +\n                \"  web-config:\\n\" +\n                \"    base-url: \\\"\" + configVO.getBaseUrl() + \"\\\"\\n\" +\n                \"    name: \\\"\" + configVO.getName() + \"\\\"\\n\" +\n                \"    short-name: \\\"\" + configVO.getShortName() + \"\\\"\\n\" +\n                \"    description: \\\"\" + configVO.getDescription() + \"\\\"\\n\" +\n                \"    footer:\\n\" +\n                \"      record:\\n\" +\n                \"        name: \\\"\" + configVO.getRecordName() + \"\\\"\\n\" +\n                \"        url: \\\"\" + configVO.getRecordUrl() + \"\\\"\\n\" +\n                \"      project:\\n\" +\n                \"        name: \\\"\" + configVO.getProjectName() + \"\\\"\\n\" +\n                \"        url: \\\"\" + configVO.getProjectUrl() + \"\\\"\\n\";\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/utils/ExcelUtil.java",
    "content": "package com.simplefanc.voj.backend.common.utils;\n\nimport cn.hutool.core.util.CharsetUtil;\nimport lombok.experimental.UtilityClass;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * @author chenfan\n * @date 2022/5/20 20:40\n **/\n@UtilityClass\npublic class ExcelUtil {\n    public void wrapExcelResponse(HttpServletResponse response, String fileName) {\n        response.setContentType(\"application/vnd.ms-excel\");\n        response.setCharacterEncoding(CharsetUtil.UTF_8);\n        // 这里URLEncoder.encode可以防止中文乱码\n        response.setHeader(\"Content-disposition\", \"attachment;filename=\"\n                + URLEncoder.encode(fileName, StandardCharsets.UTF_8)\n                + \".xlsx\");\n        response.setHeader(\"Content-Type\", \"application/xlsx\");\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/utils/JwtUtil.java",
    "content": "package com.simplefanc.voj.backend.common.utils;\n\nimport com.auth0.jwt.JWT;\nimport com.auth0.jwt.algorithms.Algorithm;\nimport com.auth0.jwt.exceptions.JWTVerificationException;\nimport lombok.Data;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Date;\n\n@Slf4j(topic = \"voj\")\n@Data\n@Component\n@ConfigurationProperties(prefix = \"voj.jwt\")\npublic class JwtUtil {\n\n    public final static String TOKEN_KEY = \"token-key:\";\n\n    public final static String TOKEN_REFRESH = \"token-refresh:\";\n\n    private long expire;\n\n    private long checkRefreshExpire;\n\n    private String secret;\n\n    private final RedisUtil redisUtil;\n\n    /**\n     * 生成jwt token\n     */\n    public String generateToken(String userId) {\n        // 过期时间\n        Date expireTime = new Date(System.currentTimeMillis() + expire * 1000);\n        String token = JWT.create()\n                .withSubject(userId)\n                .withExpiresAt(expireTime)\n                .sign(Algorithm.HMAC256(secret));\n        redisUtil.set(TOKEN_REFRESH + userId, token, checkRefreshExpire);\n        redisUtil.set(TOKEN_KEY + userId, token, expire);\n        return token;\n    }\n\n    public String getClaimByToken(String token) {\n        return JWT.decode(token)\n                .getSubject();\n    }\n\n    /**\n     * token是否合法&过期\n     * 抛异常\n     */\n    public boolean verifyToken(String token) {\n        try {\n            JWT.require(Algorithm.HMAC256(secret))\n                    .build()\n                    .verify(token);\n        } catch (JWTVerificationException e) {\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/utils/MyFileUtil.java",
    "content": "package com.simplefanc.voj.backend.common.utils;\n\nimport cn.hutool.core.io.file.FileReader;\nimport cn.hutool.core.util.CharsetUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.common.result.ResultStatus;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.net.URLEncoder;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author chenfan\n * @date 2022/5/20 21:06\n **/\n@Slf4j\n@UtilityClass\npublic class MyFileUtil {\n    public String getFileSuffix(MultipartFile file){\n        return file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(\".\") + 1);\n    }\n\n    public void download(HttpServletResponse response, String filePath, String fileName, String errMsg) {\n        try(// 放到缓冲流里面\n            BufferedInputStream bins = new BufferedInputStream(new FileReader(filePath).getInputStream());\n            // 获取文件输出IO流\n            BufferedOutputStream bouts = new BufferedOutputStream(response.getOutputStream())\n        ) {\n            response.setContentType(\"application/x-download\");\n            response.setHeader(\"Content-disposition\", \"attachment;filename=\" + URLEncoder.encode(fileName, CharsetUtil.UTF_8));\n            int bytesRead = 0;\n            byte[] buffer = new byte[1024 * 10];\n            // 开始向网络传输文件流\n            while ((bytesRead = bins.read(buffer, 0, 1024 * 10)) != -1) {\n                bouts.write(buffer, 0, bytesRead);\n            }\n            bouts.flush();\n        } catch (IOException e) {\n            log.error(errMsg + \"------------>\", e);\n            response.reset();\n            response.setContentType(\"application/json\");\n            response.setCharacterEncoding(CharsetUtil.UTF_8);\n            Map<String, Object> map = new HashMap<>();\n            map.put(\"status\", ResultStatus.SYSTEM_ERROR);\n            map.put(\"msg\", errMsg);\n            map.put(\"data\", null);\n            try {\n                response.getWriter().println(JSONUtil.toJsonStr(map));\n            } catch (IOException ioException) {\n                ioException.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/utils/RedisUtil.java",
    "content": "package com.simplefanc.voj.backend.common.utils;\n\nimport cn.hutool.core.collection.CollUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.dao.DataAccessException;\nimport org.springframework.data.redis.connection.RedisConnection;\nimport org.springframework.data.redis.connection.StringRedisConnection;\nimport org.springframework.data.redis.core.RedisCallback;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.stereotype.Component;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/23 23:48\n * @Description:\n */\n@Component\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic final class RedisUtil {\n\n    private final RedisTemplate<String, Object> redisTemplate;\n\n    // =============================common============================\n\n    public boolean getLock(String lockName, int expireTime) {\n        boolean result = false;\n        try {\n            boolean isExist = hasKey(lockName);\n            if (!isExist) {\n                set(lockName, 0);\n                expire(lockName, expireTime <= 0 ? 3600 : expireTime);\n            }\n            long reVal = incr(lockName, 1);\n            if (1 == reVal) {\n                // 获取到锁\n                result = true;\n            }\n        } catch (Exception e) {\n            log.error(\"获取锁过程出错-->\", e);\n        }\n        return result;\n    }\n\n    public boolean releaseLock(String lockName) {\n\n        return expire(lockName, 10);\n    }\n\n    /**\n     * 指定缓存失效时间\n     *\n     * @param key  键\n     * @param time 时间(秒)\n     */\n    public boolean expire(String key, long time) {\n        try {\n            if (time > 0) {\n                redisTemplate.expire(key, time, TimeUnit.SECONDS);\n            }\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 根据key 获取过期时间\n     *\n     * @param key 键 不能为null\n     * @return 时间(秒) 返回0代表为永久有效\n     */\n    public long getExpire(String key) {\n        return redisTemplate.getExpire(key, TimeUnit.SECONDS);\n    }\n\n    /**\n     * 判断key是否存在\n     *\n     * @param key 键\n     * @return true 存在 false不存在\n     */\n    public boolean hasKey(String key) {\n        try {\n            return redisTemplate.hasKey(key);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 删除缓存\n     *\n     * @param key 可以传一个值 或多个\n     */\n    @SuppressWarnings(\"unchecked\")\n    public void del(String... key) {\n        if (key != null && key.length > 0) {\n            if (key.length == 1) {\n                redisTemplate.delete(key[0]);\n            } else {\n                redisTemplate.delete(CollUtil.newArrayList(key));\n            }\n        }\n    }\n\n    // ============================String=============================\n\n    /**\n     * 普通缓存获取\n     *\n     * @param key 键\n     * @return 值\n     */\n    public Object get(String key) {\n        return key == null ? null : redisTemplate.opsForValue().get(key);\n    }\n\n    public <T> T get(String key, Class<? extends T> clazz) {\n        return key == null ? null : (T) redisTemplate.opsForValue().get(key);\n    }\n\n    /**\n     * 普通缓存放入\n     *\n     * @param key   键\n     * @param value 值\n     * @return true成功 false失败\n     */\n\n    public boolean set(String key, Object value) {\n        try {\n            redisTemplate.opsForValue().set(key, value);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 普通缓存放入并设置时间\n     *\n     * @param key   键\n     * @param value 值\n     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期\n     * @return true成功 false 失败\n     */\n\n    public boolean set(String key, Object value, long time) {\n        try {\n            if (time > 0) {\n                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);\n            } else {\n                set(key, value);\n            }\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 递增\n     *\n     * @param key   键\n     * @param delta 要增加几(大于0)\n     */\n    public long incr(String key, long delta) {\n        if (delta < 0) {\n            throw new RuntimeException(\"递增因子必须大于0\");\n        }\n        return redisTemplate.opsForValue().increment(key, delta);\n    }\n\n    /**\n     * 递减\n     *\n     * @param key   键\n     * @param delta 要减少几(小于0)\n     */\n    public long decr(String key, long delta) {\n        if (delta < 0) {\n            throw new RuntimeException(\"递减因子必须大于0\");\n        }\n        return redisTemplate.opsForValue().increment(key, -delta);\n    }\n\n    // ================================Map=================================\n\n    /**\n     * HashGet\n     *\n     * @param key  键 不能为null\n     * @param item 项 不能为null\n     */\n    public Object hget(String key, String item) {\n        return redisTemplate.opsForHash().get(key, item);\n    }\n\n    /**\n     * 获取hashKey对应的所有键值\n     *\n     * @param key 键\n     * @return 对应的多个键值\n     */\n    public Map<Object, Object> hmget(String key) {\n        return redisTemplate.opsForHash().entries(key);\n    }\n\n    /**\n     * HashSet\n     *\n     * @param key 键\n     * @param map 对应多个键值\n     */\n    public boolean hmset(String key, Map<String, Object> map) {\n        try {\n            redisTemplate.opsForHash().putAll(key, map);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * HashSet 并设置时间\n     *\n     * @param key  键\n     * @param map  对应多个键值\n     * @param time 时间(秒)\n     * @return true成功 false失败\n     */\n    public boolean hmset(String key, Map<String, Object> map, long time) {\n        try {\n            redisTemplate.opsForHash().putAll(key, map);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 向一张hash表中放入数据,如果不存在将创建\n     *\n     * @param key   键\n     * @param item  项\n     * @param value 值\n     * @return true 成功 false失败\n     */\n    public boolean hset(String key, String item, Object value) {\n        try {\n            redisTemplate.opsForHash().put(key, item, value);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 向一张hash表中放入数据,如果不存在将创建\n     *\n     * @param key   键\n     * @param item  项\n     * @param value 值\n     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间\n     * @return true 成功 false失败\n     */\n    public boolean hset(String key, String item, Object value, long time) {\n        try {\n            redisTemplate.opsForHash().put(key, item, value);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 删除hash表中的值\n     *\n     * @param key  键 不能为null\n     * @param item 项 可以使多个 不能为null\n     */\n    public void hdel(String key, Object... item) {\n        redisTemplate.opsForHash().delete(key, item);\n    }\n\n    /**\n     * 判断hash表中是否有该项的值\n     *\n     * @param key  键 不能为null\n     * @param item 项 不能为null\n     * @return true 存在 false不存在\n     */\n    public boolean hHasKey(String key, String item) {\n        return redisTemplate.opsForHash().hasKey(key, item);\n    }\n\n    /**\n     * hash递增 如果不存在,就会创建一个 并把新增后的值返回\n     *\n     * @param key  键\n     * @param item 项\n     * @param by   要增加几(大于0)\n     */\n    public double hincr(String key, String item, double by) {\n        return redisTemplate.opsForHash().increment(key, item, by);\n    }\n\n    /**\n     * hash递减\n     *\n     * @param key  键\n     * @param item 项\n     * @param by   要减少记(小于0)\n     */\n    public double hdecr(String key, String item, double by) {\n        return redisTemplate.opsForHash().increment(key, item, -by);\n    }\n\n    // ============================set=============================\n\n    /**\n     * 根据key获取Set中的所有值\n     *\n     * @param key 键\n     */\n    public Set<Object> sGet(String key) {\n        try {\n            return redisTemplate.opsForSet().members(key);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * 根据value从一个set中查询,是否存在\n     *\n     * @param key   键\n     * @param value 值\n     * @return true 存在 false不存在\n     */\n    public boolean sHasKey(String key, Object value) {\n        try {\n            return redisTemplate.opsForSet().isMember(key, value);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 将数据放入set缓存\n     *\n     * @param key    键\n     * @param values 值 可以是多个\n     * @return 成功个数\n     */\n    public long sSet(String key, Object... values) {\n        try {\n            return redisTemplate.opsForSet().add(key, values);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return 0;\n        }\n    }\n\n    /**\n     * 将set数据放入缓存\n     *\n     * @param key    键\n     * @param time   时间(秒)\n     * @param values 值 可以是多个\n     * @return 成功个数\n     */\n    public long sSetAndTime(String key, long time, Object... values) {\n        try {\n            Long count = redisTemplate.opsForSet().add(key, values);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return count;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return 0;\n        }\n    }\n\n    /**\n     * 获取set缓存的长度\n     *\n     * @param key 键\n     */\n    public long sGetSetSize(String key) {\n        try {\n            return redisTemplate.opsForSet().size(key);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return 0;\n        }\n    }\n\n    /**\n     * 移除值为value的\n     *\n     * @param key    键\n     * @param values 值 可以是多个\n     * @return 移除的个数\n     */\n\n    public long setRemove(String key, Object... values) {\n        try {\n            Long count = redisTemplate.opsForSet().remove(key, values);\n            return count;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return 0;\n        }\n    }\n\n    // ===============================list=================================\n\n    /**\n     * 获取list缓存的内容\n     *\n     * @param key   键\n     * @param start 开始\n     * @param end   结束 0 到 -1代表所有值\n     */\n    public List<Object> lGet(String key, long start, long end) {\n        try {\n            return redisTemplate.opsForList().range(key, start, end);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * 获取list缓存的长度\n     *\n     * @param key 键\n     */\n    public long lGetListSize(String key) {\n        try {\n            return redisTemplate.opsForList().size(key);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return 0;\n        }\n    }\n\n    /**\n     * 通过索引 获取list中的值\n     *\n     * @param key   键\n     * @param index 索引 index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推\n     */\n    public Object lGetIndex(String key, long index) {\n        try {\n            return redisTemplate.opsForList().index(key, index);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * 将list右边放入缓存\n     *\n     * @param key   键\n     * @param value 值\n     */\n    public boolean lrPush(String key, Object value) {\n        try {\n            redisTemplate.opsForList().rightPush(key, value);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 将list左边放入缓存\n     *\n     * @param key   键\n     * @param value 值\n     */\n    public boolean llPush(String key, Object value) {\n        try {\n            redisTemplate.opsForList().leftPush(key, value);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 将list右边放入缓存\n     *\n     * @param key   键\n     * @param value 值\n     * @param time  时间(秒)\n     */\n    public boolean lrPush(String key, Object value, long time) {\n        try {\n            redisTemplate.opsForList().rightPush(key, value);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n\n    }\n\n    /**\n     * 将list放入缓存\n     *\n     * @param key   键\n     * @param value 值\n     * @return\n     */\n    public boolean lrPush(String key, List<Object> value) {\n        try {\n            redisTemplate.opsForList().rightPushAll(key, value);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n\n    }\n\n    public boolean llPushList(String key, List<Object> value) {\n        try {\n            redisTemplate.opsForList().leftPushAll(key, value);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n\n    }\n\n    public Object lrPop(String key) {\n        try {\n            return redisTemplate.opsForList().rightPop(key, 10, TimeUnit.SECONDS);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * 将list放入缓存\n     *\n     * @param key   键\n     * @param value 值\n     * @param time  时间(秒)\n     * @return\n     */\n    public boolean llPush(String key, List<Object> value, long time) {\n        try {\n            redisTemplate.opsForList().rightPushAll(key, value);\n            if (time > 0) {\n                expire(key, time);\n            }\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 根据索引修改list中的某条数据\n     *\n     * @param key   键\n     * @param index 索引\n     * @param value 值\n     * @return\n     */\n\n    public boolean lUpdateIndex(String key, long index, Object value) {\n        try {\n            redisTemplate.opsForList().set(key, index, value);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 移除N个值为value\n     *\n     * @param key   键\n     * @param count 移除多少个\n     * @param value 值\n     * @return 移除的个数\n     */\n\n    public long lRemove(String key, long count, Object value) {\n        try {\n            Long remove = redisTemplate.opsForList().remove(key, count, value);\n            return remove;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return 0;\n        }\n\n    }\n\n    /**\n     * 给特定频道发布消息\n     *\n     * @param channel 管道主题\n     * @param message 消息\n     * @return\n     */\n    public void sendMessage(String channel, Object message) {\n        redisTemplate.convertAndSend(channel, message);\n    }\n\n\n    /**\n     * 批量查询优化：pipeline\n     *\n     * @param keys\n     * @return\n     */\n    public List<Object> batchGet(List<String> keys) {\n        return redisTemplate.executePipelined(new RedisCallback<Object>() {\n            @Override\n            public Object doInRedis(RedisConnection connection) throws DataAccessException {\n                StringRedisConnection src = (StringRedisConnection) connection;\n                for (String k : keys) {\n                    src.get(k);\n                }\n                return null;\n            }\n        });\n    }\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/common/utils/RestTemplateUtil.java",
    "content": "package com.simplefanc.voj.backend.common.utils;\n\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.net.URI;\n\n/**\n * @author chenfan\n * @date 2022/10/14 11:54\n **/\n@Component\n@RequiredArgsConstructor\npublic class RestTemplateUtil {\n    private final RestTemplate restTemplate;\n\n    public <T> T get(URI uri, String path, Class<T> clazz) {\n        return restTemplate.getForObject(uri + path, clazz);\n    }\n\n    public <T> T get(String uri, String path, Class<T> clazz) {\n        return restTemplate.getForObject(\"http://\" + uri + path, clazz);\n    }\n\n    /**\n     * @param uri     http://ip:port\n     * @param path\n     * @param request\n     * @param clazz\n     * @param <T>\n     * @return\n     */\n    public <T> T post(String uri, String path, Object request, Class<T> clazz) {\n//        String uri = String.format(\"%s://%s:%s\", scheme, instance.getHost(), instance.getPort());\n        return restTemplate.postForObject(\"http://\" + uri + path, request, clazz);\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/CacheConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport com.simplefanc.voj.backend.cache.DoubleCacheManager;\nimport com.simplefanc.voj.backend.config.property.DoubleCacheProperties;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.core.RedisTemplate;\n\n/**\n * @author: chenfan\n * @create: 2022-10-6 10:07\n **/\n//@Configuration\npublic class CacheConfig {\n//    @Autowired\n    DoubleCacheProperties doubleCacheConfig;\n\n//    @Bean\n    public DoubleCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate,\n                                           DoubleCacheProperties doubleCacheConfig) {\n        return new DoubleCacheManager(redisTemplate, doubleCacheConfig);\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/CommonAsyncTaskConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.annotation.AsyncConfigurer;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\n\nimport java.lang.reflect.Method;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ThreadPoolExecutor;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/6 23:36\n * @Description: 通用异步线程池\n */\n@Configuration\n@Slf4j(topic = \"voj\")\npublic class CommonAsyncTaskConfig implements AsyncConfigurer {\n\n    @Override\n    public Executor getAsyncExecutor() {\n        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();\n        // 线程池维护线程的最少数量\n        taskExecutor.setCorePoolSize(10);\n        // 线程池维护线程的最大数量\n        taskExecutor.setMaxPoolSize(20);\n        // 缓存队列\n        taskExecutor.setQueueCapacity(200);\n        // 活跃时间\n        taskExecutor.setKeepAliveSeconds(3);\n        // 对拒绝task的处理策略\n        // (1) 默认的ThreadPoolExecutor.AbortPolicy 处理程序遭到拒绝将抛出运行时RejectedExecutionException;\n        // (2) ThreadPoolExecutor.CallerRunsPolicy 线程调用运行该任务的 execute\n        // 本身。此策略提供简单的反馈控制机制，能够减缓新任务的提交速度\n        // (3) ThreadPoolExecutor.DiscardPolicy 不能执行的任务将被删除;\n        // (4) ThreadPoolExecutor.DiscardOldestPolicy\n        // 如果执行程序尚未关闭，则位于工作队列头部的任务将被删除，然后重试执行程序（如果再次失败，则重复此过程）\n        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());\n        // 线程名前缀,方便排查问题\n        taskExecutor.setThreadNamePrefix(\"CommonExecutor-\");\n        // 注意一定要初始化\n        taskExecutor.initialize();\n\n        return taskExecutor;\n\n    }\n\n    /**\n     * 异步任务中异常处理\n     *\n     * @return\n     */\n    @Override\n    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {\n        return new AsyncUncaughtExceptionHandler() {\n\n            @Override\n            public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) {\n                log.error(\"==========================\" + arg0.getMessage() + \"=======================\", arg0);\n                log.error(\"exception method:\" + arg1.getName());\n            }\n        };\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/ConfigVO.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport lombok.Data;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.stereotype.Component;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/2 21:30\n * @Description:\n */\n@RefreshScope\n@Data\n@Component\npublic class ConfigVO {\n\n    /**\n     * 数据库配置\n     */\n    @Value(\"${voj.db.username}\")\n    private String mysqlUsername;\n\n    @Value(\"${voj.db.password}\")\n    private String mysqlPassword;\n\n    @Value(\"${voj.db.name}\")\n    private String mysqlDbName;\n\n    @Value(\"${voj.db.host}\")\n    private String mysqlHost;\n\n    @Value(\"${voj.db.public-host}\")\n    private String mysqlPublicHost;\n\n    @Value(\"${voj.db.port}\")\n    private Integer mysqlPort;\n\n    /**\n     * 判题服务token\n     */\n    @Value(\"${voj.judge.token}\")\n    private String judgeToken;\n\n    /**\n     * 缓存配置\n     */\n    @Value(\"${voj.redis.host}\")\n    private String redisHost;\n\n    @Value(\"${voj.redis.port}\")\n    private Integer redisPort;\n\n    @Value(\"${voj.redis.password}\")\n    private String redisPassword;\n\n    /**\n     * jwt配置\n     */\n    @Value(\"${voj.jwt.secret}\")\n    private String tokenSecret;\n\n    @Value(\"${voj.jwt.expire}\")\n    private String tokenExpire;\n\n    @Value(\"${voj.jwt.checkRefreshExpire}\")\n    private String checkRefreshExpire;\n\n    /**\n     * 邮箱配置\n     */\n    @Value(\"${voj.mail.username}\")\n    private String emailUsername;\n\n    @Value(\"${voj.mail.password}\")\n    private String emailPassword;\n\n    @Value(\"${voj.mail.host}\")\n    private String emailHost;\n\n    @Value(\"${voj.mail.port}\")\n    private Integer emailPort;\n\n    @Value(\"${voj.mail.ssl}\")\n    private Boolean emailSsl;\n\n    /**\n     * 网站前端显示配置\n     */\n    @Value(\"${voj.web-config.base-url}\")\n    private String baseUrl;\n\n    @Value(\"${voj.web-config.name}\")\n    private String name;\n\n    @Value(\"${voj.web-config.short-name}\")\n    private String shortName;\n\n    @Value(\"${voj.web-config.description}\")\n    private String description;\n\n    @Value(\"${voj.web-config.footer.record.name}\")\n    private String recordName;\n\n    @Value(\"${voj.web-config.footer.record.url}\")\n    private String recordUrl;\n\n    @Value(\"${voj.web-config.footer.project.name}\")\n    private String projectName;\n\n    @Value(\"${voj.web-config.footer.project.url}\")\n    private String projectUrl;\n\n    /**\n     * 系统开关\n     */\n    @Value(\"${voj.switch.register}\")\n    private Boolean register;\n\n    @Value(\"${voj.switch.problem}\")\n    private Boolean problem;\n\n    @Value(\"${voj.switch.training}\")\n    private Boolean training;\n\n    @Value(\"${voj.switch.contest}\")\n    private Boolean contest;\n\n    @Value(\"${voj.switch.status}\")\n    private Boolean status;\n\n    @Value(\"${voj.switch.rank}\")\n    private Boolean rank;\n\n    @Value(\"${voj.switch.discussion}\")\n    private Boolean discussion;\n\n    @Value(\"${voj.switch.introduction}\")\n    private Boolean introduction;\n\n    /**\n     * 是否开启公开评论区\n     */\n//    @Value(\"${voj.switch.discussion.public:true}\")\n//    private Boolean openPublicDiscussion;\n\n    /**\n     * 是否开启比赛讨论区\n     */\n//    @Value(\"${voj.switch.discussion.contest:true}\")\n//    private Boolean openContestComment;\n\n\n    /**\n     * 是否开启公开评测\n     */\n    @Value(\"${voj.switch.judge.public:true}\")\n    private Boolean openPublicJudge;\n\n    /**\n     * 是否开启比赛评测\n     */\n    @Value(\"${voj.switch.judge.contest:true}\")\n    private Boolean openContestJudge;\n\n    @Value(\"${voj.switch.judge.code-visible-start-time}\")\n    private Long codeVisibleStartTime;\n\n    /**\n     * 非比赛的提交间隔秒数\n     */\n    @Value(\"${voj.switch.judge.submit-interval:8}\")\n    private Integer defaultSubmitInterval;\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/CorsConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.CorsRegistry;\nimport org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport java.io.File;\n\n/**\n * 解决跨域问题\n */\n@Configuration\n@RequiredArgsConstructor\npublic class CorsConfig implements WebMvcConfigurer {\n\n    private final FilePathProperties filePathProps;\n\n    @Override\n    public void addCorsMappings(CorsRegistry registry) {\n        registry.addMapping(\"/**\")\n                .allowedOriginPatterns(\"*\")\n                .allowedMethods(\"*\")\n                .allowedHeaders(\"*\")\n                .allowCredentials(true)\n                .maxAge(3600);\n    }\n\n    /**\n     * 前端直接通过/public/img/图片名称即可拿到\n     *\n     * @param registry\n     */\n    @Override\n    public void addResourceHandlers(ResourceHandlerRegistry registry) {\n        // /api/public/img/** /api/public/file/**\n        registry.addResourceHandler(filePathProps.getImgApi() + \"**\", filePathProps.getFileApi() + \"**\")\n                .addResourceLocations(\"file:\" + filePathProps.getUserAvatarFolder() + File.separator,\n                        \"file:\" + filePathProps.getMarkdownFileFolder() + File.separator,\n                        \"file:\" + filePathProps.getHomeCarouselFolder() + File.separator,\n                        \"file:\" + filePathProps.getProblemFileFolder() + File.separator);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/DruidConfiguration.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport com.alibaba.druid.pool.DruidDataSource;\nimport lombok.Data;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/21 17:57\n * @Description:\n */\n@Configuration\n@RefreshScope\n@Data\npublic class DruidConfiguration {\n\n    @Value(\"${voj.db.username:${MYSQL_USERNAME:root}}\")\n    private String username;\n\n    @Value(\"${voj.db.password:${MYSQL_ROOT_PASSWORD:voj123456}}\")\n    private String password;\n\n    @Value(\"${voj.db.host:${MYSQL_HOST:172.20.0.3}}\")\n    private String host;\n\n    @Value(\"${voj.db.port:${MYSQL_PORT:3306}}\")\n    private Integer port;\n\n    @Value(\"${voj.db.name:${MYSQL_DATABASE_NAME:voj}}\")\n    private String name;\n\n    @Value(\"${spring.datasource.driver-class-name}\")\n    private String driverClassName;\n\n    @Value(\"${spring.datasource.initial-size}\")\n    private Integer initialSize;\n\n    @Value(\"${spring.datasource.min-idle}\")\n    private Integer minIdle;\n\n    @Value(\"${spring.datasource.maxActive}\")\n    private Integer maxActive;\n\n    @Value(\"${spring.datasource.maxWait}\")\n    private Integer maxWait;\n\n    @Bean(name = \"datasource\")\n    @RefreshScope\n    public DruidDataSource dataSource() {\n        DruidDataSource datasource = new DruidDataSource();\n        String url = \"jdbc:mysql://\" + host + \":\" + port + \"/\" + name\n                + \"?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true\";\n        datasource.setUrl(url);\n        datasource.setUsername(username);\n        datasource.setPassword(password);\n        datasource.setDriverClassName(driverClassName);\n        datasource.setMaxActive(maxActive);\n        datasource.setInitialSize(initialSize);\n        datasource.setMinIdle(minIdle);\n        datasource.setMaxWait(maxWait);\n        return datasource;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/JudgeAsyncTaskConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.annotation.EnableAsync;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\n\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ThreadPoolExecutor;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/24 16:54\n * @Description: 专用于判题的异步线程池\n */\n@Configuration\npublic class JudgeAsyncTaskConfig {\n\n    @Bean\n    public Executor judgeTaskAsyncPool() {\n        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\n        // 核心线程池大小\n        executor.setCorePoolSize(2);\n        // 最大线程数\n        executor.setMaxPoolSize(10);\n        // 队列容量\n        executor.setQueueCapacity(500);\n        // 活跃时间\n        executor.setKeepAliveSeconds(3);\n        // 线程名字前缀\n        executor.setThreadNamePrefix(\"JudgeExecutor-\");\n\n        // setRejectedExecutionHandler：当pool已经达到max size的时候，如何处理新任务\n        // CallerRunsPolicy：不在新线程中执行任务，而是由调用者所在的线程来执行\n        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());\n        executor.initialize();\n        return executor;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/MyMetaObjectConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;\nimport org.apache.ibatis.reflection.MetaObject;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/4 14:14\n * @Description: 处理mybatis-plus自动填充时间\n */\n@Component\npublic class MyMetaObjectConfig implements MetaObjectHandler {\n\n    @Override\n    public void insertFill(MetaObject metaObject) {\n        this.setFieldValByName(\"gmtCreate\", new Date(), metaObject);\n        this.setFieldValByName(\"gmtModified\", new Date(), metaObject);\n    }\n\n    @Override\n    public void updateFill(MetaObject metaObject) {\n        this.setFieldValByName(\"gmtModified\", new Date(), metaObject);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/MybatisPlusConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;\nimport org.mybatis.spring.annotation.MapperScan;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.transaction.annotation.EnableTransactionManagement;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/19 21:04\n * @Description:\n */\n@Configuration\n@EnableTransactionManagement\n@MapperScan(\"com.simplefanc.voj.backend.mapper\")\npublic class MybatisPlusConfig {\n\n    /**\n     * 注册乐观锁插件\n     *\n     * @return\n     */\n    @Bean\n    public OptimisticLockerInterceptor optimisticLockerInterceptor() {\n        return new OptimisticLockerInterceptor();\n    }\n\n    /**\n     * 分页插件\n     *\n     * @return\n     */\n    @Bean\n    public PaginationInterceptor paginationInterceptor() {\n        return new PaginationInterceptor();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/NacosConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport com.alibaba.cloud.nacos.NacosDiscoveryProperties;\nimport com.simplefanc.voj.common.utils.IpUtil;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Primary;\n\n/**\n * @Author: chenfan\n * @Date: 2022/10/14\n * @Description:\n */\n@Configuration\npublic class NacosConfig {\n\n    @Value(\"${voj-backend.ip}\")\n    private String ip;\n\n    @Value(\"${voj-backend.port}\")\n    private Integer port;\n\n    /**\n     * 用于改变程序自动获取的本机ip\n     */\n    @Bean\n    @Primary\n    public NacosDiscoveryProperties nacosProperties() {\n        NacosDiscoveryProperties nacosDiscoveryProperties = new NacosDiscoveryProperties();\n        // 此处只改了ip，其他参数可以根据自己的需求改变\n        nacosDiscoveryProperties.setIp(IpUtil.getServiceIp());\n        if (!\"-1\".equals(ip)) {\n            nacosDiscoveryProperties.setIp(ip);\n        }\n        nacosDiscoveryProperties.setPort(port);\n\n        return nacosDiscoveryProperties;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/RedisConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport com.fasterxml.jackson.annotation.JsonAutoDetect;\nimport com.fasterxml.jackson.annotation.PropertyAccessor;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;\nimport org.springframework.data.redis.serializer.RedisSerializer;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/23 23:47\n * @Description:\n */\n@Configuration\npublic class RedisConfig {\n\n    @Bean\n    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {\n        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();\n        redisTemplate.setConnectionFactory(redisConnectionFactory);\n        redisTemplate.setKeySerializer(redisKeySerializer());\n        redisTemplate.setHashKeySerializer(redisKeySerializer());\n        redisTemplate.setValueSerializer(redisValueSerializer());\n        redisTemplate.setHashValueSerializer(redisValueSerializer());\n        redisTemplate.afterPropertiesSet();\n        return redisTemplate;\n    }\n\n    @Bean\n    public RedisSerializer<String> redisKeySerializer() {\n        return RedisSerializer.string();\n    }\n\n    @Bean\n    public RedisSerializer<Object> redisValueSerializer() {\n        // 创建JSON序列化器\n        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);\n        ObjectMapper objectMapper = new ObjectMapper();\n        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);\n        // 必须设置，否则无法将JSON转化为对象，会转化成Map类型\n        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);\n        serializer.setObjectMapper(objectMapper);\n        return serializer;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/RestTemplateConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.client.ClientHttpRequestFactory;\nimport org.springframework.http.client.SimpleClientHttpRequestFactory;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/19 22:47\n * @Description:\n */\n@Configuration\npublic class RestTemplateConfig {\n\n    @Bean\n    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {\n        return new RestTemplate(factory);\n    }\n\n    @Bean\n    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {\n        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();\n        // 单位为ms\n        factory.setReadTimeout(600000);\n        // 单位为ms\n        factory.setConnectTimeout(50000);\n        return factory;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/ShiroConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport com.simplefanc.voj.backend.shiro.AccountRealm;\nimport com.simplefanc.voj.backend.shiro.JwtFilter;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.mgt.DefaultSessionStorageEvaluator;\nimport org.apache.shiro.mgt.DefaultSubjectDAO;\nimport org.apache.shiro.mgt.SecurityManager;\nimport org.apache.shiro.session.mgt.SessionManager;\nimport org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;\nimport org.apache.shiro.spring.web.ShiroFilterFactoryBean;\nimport org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;\nimport org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;\nimport org.apache.shiro.web.mgt.DefaultWebSecurityManager;\nimport org.apache.shiro.web.session.mgt.DefaultWebSessionManager;\nimport org.crazycake.shiro.RedisCacheManager;\nimport org.crazycake.shiro.RedisSessionDAO;\nimport org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport javax.servlet.Filter;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * shiro启用注解拦截控制器\n */\n@Configuration\n@RequiredArgsConstructor\npublic class ShiroConfig {\n\n    private final JwtFilter jwtFilter;\n\n    @Bean\n    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {\n        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();\n        creator.setProxyTargetClass(true);\n        return creator;\n    }\n\n    @Bean\n    public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {\n        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();\n        sessionManager.setSessionDAO(redisSessionDAO);\n        return sessionManager;\n    }\n\n    @Bean\n    public DefaultWebSecurityManager securityManager(AccountRealm accountRealm, SessionManager sessionManager,\n                                                     RedisCacheManager redisCacheManager) {\n        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);\n        securityManager.setSessionManager(sessionManager);\n        securityManager.setCacheManager(redisCacheManager);\n        // 关闭shiro自带的session，详情见文档\n        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();\n        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();\n        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);\n        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);\n        securityManager.setSubjectDAO(subjectDAO);\n        return securityManager;\n    }\n\n    @Bean(\"shiroFilterFactoryBean\")\n    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,\n                                                         ShiroFilterChainDefinition shiroFilterChainDefinition) {\n        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();\n        shiroFilter.setSecurityManager(securityManager);\n\n        Map<String, Filter> filters = new HashMap<>();\n        filters.put(\"jwt\", jwtFilter);\n        shiroFilter.setFilters(filters);\n\n        shiroFilter.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());\n        return shiroFilter;\n    }\n\n    @Bean\n    public ShiroFilterChainDefinition shiroFilterChainDefinition() {\n        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();\n        Map<String, String> filterMap = new LinkedHashMap<>();\n        // 主要通过注解方式校验权限\n        filterMap.put(\"/**\", \"jwt\");\n        chainDefinition.addPathDefinitions(filterMap);\n        return chainDefinition;\n    }\n\n    // 开启注解代理\n    @Bean\n    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {\n        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();\n        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);\n        return authorizationAttributeSourceAdvisor;\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/StartupRunner.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport cn.hutool.core.util.IdUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.dao.judge.RemoteJudgeAccountEntityService;\nimport com.simplefanc.voj.backend.config.property.RemoteAccountProperties;\nimport com.simplefanc.voj.backend.service.admin.system.ConfigService;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.stereotype.Component;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/2/19 22:11\n * @Description: 项目启动后，初始化运行该 run 方法，根据 .env 中自定义配置修改 Nacos 配置\n */\n@Component\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class StartupRunner implements CommandLineRunner {\n\n    private final ConfigVO configVO;\n\n    private final ConfigService configService;\n\n    private final RemoteJudgeAccountEntityService remoteJudgeAccountEntityService;\n\n    @Value(\"${OPEN_REMOTE_JUDGE:true}\")\n    private String openRemoteJudge;\n\n    /**\n     * jwt配置\n     */\n    @Value(\"${JWT_TOKEN_SECRET:default}\")\n    private String tokenSecret;\n\n    @Value(\"${JWT_TOKEN_EXPIRE:86400}\")\n    private String tokenExpire;\n\n    @Value(\"${JWT_TOKEN_FRESH_EXPIRE:43200}\")\n    private String checkRefreshExpire;\n\n    /**\n     * 数据库配置\n     */\n    @Value(\"${MYSQL_USERNAME:root}\")\n    private String mysqlUsername;\n\n    @Value(\"${MYSQL_ROOT_PASSWORD:root}\")\n    private String mysqlPassword;\n\n    @Value(\"${MYSQL_DATABASE_NAME:voj}\")\n    private String mysqlDbName;\n\n    @Value(\"${MYSQL_HOST:172.20.0.3}\")\n    private String mysqlHost;\n\n    @Value(\"${MYSQL_PUBLIC_HOST:172.20.0.3}\")\n    private String mysqlPublicHost;\n\n    @Value(\"${MYSQL_PORT:3306}\")\n    private Integer mysqlPort;\n\n    /**\n     * 缓存配置\n     */\n    @Value(\"${REDIS_HOST:172.20.0.2}\")\n    private String redisHost;\n\n    @Value(\"${REDIS_PORT:6379}\")\n    private Integer redisPort;\n\n    @Value(\"${REDIS_PASSWORD:}\")\n    private String redisPassword;\n\n    /**\n     * 判题服务token\n     */\n    @Value(\"${JUDGE_TOKEN:default}\")\n    private String judgeToken;\n\n    /**\n     * 邮箱配置\n     */\n    @Value(\"${EMAIL_USERNAME:your_email_username}\")\n    private String emailUsername;\n\n    @Value(\"${EMAIL_PASSWORD:your_email_password}\")\n    private String emailPassword;\n\n    @Value(\"${EMAIL_SERVER_HOST:your_email_host}\")\n    private String emailHost;\n\n    @Value(\"${EMAIL_SERVER_PORT:465}\")\n    private Integer emailPort;\n\n    @Value(\"${spring.profiles.active}\")\n    private String profile;\n\n    private final RemoteAccountProperties remoteAccountProps;\n\n    @Override\n    public void run(String... args) {\n        if (\"dev\".equals(profile)) {\n            return;\n        }\n\n        // 动态修改nacos上的配置文件\n        if (\"default\".equals(judgeToken)) {\n            configVO.setJudgeToken(IdUtil.fastSimpleUUID());\n        } else {\n            configVO.setJudgeToken(judgeToken);\n        }\n\n        if (\"default\".equals(tokenSecret)) {\n            configVO.setTokenSecret(IdUtil.fastSimpleUUID());\n        } else {\n            configVO.setTokenSecret(tokenSecret);\n        }\n        configVO.setTokenExpire(tokenExpire);\n        configVO.setCheckRefreshExpire(checkRefreshExpire);\n\n        configVO.setMysqlUsername(mysqlUsername);\n        configVO.setMysqlPassword(mysqlPassword);\n        configVO.setMysqlHost(mysqlHost);\n        configVO.setMysqlPublicHost(mysqlPublicHost);\n        configVO.setMysqlPort(mysqlPort);\n        configVO.setMysqlDbName(mysqlDbName);\n\n        configVO.setRedisHost(redisHost);\n        configVO.setRedisPort(redisPort);\n        configVO.setRedisPassword(redisPassword);\n\n        configVO.setEmailHost(emailHost);\n        configVO.setEmailPort(emailPort);\n        configVO.setEmailUsername(emailUsername);\n        configVO.setEmailPassword(emailPassword);\n\n        configService.sendNewConfigToNacos();\n\n        if (\"true\".equals(openRemoteJudge)) {\n            addRemoteJudgeAccountToDb();\n        }\n    }\n\n    private void addRemoteJudgeAccountToDb() {\n        // 初始化清空表\n        remoteJudgeAccountEntityService.remove(new QueryWrapper<>());\n        List<RemoteJudgeAccount> accountList = new LinkedList<>();\n        for (RemoteAccountProperties.RemoteOj remoteOj : remoteAccountProps.getOjs()) {\n            for (RemoteAccountProperties.Account account : remoteOj.getAccounts()) {\n                accountList.add(new RemoteJudgeAccount().setUsername(account.getUsername())\n                        .setPassword(account.getPassword()).setStatus(true).setVersion(0L).setOj(remoteOj.getOj()));\n            }\n        }\n        if (!remoteJudgeAccountEntityService.saveOrUpdateBatch(accountList)) {\n            log.error(\"RemoteJudgeAccount添加失败------------>{}\", \"请检查配置文件，然后重新启动！\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/SwaggerConfig.java",
    "content": "package com.simplefanc.voj.backend.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.core.env.Environment;\nimport org.springframework.core.env.Profiles;\nimport springfox.documentation.builders.RequestHandlerSelectors;\nimport springfox.documentation.service.ApiInfo;\nimport springfox.documentation.service.Contact;\nimport springfox.documentation.spi.DocumentationType;\nimport springfox.documentation.spring.web.plugins.Docket;\n\nimport java.util.ArrayList;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/29 22:28\n * @Description:\n */\n// @Configuration\n// @EnableSwagger2 //开启swagger2\npublic class SwaggerConfig {\n\n    @Bean\n    public Docket docket(Environment environment) {\n        // 设置要显示的swagger环境\n        // 线下环境\n        Profiles profiles = Profiles.of(\"dev\", \"test\");\n        // 通过环境判断是否在自己所设定的环境当中\n        boolean flag = environment.acceptsProfiles(profiles);\n        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())\n                // 分组\n                .groupName(\"chenfan\")\n                // 开启\n                .enable(flag).select()\n                // RequestHandlerSelectors扫描方式\n                // any()全部\n                // none 都不扫描\n                // path 过滤什么路径\n                .apis(RequestHandlerSelectors.basePackage(\"com.simplefanc.voj\")).build();\n    }\n\n    /**\n     * 配置swagger信息\n     *\n     * @return\n     */\n    private ApiInfo apiInfo() {\n        // 作者信息\n        Contact contact = new Contact(\"chenfan\", \"\", \"\");\n        return new ApiInfo(\"VOJ\", \"VOJ后台相关接口文档\", \"1.0\", \"\", contact, \"Apache 2.0\",\n                \"https://www.apache.org/licenses/LICENSE-2.0\", new ArrayList());\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/property/DoubleCacheProperties.java",
    "content": "package com.simplefanc.voj.backend.config.property;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\n/**\n * @program: double-cache\n * @author: chenfan\n * @create: 2022-10-6 10:07\n **/\n@Data\n@Component\n@ConfigurationProperties(prefix = \"voj.cache\")\npublic class DoubleCacheProperties {\n    private Boolean allowNull = true;\n    /**\n     * 缓存初始容量（能存储多少个缓存对象）\n     */\n    private Integer initialCapacity = 100;\n    /**\n     * 缓存最大容量\n     */\n    private Integer maximumSize = 1000;\n    /**\n     * 指定缓存的过期时间（写了之后多久过期）\n     */\n    private Long expireAfterWrite;\n    private Long expireAfterAccess;\n    private Long refreshAfterWrite;\n    private Long redisExpire;\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/property/EmailRuleBO.java",
    "content": "package com.simplefanc.voj.backend.config.property;\n\nimport lombok.Data;\nimport org.springframework.beans.factory.config.YamlPropertiesFactoryBean;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.context.annotation.PropertySource;\nimport org.springframework.core.env.PropertiesPropertySource;\nimport org.springframework.core.io.support.DefaultPropertySourceFactory;\nimport org.springframework.core.io.support.EncodedResource;\nimport org.springframework.lang.Nullable;\nimport org.springframework.stereotype.Component;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Properties;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/17 12:10\n * @Description: 邮箱规则类，读取email-rule.yml文件\n */\n\n@Component\n@PropertySource(value = \"classpath:email-rule.yml\", factory = CompositePropertySourceFactory.class)\n@ConfigurationProperties(prefix = \"voj\")\n@Data\npublic class EmailRuleBO {\n\n    private List<String> blackList;\n\n}\n\nclass CompositePropertySourceFactory extends DefaultPropertySourceFactory {\n\n    @Override\n    public org.springframework.core.env.PropertySource<?> createPropertySource(@Nullable String name,\n                                                                               EncodedResource resource) throws IOException {\n        String sourceName = Optional.ofNullable(name).orElse(resource.getResource().getFilename());\n        if (!resource.getResource().exists()) {\n            // return an empty Properties\n            return new PropertiesPropertySource(sourceName, new Properties());\n        } else if (sourceName.endsWith(\".yml\") || sourceName.endsWith(\".yaml\")) {\n            Properties propertiesFromYaml = loadYaml(resource);\n            return new PropertiesPropertySource(sourceName, propertiesFromYaml);\n        } else {\n            return super.createPropertySource(name, resource);\n        }\n    }\n\n    /**\n     * load yaml file to properties\n     *\n     * @param resource\n     * @return\n     * @throws IOException\n     */\n    private Properties loadYaml(EncodedResource resource) {\n        YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();\n        factory.setResources(resource.getResource());\n        factory.afterPropertiesSet();\n        return factory.getObject();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/property/FilePathProperties.java",
    "content": "package com.simplefanc.voj.backend.config.property;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\n/**\n * https://www.baeldung.com/spring-boot-yaml-list\n */\n@Component\n@ConfigurationProperties(prefix = \"filepath\")\n@Data\npublic class FilePathProperties {\n\n    private String userAvatarFolder;\n\n    /**\n     * 主页轮播图\n     */\n    private String homeCarouselFolder;\n\n    private String markdownFileFolder;\n\n    private String problemFileFolder;\n\n    private String contestTextPrintFolder;\n\n    private String imgApi;\n\n    private String fileApi;\n\n    private String testcaseTmpFolder;\n\n    private String testcaseBaseFolder;\n\n    private String fileDownloadTmpFolder;\n\n    private String contestAcSubmissionTmpFolder;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/config/property/RemoteAccountProperties.java",
    "content": "package com.simplefanc.voj.backend.config.property;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\nimport java.util.List;\n\n/**\n * https://www.baeldung.com/spring-boot-yaml-list\n */\n@Component\n@ConfigurationProperties(prefix = \"voj.remote\")\n@Data\npublic class RemoteAccountProperties {\n\n    private List<RemoteOj> ojs;\n\n    @Data\n    public static class RemoteOj {\n\n        private String oj;\n\n        private List<Account> accounts;\n\n    }\n\n    @Data\n    public static class Account {\n\n        private String username;\n\n        private String password;\n\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/AdminContestController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.AnnouncementDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ContestProblemDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.pojo.vo.AdminContestVO;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.backend.service.admin.contest.AdminContestAnnouncementService;\nimport com.simplefanc.voj.backend.service.admin.contest.AdminContestProblemService;\nimport com.simplefanc.voj.backend.service.admin.contest.AdminContestService;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/19 22:28\n * @Description:\n */\n@RestController\n@RequestMapping(\"/api/admin/contest\")\n@RequiredArgsConstructor\npublic class AdminContestController {\n\n    private final AdminContestService adminContestService;\n\n    private final AdminContestProblemService adminContestProblemService;\n\n    private final AdminContestAnnouncementService adminContestAnnouncementService;\n\n    @GetMapping(\"/get-contest-list\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<IPage<Contest>> getContestList(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                       @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                                       @RequestParam(value = \"keyword\", required = false) String keyword) {\n\n        IPage<Contest> contestList = adminContestService.getContestList(limit, currentPage, keyword);\n        return CommonResult.successResponse(contestList);\n    }\n\n    @GetMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<AdminContestVO> getContest(@RequestParam(\"cid\") Long cid) {\n        AdminContestVO adminContestVO = adminContestService.getContest(cid);\n        return CommonResult.successResponse(adminContestVO);\n    }\n\n    @DeleteMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = \"root\")\n    public CommonResult<Void> deleteContest(@RequestParam(\"cid\") Long cid) {\n        adminContestService.deleteContest(cid);\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> addContest(@RequestBody AdminContestVO adminContestVO) {\n        adminContestService.addContest(adminContestVO);\n        return CommonResult.successResponse();\n    }\n\n    @PutMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> updateContest(@RequestBody AdminContestVO adminContestVO) {\n        adminContestService.updateContest(adminContestVO);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/clone\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> cloneContest(@RequestParam(\"cid\") Long cid) {\n        adminContestService.cloneContest(cid);\n        return CommonResult.successResponse();\n    }\n\n    @PutMapping(\"/change-contest-visible\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> changeContestVisible(@RequestParam(value = \"cid\") Long cid,\n                                                   @RequestParam(value = \"uid\") String uid,\n                                                   @RequestParam(value = \"visible\") Boolean visible) {\n        adminContestService.changeContestVisible(cid, uid, visible);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * 以下为比赛的题目的增删改查操作接口\n     *\n     * @param limit\n     * @param currentPage\n     * @param keyword\n     * @param cid\n     * @param problemType: 0:ACM; 1:OI\n     * @param oj\n     * @return\n     */\n    @GetMapping(\"/get-problem-list\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Map<String, Object>> getProblemList(\n            @RequestParam(value = \"limit\", required = false) Integer limit,\n            @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n            @RequestParam(value = \"keyword\", required = false) String keyword,\n            @RequestParam(value = \"cid\") Long cid,\n            @RequestParam(value = \"problemType\", required = false) Integer problemType,\n            @RequestParam(value = \"oj\", required = false) String oj) {\n        Map<String, Object> problemList = adminContestProblemService.getProblemList(limit, currentPage, keyword,\n                cid, problemType, oj);\n        return CommonResult.successResponse(problemList);\n    }\n\n    @GetMapping(\"/problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Problem> getProblem(@RequestParam(\"pid\") Long pid) {\n        Problem problem = adminContestProblemService.getProblem(pid);\n        return CommonResult.successResponse(problem);\n    }\n\n    @DeleteMapping(\"/problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> deleteProblem(@RequestParam(\"pid\") Long pid,\n                                            @RequestParam(value = \"cid\", required = false) Long cid) {\n        adminContestProblemService.deleteProblem(pid, cid);\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"/problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Map<Object, Object>> addProblem(@RequestBody ProblemDTO problemDTO) {\n        Map<Object, Object> problemMap = adminContestProblemService.addProblem(problemDTO);\n        return CommonResult.successResponse(problemMap);\n    }\n\n    @PutMapping(\"/problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> updateProblem(@RequestBody ProblemDTO problemDTO) {\n        adminContestProblemService.updateProblem(problemDTO);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/contest-problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<ContestProblem> getContestProblem(@RequestParam(value = \"cid\") Long cid,\n                                                          @RequestParam(value = \"pid\") Long pid) {\n        ContestProblem contestProblem = adminContestProblemService.getContestProblem(cid, pid);\n        return CommonResult.successResponse(contestProblem);\n    }\n\n    @PutMapping(\"/contest-problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<ContestProblem> setContestProblem(@RequestBody ContestProblem contestProblem) {\n        return CommonResult.successResponse(adminContestProblemService.setContestProblem(contestProblem));\n    }\n\n    @PostMapping(\"/add-problem-from-public\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> addProblemFromPublic(@RequestBody ContestProblemDTO contestProblemDTO) {\n        adminContestProblemService.addProblemFromPublic(contestProblemDTO);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/import-remote-oj-problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> importContestRemoteOjProblem(@RequestParam(\"name\") String name,\n                                                           @RequestParam(\"problemId\") String problemId, @RequestParam(\"cid\") Long cid) {\n        adminContestProblemService.importContestRemoteOjProblem(name, problemId, cid);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * 以下处理比赛公告的操作请求\n     */\n    @GetMapping(\"/announcement\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<IPage<AnnouncementVO>> getAnnouncementList(\n            @RequestParam(value = \"limit\", required = false) Integer limit,\n            @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n            @RequestParam(value = \"cid\") Long cid) {\n        IPage<AnnouncementVO> announcementList = adminContestAnnouncementService.getAnnouncementList(limit, currentPage,\n                cid);\n        return CommonResult.successResponse(announcementList);\n    }\n\n    @DeleteMapping(\"/announcement\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> deleteAnnouncement(@RequestParam(\"aid\") Long aid) {\n        adminContestAnnouncementService.deleteAnnouncement(aid);\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"/announcement\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> addAnnouncement(@RequestBody AnnouncementDTO announcementDTO) {\n        adminContestAnnouncementService.addAnnouncement(announcementDTO);\n        return CommonResult.successResponse();\n    }\n\n    @PutMapping(\"/announcement\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> updateAnnouncement(@RequestBody AnnouncementDTO announcementDTO) {\n        adminContestAnnouncementService.updateAnnouncement(announcementDTO);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/AdminDiscussionController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.service.admin.discussion.AdminDiscussionService;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionReport;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/15 20:35\n * @Description:\n */\n@RestController\n@RequestMapping(\"/api/admin\")\n@RequiredArgsConstructor\npublic class AdminDiscussionController {\n\n    private final AdminDiscussionService adminDiscussionService;\n\n    @PutMapping(\"/discussion\")\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    @RequiresAuthentication\n    public CommonResult<Void> updateDiscussion(@RequestBody Discussion discussion) {\n        adminDiscussionService.updateDiscussion(discussion);\n        return CommonResult.successResponse();\n    }\n\n    @DeleteMapping(\"/discussion\")\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    @RequiresAuthentication\n    public CommonResult<Void> removeDiscussion(@RequestBody List<Integer> didList) {\n        adminDiscussionService.removeDiscussion(didList);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/discussion-report\")\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    @RequiresAuthentication\n    public CommonResult<IPage<DiscussionReport>> getDiscussionReport(\n            @RequestParam(value = \"limit\", defaultValue = \"10\") Integer limit,\n            @RequestParam(value = \"currentPage\", defaultValue = \"1\") Integer currentPage) {\n        IPage<DiscussionReport> discussionReportIPage = adminDiscussionService.getDiscussionReport(limit, currentPage);\n        return CommonResult.successResponse(discussionReportIPage);\n    }\n\n    @PutMapping(\"/discussion-report\")\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    @RequiresAuthentication\n    public CommonResult<Void> updateDiscussionReport(@RequestBody DiscussionReport discussionReport) {\n        adminDiscussionService.updateDiscussionReport(discussionReport);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/AdminJudgeController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.simplefanc.voj.backend.service.admin.rejudge.RejudgeService;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresPermissions;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/3 14:09\n * @Description: 超管重判提交\n */\n\n@RestController\n@RequestMapping(\"/api/admin/judge\")\n@RequiredArgsConstructor\npublic class AdminJudgeController {\n\n    private final RejudgeService rejudgeService;\n\n    @GetMapping(\"/rejudge\")\n    @RequiresAuthentication\n    @RequiresRoles(\"root\")\n    @RequiresPermissions(\"rejudge\")\n    public CommonResult<Judge> rejudge(@RequestParam(\"submitId\") Long submitId) {\n        Judge judge = rejudgeService.rejudge(submitId);\n        return CommonResult.successResponse(judge, \"重判成功！该提交已进入判题队列！\");\n    }\n\n    @GetMapping(\"/rejudge-contest-problem\")\n    @RequiresAuthentication\n    @RequiresRoles(\"root\")\n    @RequiresPermissions(\"rejudge\")\n    public CommonResult<Void> rejudgeContestProblem(@RequestParam(\"cid\") Long cid, @RequestParam(\"pid\") Long pid) {\n        rejudgeService.rejudgeContestProblem(cid, pid);\n        return CommonResult.successResponse(\"重判成功！该题目对应的全部提交已进入判题队列！\");\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/AdminProblemController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.service.admin.problem.AdminProblemService;\nimport com.simplefanc.voj.common.pojo.dto.CompileDTO;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/11 21:45\n * @Description:\n */\n@RestController\n@RequestMapping(\"/api/admin/problem\")\n@RequiredArgsConstructor\npublic class AdminProblemController {\n\n    private final AdminProblemService adminProblemService;\n\n    @GetMapping(\"/get-problem-list\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<IPage<Problem>> getProblemList(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                       @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                                       @RequestParam(value = \"keyword\", required = false) String keyword,\n                                                       @RequestParam(value = \"auth\", required = false) Integer auth,\n                                                       @RequestParam(value = \"oj\", required = false) String oj) {\n        return CommonResult.successResponse(adminProblemService.getProblemList(limit, currentPage, keyword, auth, oj));\n    }\n\n    @GetMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Problem> getProblem(@RequestParam(\"pid\") Long pid) {\n        return CommonResult.successResponse(adminProblemService.getProblem(pid));\n    }\n\n    @DeleteMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> deleteProblem(@RequestParam(\"pid\") Long pid) {\n        adminProblemService.deleteProblem(pid);\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> addProblem(@RequestBody ProblemDTO problemDTO) {\n        adminProblemService.addProblem(problemDTO);\n        return CommonResult.successResponse();\n    }\n\n    @PutMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> updateProblem(@RequestBody ProblemDTO problemDTO) {\n        adminProblemService.updateProblem(problemDTO);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/get-problem-cases\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<List<ProblemCase>> getProblemCases(@RequestParam(\"pid\") Long pid,\n                                                           @RequestParam(value = \"isUpload\", defaultValue = \"true\") Boolean isUpload) {\n        List<ProblemCase> problemCaseList = adminProblemService.getProblemCases(pid, isUpload);\n        return CommonResult.successResponse(problemCaseList);\n    }\n\n    @PostMapping(\"/compile-spj\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult compileSpj(@RequestBody CompileDTO compileDTO) {\n        if (StrUtil.isEmpty(compileDTO.getCode()) || StrUtil.isEmpty(compileDTO.getLanguage())) {\n            return CommonResult.errorResponse(\"参数不能为空！\");\n        }\n        return adminProblemService.compileSpj(compileDTO);\n    }\n\n    @PostMapping(\"/compile-interactive\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult compileInteractive(@RequestBody CompileDTO compileDTO) {\n        if (StrUtil.isEmpty(compileDTO.getCode()) || StrUtil.isEmpty(compileDTO.getLanguage())) {\n            return CommonResult.errorResponse(\"参数不能为空！\");\n        }\n        return adminProblemService.compileInteractive(compileDTO);\n    }\n\n    @GetMapping(\"/import-remote-oj-problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> importRemoteOjProblem(@RequestParam(\"name\") String name,\n                                                    @RequestParam(\"problemId\") String problemId) {\n        adminProblemService.importRemoteOjProblem(name, problemId);\n        return CommonResult.successResponse(\"导入新题目成功\");\n    }\n\n    @PutMapping(\"/change-problem-auth\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> changeProblemAuth(@RequestBody Problem problem) {\n        adminProblemService.changeProblemAuth(problem);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/AdminTagController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.simplefanc.voj.backend.service.admin.tag.AdminTagService;\nimport com.simplefanc.voj.common.constants.Constant;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport com.simplefanc.voj.common.pojo.entity.problem.TagClassification;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/2 23:24\n * @Description: 处理tag的增删改\n */\n@RestController\n@RequestMapping(\"/api/admin/tag\")\n@RequiredArgsConstructor\npublic class AdminTagController {\n\n    private final AdminTagService adminTagService;\n\n    @PostMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Tag> addTag(@RequestBody Tag tag) {\n        return CommonResult.successResponse(adminTagService.addTag(tag));\n    }\n\n    @PutMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> updateTag(@RequestBody Tag tag) {\n        adminTagService.updateTag(tag);\n        return CommonResult.successResponse();\n    }\n\n    @DeleteMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> deleteTag(@RequestParam(\"tid\") Long tid) {\n        adminTagService.deleteTag(tid);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/classification\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<List<TagClassification>> getTagClassification(@RequestParam(value = \"oj\", defaultValue = Constant.LOCAL) String oj) {\n        return CommonResult.successResponse(adminTagService.getTagClassification(oj));\n    }\n\n    @PostMapping(\"/classification\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<TagClassification> addTagClassification(@RequestBody TagClassification tagClassification) {\n        return CommonResult.successResponse(adminTagService.addTagClassification(tagClassification));\n    }\n\n    @PutMapping(\"/classification\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> updateTagClassification(@RequestBody TagClassification tagClassification) {\n        adminTagService.updateTagClassification(tagClassification);\n        return CommonResult.successResponse();\n    }\n\n    @DeleteMapping(\"/classification\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> deleteTagClassification(@RequestParam(\"tcid\") Long tcid) {\n        adminTagService.deleteTagClassification(tcid);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/AdminTrainingCategoryController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.simplefanc.voj.backend.service.admin.training.AdminTrainingCategoryService;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/27 15:11\n * @Description:\n */\n\n@RestController\n@RequestMapping(\"/api/admin/training/category\")\n@RequiredArgsConstructor\npublic class AdminTrainingCategoryController {\n\n    private final AdminTrainingCategoryService adminTrainingCategoryService;\n\n    @PostMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<TrainingCategory> addTrainingCategory(@RequestBody TrainingCategory trainingCategory) {\n        return CommonResult.successResponse(adminTrainingCategoryService.addTrainingCategory(trainingCategory));\n    }\n\n    @PutMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> updateTrainingCategory(@RequestBody TrainingCategory trainingCategory) {\n        adminTrainingCategoryService.updateTrainingCategory(trainingCategory);\n        return CommonResult.successResponse();\n    }\n\n    @DeleteMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> deleteTrainingCategory(@RequestParam(\"cid\") Long cid) {\n        adminTrainingCategoryService.deleteTrainingCategory(cid);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/AdminTrainingController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.TrainingDTO;\nimport com.simplefanc.voj.backend.pojo.dto.TrainingProblemDTO;\nimport com.simplefanc.voj.backend.service.admin.training.AdminTrainingProblemService;\nimport com.simplefanc.voj.backend.service.admin.training.AdminTrainingService;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingProblem;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/22 20:57\n * @Description:\n */\n@RestController\n@RequestMapping(\"/api/admin/training\")\n@RequiredArgsConstructor\npublic class AdminTrainingController {\n\n    private final AdminTrainingService adminTrainingService;\n\n    private final AdminTrainingProblemService adminTrainingProblemService;\n\n    @GetMapping(\"/list\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<IPage<Training>> getTrainingList(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                         @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                                         @RequestParam(value = \"keyword\", required = false) String keyword) {\n        return CommonResult.successResponse(adminTrainingService.getTrainingList(limit, currentPage, keyword));\n    }\n\n    @GetMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<TrainingDTO> getTraining(@RequestParam(\"tid\") Long tid) {\n        TrainingDTO training = adminTrainingService.getTraining(tid);\n        return CommonResult.successResponse(training);\n    }\n\n    @DeleteMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = \"root\") // 只有超级管理员能删除训练\n    public CommonResult<Void> deleteTraining(@RequestParam(\"tid\") Long tid) {\n        adminTrainingService.deleteTraining(tid);\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> addTraining(@RequestBody TrainingDTO trainingDTO) {\n        adminTrainingService.addTraining(trainingDTO);\n        return CommonResult.successResponse();\n    }\n\n    @PutMapping(\"\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> updateTraining(@RequestBody TrainingDTO trainingDTO) {\n        adminTrainingService.updateTraining(trainingDTO);\n        return CommonResult.successResponse();\n    }\n\n    @PutMapping(\"/change-training-status\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> changeTrainingStatus(@RequestParam(value = \"tid\") Long tid,\n                                                   @RequestParam(value = \"author\") String author,\n                                                   @RequestParam(value = \"status\") Boolean status) {\n        adminTrainingService.changeTrainingStatus(tid, author, status);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/get-problem-list\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<HashMap<String, Object>> getProblemList(\n            @RequestParam(value = \"limit\", required = false) Integer limit,\n            @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n            @RequestParam(value = \"keyword\", required = false) String keyword,\n            @RequestParam(value = \"queryExisted\", defaultValue = \"false\") Boolean queryExisted,\n            @RequestParam(value = \"tid\") Long tid) {\n        HashMap<String, Object> problemMap = adminTrainingProblemService.getProblemList(limit, currentPage, keyword,\n                queryExisted, tid);\n        return CommonResult.successResponse(problemMap);\n    }\n\n    @PutMapping(\"/problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> updateProblem(@RequestBody TrainingProblem trainingProblem) {\n        adminTrainingProblemService.updateProblem(trainingProblem);\n        return CommonResult.successResponse();\n    }\n\n    @DeleteMapping(\"/problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> deleteProblem(@RequestParam(\"pid\") Long pid,\n                                            @RequestParam(value = \"tid\", required = false) Long tid) {\n        adminTrainingProblemService.deleteProblem(pid, tid);\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"/add-problem-from-public\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> addProblemFromPublic(@RequestBody TrainingProblemDTO trainingProblemDTO) {\n        adminTrainingProblemService.addProblemFromPublic(trainingProblemDTO);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/import-remote-oj-problem\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> importTrainingRemoteOjProblem(@RequestParam(\"name\") String name,\n                                                            @RequestParam(\"problemId\") String problemId, @RequestParam(\"tid\") Long tid) {\n        adminTrainingProblemService.importTrainingRemoteOjProblem(name, problemId, tid);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/AdminUserController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.AdminEditUserDTO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.admin.user.AdminUserService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresPermissions;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/6 15:18\n * @Description:\n */\n@RestController\n@RequestMapping(\"/api/admin/user\")\n@RequiredArgsConstructor\npublic class AdminUserController {\n\n    private final AdminUserService adminUserService;\n\n    @GetMapping(\"/get-user-list\")\n    @RequiresAuthentication\n    @RequiresPermissions(\"user_admin\")\n    public CommonResult<IPage<UserRolesVO>> getUserList(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                        @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                                        @RequestParam(value = \"roleId\", required = false) Long roleId,\n                                                        @RequestParam(value = \"status\", required = false) Integer status,\n                                                        @RequestParam(value = \"keyword\", required = false) String keyword) {\n        return CommonResult.successResponse(adminUserService.getUserList(limit, currentPage, keyword, roleId, status));\n    }\n\n    @PutMapping(\"/edit-user\")\n    @RequiresPermissions(\"user_admin\")\n    @RequiresAuthentication\n    public CommonResult<Void> editUser(@RequestBody AdminEditUserDTO adminEditUserDTO) {\n        adminUserService.editUser(adminEditUserDTO);\n        return CommonResult.successResponse();\n    }\n\n    @DeleteMapping(\"/delete-user\")\n    @RequiresPermissions(\"user_admin\")\n    @RequiresAuthentication\n    public CommonResult<Void> deleteUser(@RequestBody Map<String, Object> params) {\n        // TODO 参数\n        adminUserService.deleteUser((List<String>) params.get(\"ids\"));\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"/forbid-user\")\n    @RequiresPermissions(\"user_admin\")\n    @RequiresAuthentication\n    public CommonResult<Void> forbidUser(@RequestBody Map<String, Object> params) {\n        // TODO 参数\n        adminUserService.forbidUser((List<String>) params.get(\"ids\"));\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"/insert-batch-user\")\n    @RequiresPermissions(\"user_admin\")\n    @RequiresAuthentication\n    public CommonResult<Void> insertBatchUser(@RequestBody Map<String, Object> params) {\n        // TODO 参数\n        adminUserService.insertBatchUser((List<List<String>>) params.get(\"users\"));\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"/generate-user\")\n    @RequiresPermissions(\"user_admin\")\n    @RequiresAuthentication\n    public CommonResult<Map<Object, Object>> generateUser(@RequestBody Map<String, Object> params) {\n        // TODO 参数\n        return CommonResult.successResponse(adminUserService.generateUser(params));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/AnnouncementController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.backend.service.admin.announcement.AdminAnnouncementService;\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresPermissions;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/10 19:53\n * @Description:\n */\n@RestController\n@RequiresAuthentication\n@RequiredArgsConstructor\npublic class AnnouncementController {\n\n    private final AdminAnnouncementService adminAnnouncementService;\n\n    @GetMapping(\"/api/admin/announcement\")\n    @RequiresPermissions(\"announcement_admin\")\n    public CommonResult<IPage<AnnouncementVO>> getAnnouncementList(\n            @RequestParam(value = \"limit\", required = false) Integer limit,\n            @RequestParam(value = \"currentPage\", required = false) Integer currentPage) {\n        return CommonResult.successResponse(adminAnnouncementService.getAnnouncementList(limit, currentPage));\n    }\n\n    @DeleteMapping(\"/api/admin/announcement\")\n    @RequiresPermissions(\"announcement_admin\")\n    public CommonResult<Void> deleteAnnouncement(@RequestParam(\"aid\") Long aid) {\n        adminAnnouncementService.deleteAnnouncement(aid);\n        return CommonResult.successResponse();\n    }\n\n    @PostMapping(\"/api/admin/announcement\")\n    @RequiresRoles(\"root\") // 只有超级管理员能操作\n    @RequiresPermissions(\"announcement_admin\")\n    public CommonResult<Void> addAnnouncement(@RequestBody Announcement announcement) {\n        adminAnnouncementService.addAnnouncement(announcement);\n        return CommonResult.successResponse();\n    }\n\n    @PutMapping(\"/api/admin/announcement\")\n    @RequiresPermissions(\"announcement_admin\")\n    public CommonResult<Void> updateAnnouncement(@RequestBody Announcement announcement) {\n        adminAnnouncementService.updateAnnouncement(announcement);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/ConfigController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.backend.pojo.dto.DbAndRedisConfigDTO;\nimport com.simplefanc.voj.backend.pojo.dto.EmailConfigDTO;\nimport com.simplefanc.voj.backend.pojo.dto.TestEmailDTO;\nimport com.simplefanc.voj.backend.pojo.dto.WebConfigDTO;\nimport com.simplefanc.voj.backend.service.admin.system.ConfigService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresPermissions;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/2 21:42\n * @Description:\n */\n@RestController\n@RequestMapping(\"/api/admin/config\")\n@RequiredArgsConstructor\npublic class ConfigController {\n\n    private final ConfigService configService;\n\n    /**\n     * @MethodName getServiceInfo\n     * @Params * @param null\n     * @Description 获取当前服务的相关信息以及当前系统的cpu情况，内存使用情况\n     * @Return CommonResult\n     * @Since 2021/12/3\n     */\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    @RequestMapping(\"/get-service-info\")\n    public CommonResult<JSONObject> getServiceInfo() {\n        return CommonResult.successResponse(configService.getServiceInfo());\n    }\n\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    @RequestMapping(\"/get-judge-service-info\")\n    public CommonResult<List<JSONObject>> getJudgeServiceInfo() {\n        return CommonResult.successResponse(configService.getJudgeServiceInfo());\n    }\n\n    @RequiresPermissions(\"system_info_admin\")\n    @RequestMapping(\"/get-web-config\")\n    public CommonResult<WebConfigDTO> getWebConfig() {\n        return CommonResult.successResponse(configService.getWebConfig());\n    }\n\n    @RequiresPermissions(\"system_info_admin\")\n    @DeleteMapping(\"/home-carousel\")\n    public CommonResult<Void> deleteHomeCarousel(@RequestParam(\"id\") Long id) {\n        configService.deleteHomeCarousel(id);\n        return CommonResult.successResponse();\n    }\n\n    @RequiresPermissions(\"system_info_admin\")\n    @RequestMapping(value = \"/set-web-config\", method = RequestMethod.PUT)\n    public CommonResult<Void> setWebConfig(@RequestBody WebConfigDTO config) {\n        configService.setWebConfig(config);\n        return CommonResult.successResponse();\n    }\n\n    @RequiresPermissions(\"system_info_admin\")\n    @RequestMapping(\"/get-email-config\")\n    public CommonResult<EmailConfigDTO> getEmailConfig() {\n        return CommonResult.successResponse(configService.getEmailConfig());\n    }\n\n    @RequiresPermissions(\"system_info_admin\")\n    @PutMapping(\"/set-email-config\")\n    public CommonResult<Void> setEmailConfig(@RequestBody EmailConfigDTO config) {\n        configService.setEmailConfig(config);\n        return CommonResult.successResponse();\n    }\n\n    @RequiresPermissions(\"system_info_admin\")\n    @PostMapping(\"/test-email\")\n    public CommonResult<Void> testEmail(@RequestBody TestEmailDTO testEmailDTO) {\n        configService.testEmail(testEmailDTO);\n        return CommonResult.successResponse();\n    }\n\n    @RequiresPermissions(\"system_info_admin\")\n    @RequestMapping(\"/get-db-and-redis-config\")\n    public CommonResult<DbAndRedisConfigDTO> getDbAndRedisConfig() {\n        return CommonResult.successResponse(configService.getDbAndRedisConfig());\n    }\n\n    @RequiresPermissions(\"system_info_admin\")\n    @PutMapping(\"/set-db-and-redis-config\")\n    public CommonResult<Void> setDbAndRedisConfig(@RequestBody DbAndRedisConfigDTO config) {\n        configService.setDbAndRedisConfig(config);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/DashboardController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.simplefanc.voj.backend.service.admin.system.DashboardService;\nimport com.simplefanc.voj.common.pojo.entity.user.Session;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/6 15:10\n * @Description:\n */\n@RestController\n@RequestMapping(\"/api/admin/dashboard\")\n@RequiredArgsConstructor\npublic class DashboardController {\n\n    private final DashboardService dashboardService;\n\n    @PostMapping(\"/get-sessions\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Session> getRecentSession() {\n        return CommonResult.successResponse(dashboardService.getRecentSession());\n    }\n\n    @GetMapping(\"/get-dashboard-info\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Map<Object, Object>> getDashboardInfo() {\n        return CommonResult.successResponse(dashboardService.getDashboardInfo());\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/admin/SwitchController.java",
    "content": "package com.simplefanc.voj.backend.controller.admin;\n\nimport com.simplefanc.voj.backend.pojo.dto.SwitchConfigDTO;\nimport com.simplefanc.voj.backend.service.admin.system.ConfigService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresPermissions;\nimport org.springframework.web.bind.annotation.PutMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n\n/**\n * @Author chenfan\n * @Date 2022/9/20\n */\n@RestController\n@RequestMapping(\"/api/admin/switch\")\n@RequiredArgsConstructor\npublic class SwitchController {\n\n    private final ConfigService configService;\n\n    @RequiresPermissions(\"system_info_admin\")\n    @RequestMapping(\"/info\")\n    public CommonResult<SwitchConfigDTO> getSwitchConfig() {\n        return CommonResult.successResponse(configService.getSwitchConfig());\n    }\n\n    @RequiresPermissions(\"system_info_admin\")\n    @PutMapping(\"/update\")\n    public CommonResult<Void> setSwitchConfig(@RequestBody SwitchConfigDTO config) {\n        configService.setSwitchConfig(config);\n        return CommonResult.successResponse();\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/file/ContestFileController.java",
    "content": "package com.simplefanc.voj.backend.controller.file;\n\nimport com.simplefanc.voj.backend.service.file.ContestFileService;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/5 19:55\n * @Description:\n */\n@Controller\n@RequestMapping(\"/api/file\")\n@RequiredArgsConstructor\npublic class ContestFileController {\n\n    private final ContestFileService contestFileService;\n\n    @GetMapping(\"/download-contest-rank\")\n    @RequiresAuthentication\n    public void downloadContestRank(@RequestParam(\"cid\") Long cid, @RequestParam(\"forceRefresh\") Boolean forceRefresh,\n                                    @RequestParam(value = \"removeStar\", defaultValue = \"false\") Boolean removeStar,\n                                    HttpServletResponse response) throws IOException {\n        contestFileService.downloadContestRank(cid, forceRefresh, removeStar, response);\n    }\n\n    @GetMapping(\"/download-contest-ac-submission\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public void downloadContestAcSubmission(@RequestParam(\"cid\") Long cid,\n                                            @RequestParam(value = \"excludeAdmin\", defaultValue = \"false\") Boolean excludeAdmin,\n                                            @RequestParam(value = \"allStatus\", defaultValue = \"false\") Boolean allStatus,\n                                            @RequestParam(value = \"splitType\", defaultValue = \"user\") String splitType,\n                                            HttpServletResponse response) {\n        contestFileService.downloadContestAcSubmission(cid, excludeAdmin, allStatus, splitType, response);\n    }\n\n    @GetMapping(\"/download-contest-print-text\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public void downloadContestPrintText(@RequestParam(\"id\") Long id, HttpServletResponse response) {\n        contestFileService.downloadContestPrintText(id, response);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/file/ImageController.java",
    "content": "package com.simplefanc.voj.backend.controller.file;\n\nimport com.simplefanc.voj.backend.service.file.ImageService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/5 19:46\n * @Description:\n */\n@Controller\n@RequestMapping(\"/api/file\")\n@RequiredArgsConstructor\npublic class ImageController {\n\n    private final ImageService imageService;\n\n    @RequestMapping(value = \"/upload-avatar\", method = RequestMethod.POST)\n    @RequiresAuthentication\n    @ResponseBody\n    public CommonResult<Map<Object, Object>> uploadAvatar(@RequestParam(\"image\") MultipartFile image) {\n        return CommonResult.successResponse(imageService.uploadAvatar(image));\n    }\n\n    @RequestMapping(value = \"/upload-carouse-img\", method = RequestMethod.POST)\n    @RequiresAuthentication\n    @ResponseBody\n    @RequiresRoles(\"root\")\n    public CommonResult<Map<Object, Object>> uploadCarouselImg(@RequestParam(\"file\") MultipartFile image) {\n        return CommonResult.successResponse(imageService.uploadCarouselImg(image));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/file/ImportFpsProblemController.java",
    "content": "package com.simplefanc.voj.backend.controller.file;\n\nimport com.simplefanc.voj.backend.service.file.ImportFpsProblemService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.IOException;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/5 19:45\n * @Description:\n */\n@Controller\n@RequestMapping(\"/api/file\")\n@RequiredArgsConstructor\npublic class ImportFpsProblemController {\n\n    private final ImportFpsProblemService importFpsProblemService;\n\n    /**\n     * @param file\n     * @MethodName importFpsProblem\n     * @Description zip文件导入题目 仅超级管理员可操作\n     * @Return\n     * @Since 2021/10/06\n     */\n    @RequiresRoles(\"root\")\n    @RequiresAuthentication\n    @ResponseBody\n    @PostMapping(\"/import-fps-problem\")\n    public CommonResult<Void> importFPSProblem(@RequestParam(\"file\") MultipartFile file) {\n        try {\n            importFpsProblemService.importFPSProblem(file);\n            return CommonResult.successResponse();\n        } catch (IOException e) {\n            return CommonResult.errorResponse(e.getMessage());\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/file/ImportLOJProblemController.java",
    "content": "package com.simplefanc.voj.backend.controller.file;\n\nimport com.simplefanc.voj.backend.service.file.ImportLOJProblemService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * @Author: chenfan\n * @Date: 2022/11/7 19:45\n * @Description:\n */\n@RestController\n@RequestMapping(\"/api/file\")\n@RequiredArgsConstructor\npublic class ImportLOJProblemController {\n\n    private final ImportLOJProblemService importLOJProblemService;\n\n    @RequiresRoles(\"root\")\n    @RequiresAuthentication\n    @GetMapping(\"/import-loj-problem\")\n    public CommonResult<Void> importLOJProblem(Integer problemId) {\n        importLOJProblemService.importLOJProblem(problemId);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/file/ImportQDUOJProblemController.java",
    "content": "package com.simplefanc.voj.backend.controller.file;\n\nimport com.simplefanc.voj.backend.service.file.ImportQDUOJProblemService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.multipart.MultipartFile;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/5 19:44\n * @Description:\n */\n\n@Controller\n@RequestMapping(\"/api/file\")\n@RequiredArgsConstructor\npublic class ImportQDUOJProblemController {\n\n    private final ImportQDUOJProblemService importQDUOJProblemService;\n\n    /**\n     * @param file\n     * @MethodName importQDOJProblem\n     * @Description zip文件导入题目 仅超级管理员可操作\n     * @Return\n     * @Since 2021/5/27\n     */\n    @RequiresRoles(\"root\")\n    @RequiresAuthentication\n    @ResponseBody\n    @PostMapping(\"/import-qdoj-problem\")\n    public CommonResult<Void> importQDOJProblem(@RequestParam(\"file\") MultipartFile file) {\n        importQDUOJProblemService.importQDOJProblem(file);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/file/MarkDownFileController.java",
    "content": "package com.simplefanc.voj.backend.controller.file;\n\nimport com.simplefanc.voj.backend.service.file.MarkDownFileService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/5 20:01\n * @Description:\n */\n@Controller\n@RequestMapping(\"/api/file\")\n@RequiredArgsConstructor\npublic class MarkDownFileController {\n\n    private final MarkDownFileService markDownFileService;\n\n    @RequestMapping(value = \"/upload-md-img\", method = RequestMethod.POST)\n    @RequiresAuthentication\n    @ResponseBody\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Map<Object, Object>> uploadMDImg(@RequestParam(\"image\") MultipartFile image) {\n        return CommonResult.successResponse(markDownFileService.uploadMDImg(image));\n    }\n\n    @RequestMapping(value = \"/delete-md-img\", method = RequestMethod.GET)\n    @RequiresAuthentication\n    @ResponseBody\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Void> deleteMDImg(@RequestParam(\"fileId\") Long fileId) {\n        markDownFileService.deleteMDImg(fileId);\n        return CommonResult.successResponse();\n    }\n\n    @RequestMapping(value = \"/upload-md-file\", method = RequestMethod.POST)\n    @RequiresAuthentication\n    @ResponseBody\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Map<Object, Object>> uploadMd(@RequestParam(\"file\") MultipartFile file) {\n        return CommonResult.successResponse(markDownFileService.uploadMd(file));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/file/ProblemFileController.java",
    "content": "package com.simplefanc.voj.backend.controller.file;\n\nimport com.simplefanc.voj.backend.service.file.ProblemFileService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/5 20:05\n * @Description:\n */\n@Controller\n@RequestMapping(\"/api/file\")\n@RequiredArgsConstructor\npublic class ProblemFileController {\n\n    private final ProblemFileService problemFileService;\n\n    /**\n     * @param file\n     * @MethodName importProblem\n     * @Description zip文件导入题目 仅超级管理员可操作\n     * @Return\n     * @Since 2021/5/27\n     */\n    @RequiresRoles(\"root\")\n    @RequiresAuthentication\n    @ResponseBody\n    @PostMapping(\"/import-problem\")\n    public CommonResult<Void> importProblem(@RequestParam(\"file\") MultipartFile file) {\n        problemFileService.importProblem(file);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * @param pidList\n     * @param response\n     * @MethodName exportProblem\n     * @Description 导出指定的题目包括测试数据生成zip 仅超级管理员可操作\n     * @Return\n     * @Since 2021/5/28\n     */\n    @GetMapping(\"/export-problem\")\n    @RequiresAuthentication\n    @RequiresRoles(\"root\")\n    public void exportProblem(@RequestParam(\"pid\") List<Long> pidList, HttpServletResponse response) {\n        problemFileService.exportProblem(pidList, response);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/file/TestCaseController.java",
    "content": "package com.simplefanc.voj.backend.controller.file;\n\nimport com.simplefanc.voj.backend.service.file.TestCaseService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.Logical;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/5 19:51\n * @Description:\n */\n@Controller\n@RequestMapping(\"/api/file\")\n@RequiredArgsConstructor\npublic class TestCaseController {\n\n    private final TestCaseService testCaseService;\n\n    @PostMapping(\"/upload-testcase-zip\")\n    @ResponseBody\n    @RequiresRoles(value = {\"root\", \"admin\", \"problem_admin\"}, logical = Logical.OR)\n    public CommonResult<Map<Object, Object>> uploadTestcaseZip(@RequestParam(\"file\") MultipartFile file) {\n        return CommonResult.successResponse(testCaseService.uploadTestcaseZip(file));\n    }\n\n    @GetMapping(\"/download-testcase\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public void downloadTestcase(@RequestParam(\"pid\") Long pid, HttpServletResponse response) {\n        testCaseService.downloadTestcase(pid, response);\n    }\n\n    @GetMapping(\"/download-single-testcase\")\n    @RequiresAuthentication\n    @RequiresRoles(value = {\"root\", \"problem_admin\"}, logical = Logical.OR)\n    public void downloadSingleTestCase(@RequestParam(\"pid\") Long pid,\n                                       @RequestParam(\"inputData\") String inputData,\n                                       @RequestParam(\"outputData\") String outputData,\n                                       HttpServletResponse response) {\n        testCaseService.downloadSingleTestCase(pid, inputData, outputData, response);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/file/UserFileController.java",
    "content": "package com.simplefanc.voj.backend.controller.file;\n\nimport com.simplefanc.voj.backend.service.file.UserFileService;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/5 19:48\n * @Description:\n */\n@Controller\n@RequestMapping(\"/api/file\")\n@RequiredArgsConstructor\npublic class UserFileController {\n\n    private final UserFileService userFileService;\n\n    @RequestMapping(\"/generate-user-excel\")\n    @RequiresAuthentication\n    @RequiresRoles(\"root\")\n    public void generateUserExcel(@RequestParam(\"key\") String key, HttpServletResponse response) throws IOException {\n        userFileService.generateUserExcel(key, response);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/msg/AdminNoticeController.java",
    "content": "package com.simplefanc.voj.backend.controller.msg;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.AdminSysNoticeVO;\nimport com.simplefanc.voj.backend.service.msg.AdminNoticeService;\nimport com.simplefanc.voj.common.pojo.entity.msg.AdminSysNotice;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresRoles;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:38\n * @Description: 负责管理员发送系统通知\n */\n@RestController\n@RequestMapping(\"/api/admin/msg\")\n@RequiredArgsConstructor\npublic class AdminNoticeController {\n\n    private final AdminNoticeService adminNoticeService;\n\n    @GetMapping(\"/notice\")\n    @RequiresAuthentication\n    @RequiresRoles(\"root\")\n    public CommonResult<IPage<AdminSysNoticeVO>> getSysNotice(\n            @RequestParam(value = \"limit\", required = false) Integer limit,\n            @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n            @RequestParam(value = \"type\", required = false) String type) {\n        return CommonResult.successResponse(adminNoticeService.getSysNotice(limit, currentPage, type));\n    }\n\n    @PostMapping(\"/notice\")\n    @RequiresAuthentication\n    @RequiresRoles(\"root\")\n    public CommonResult<Void> addSysNotice(@RequestBody AdminSysNotice adminSysNotice) {\n        adminNoticeService.addSysNotice(adminSysNotice);\n        return CommonResult.successResponse();\n    }\n\n    @DeleteMapping(\"/notice\")\n    @RequiresAuthentication\n    @RequiresRoles(\"root\")\n    public CommonResult<Void> deleteSysNotice(@RequestParam(\"id\") Long id) {\n        adminNoticeService.deleteSysNotice(id);\n        return CommonResult.successResponse();\n    }\n\n    @PutMapping(\"/notice\")\n    @RequiresAuthentication\n    @RequiresRoles(\"root\")\n    public CommonResult<Void> updateSysNotice(@RequestBody AdminSysNotice adminSysNotice) {\n        adminNoticeService.updateSysNotice(adminSysNotice);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/msg/NoticeController.java",
    "content": "package com.simplefanc.voj.backend.controller.msg;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.SysMsgVO;\nimport com.simplefanc.voj.backend.service.msg.NoticeService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:42\n * @Description: 负责用户的 系统消息模块、我的消息模块\n */\n@RestController\n@RequestMapping(\"/api/msg\")\n@RequiredArgsConstructor\npublic class NoticeController {\n\n    private final NoticeService noticeService;\n\n    @RequestMapping(value = \"/sys\", method = RequestMethod.GET)\n    @RequiresAuthentication\n    public CommonResult<IPage<SysMsgVO>> getSysNotice(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                      @RequestParam(value = \"currentPage\", required = false) Integer currentPage) {\n        return CommonResult.successResponse(noticeService.getSysNotice(limit, currentPage));\n    }\n\n    @RequestMapping(value = \"/mine\", method = RequestMethod.GET)\n    @RequiresAuthentication\n    public CommonResult<IPage<SysMsgVO>> getMineNotice(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                       @RequestParam(value = \"currentPage\", required = false) Integer currentPage) {\n        return CommonResult.successResponse(noticeService.getMineNotice(limit, currentPage));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/msg/UserMessageController.java",
    "content": "package com.simplefanc.voj.backend.controller.msg;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.UserMsgVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserUnreadMsgCountVO;\nimport com.simplefanc.voj.backend.service.msg.UserMessageService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:40\n * @Description: 获取用户 评论我的、回复我的、收到的赞的消息\n */\n@RestController\n@RequestMapping(\"/api/msg\")\n@RequiredArgsConstructor\npublic class UserMessageController {\n\n    private final UserMessageService userMessageService;\n\n    /**\n     * @MethodName getUnreadMsgCount\n     * @Description 获取用户的未读消息数量，包括评论我的、回复我的、收到的赞、系统通知、我的消息\n     * @Return\n     * @Since 2021/10/1\n     */\n    @RequestMapping(value = \"/unread\", method = RequestMethod.GET)\n    @RequiresAuthentication\n    public CommonResult<UserUnreadMsgCountVO> getUnreadMsgCount() {\n        return CommonResult.successResponse(userMessageService.getUnreadMsgCount());\n    }\n\n    /**\n     * @param type Discuss Reply Like Sys Mine\n     * @MethodName cleanMsg\n     * @Description 根据type，清空各个消息模块的消息或单个消息\n     * @Return\n     * @Since 2021/10/3\n     */\n    @RequestMapping(value = \"/clean\", method = RequestMethod.DELETE)\n    @RequiresAuthentication\n    public CommonResult<Void> cleanMsg(@RequestParam(\"type\") String type,\n                                       @RequestParam(value = \"id\", required = false) Long id) {\n        userMessageService.cleanMsg(type, id);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * @param limit\n     * @param currentPage\n     * @MethodName getCommentMsg\n     * @Description 获取评论我的讨论贴的消息，按未读的在前、时间晚的在前进行排序\n     * @Return\n     * @Since 2021/10/1\n     */\n    @RequestMapping(value = \"/comment\", method = RequestMethod.GET)\n    @RequiresAuthentication\n    public CommonResult<IPage<UserMsgVO>> getCommentMsg(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                        @RequestParam(value = \"currentPage\", required = false) Integer currentPage) {\n        return CommonResult.successResponse(userMessageService.getCommentMsg(limit, currentPage));\n    }\n\n    /**\n     * @param limit\n     * @param currentPage\n     * @MethodName getReplyMsg\n     * @Description 获取回复我的评论的消息，按未读的在前、时间晚的在前进行排序\n     * @Return\n     * @Since 2021/10/1\n     */\n    @RequestMapping(value = \"/reply\", method = RequestMethod.GET)\n    @RequiresAuthentication\n    public CommonResult<IPage<UserMsgVO>> getReplyMsg(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                      @RequestParam(value = \"currentPage\", required = false) Integer currentPage) {\n        return CommonResult.successResponse(userMessageService.getReplyMsg(limit, currentPage));\n    }\n\n    /**\n     * @param limit\n     * @param currentPage\n     * @MethodName getLikeMsg\n     * @Description 获取点赞我的的消息，按未读的在前、时间晚的在前进行排序\n     * @Return\n     * @Since 2021/10/1\n     */\n    @RequestMapping(value = \"/like\", method = RequestMethod.GET)\n    @RequiresAuthentication\n    public CommonResult<IPage<UserMsgVO>> getLikeMsg(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                     @RequestParam(value = \"currentPage\", required = false) Integer currentPage) {\n        return CommonResult.successResponse(userMessageService.getLikeMsg(limit, currentPage));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/AccountController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.simplefanc.voj.backend.pojo.dto.ChangeEmailDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ChangePasswordDTO;\nimport com.simplefanc.voj.backend.pojo.dto.CheckUsernameOrEmailDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ChangeAccountVO;\nimport com.simplefanc.voj.backend.pojo.vo.CheckUsernameOrEmailVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserHomeVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserInfoVO;\nimport com.simplefanc.voj.backend.service.oj.AccountService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/23 12:00\n * @Description: 主要负责处理账号的相关操作\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class AccountController {\n\n    private final AccountService accountService;\n\n    /**\n     * @MethodName checkUsernameOrEmail\n     * @Description 检验用户名和邮箱是否存在\n     * @Return\n     * @Since 2021/11/5\n     */\n    @RequestMapping(value = \"/check-username-or-email\", method = RequestMethod.POST)\n    public CommonResult<CheckUsernameOrEmailVO> checkUsernameOrEmail(\n            @RequestBody CheckUsernameOrEmailDTO checkUsernameOrEmailDTO) {\n        return CommonResult.successResponse(accountService.checkUsernameOrEmail(checkUsernameOrEmailDTO));\n    }\n\n    /**\n     * @param uid\n     * @MethodName getUserHomeInfo\n     * @Description 前端userHome用户个人主页的数据请求，主要是返回解决题目数，AC的题目列表，提交总数，AC总数，Rating分，\n     * @Return CommonResult\n     * @Since 2021/01/07\n     */\n    @GetMapping(\"/get-user-home-info\")\n    public CommonResult<UserHomeVO> getUserHomeInfo(@RequestParam(value = \"uid\", required = false) String uid,\n                                                    @RequestParam(value = \"username\", required = false) String username) {\n        return CommonResult.successResponse(accountService.getUserHomeInfo(uid, username));\n    }\n\n    /**\n     * @MethodName changePassword\n     * @Params * @param null\n     * @Description 修改密码的操作，连续半小时内修改密码错误5次，则需要半个小时后才可以再次尝试修改密码\n     * @Return\n     * @Since 2021/1/8\n     */\n\n    @PostMapping(\"/change-password\")\n    @RequiresAuthentication\n    public CommonResult<ChangeAccountVO> changePassword(@RequestBody ChangePasswordDTO changePasswordDTO) {\n        return CommonResult.successResponse(accountService.changePassword(changePasswordDTO));\n    }\n\n    /**\n     * @MethodName changeEmail\n     * @Params * @param null\n     * @Description 修改邮箱的操作，连续半小时内密码错误5次，则需要半个小时后才可以再次尝试修改\n     * @Return\n     * @Since 2021/1/9\n     */\n    @PostMapping(\"/change-email\")\n    @RequiresAuthentication\n    public CommonResult<ChangeAccountVO> changeEmail(@RequestBody ChangeEmailDTO changeEmailDTO) {\n        return CommonResult.successResponse(accountService.changeEmail(changeEmailDTO));\n    }\n\n    @PostMapping(\"/change-userInfo\")\n    @RequiresAuthentication\n    public CommonResult<UserInfoVO> changeUserInfo(@RequestBody UserInfoVO userInfoVO) {\n        return CommonResult.successResponse(accountService.changeUserInfo(userInfoVO));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/CommentController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.simplefanc.voj.backend.pojo.dto.ReplyDTO;\nimport com.simplefanc.voj.backend.pojo.vo.CommentListVO;\nimport com.simplefanc.voj.backend.pojo.vo.CommentVO;\nimport com.simplefanc.voj.backend.service.oj.CommentService;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Comment;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresPermissions;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/5 15:41\n * @Description:\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class CommentController {\n\n    private final CommentService commentService;\n\n    @GetMapping(\"/comments\")\n    public CommonResult<CommentListVO> getComments(@RequestParam(value = \"cid\", required = false) Long cid,\n                                                   @RequestParam(value = \"did\", required = false) Integer did,\n                                                   @RequestParam(value = \"limit\", required = false, defaultValue = \"20\") Integer limit,\n                                                   @RequestParam(value = \"currentPage\", required = false, defaultValue = \"1\") Integer currentPage) {\n        return CommonResult.successResponse(commentService.getComments(cid, did, limit, currentPage));\n    }\n\n    @PostMapping(\"/comment\")\n    @RequiresPermissions(\"comment_add\")\n    @RequiresAuthentication\n    public CommonResult<CommentVO> addComment(@RequestBody Comment comment) {\n        return CommonResult.successResponse(commentService.addComment(comment));\n    }\n\n    @DeleteMapping(\"/comment\")\n    @RequiresAuthentication\n    public CommonResult<Void> deleteComment(@RequestBody Comment comment) {\n        commentService.deleteComment(comment);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/comment-like\")\n    @RequiresAuthentication\n    public CommonResult<Void> addDiscussionLike(@RequestParam(\"cid\") Integer cid,\n                                                @RequestParam(\"toLike\") Boolean toLike, @RequestParam(\"sourceId\") Integer sourceId,\n                                                @RequestParam(\"sourceType\") String sourceType) {\n        commentService.addDiscussionLike(cid, toLike, sourceId, sourceType);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/reply\")\n    public CommonResult<List<Reply>> getAllReply(@RequestParam(\"commentId\") Integer commentId,\n                                                 @RequestParam(value = \"cid\", required = false) Long cid) {\n        return CommonResult.successResponse(commentService.getAllReply(commentId, cid));\n    }\n\n    @PostMapping(\"/reply\")\n    @RequiresPermissions(\"reply_add\")\n    @RequiresAuthentication\n    public CommonResult<Reply> addReply(@RequestBody ReplyDTO replyDTO) {\n        commentService.addReply(replyDTO);\n        return CommonResult.successResponse();\n    }\n\n    @DeleteMapping(\"/reply\")\n    @RequiresAuthentication\n    public CommonResult<Void> deleteReply(@RequestBody ReplyDTO replyDTO) {\n        commentService.deleteReply(replyDTO);\n        return CommonResult.successResponse();\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/CommonController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.simplefanc.voj.backend.pojo.vo.CaptchaVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemTagVO;\nimport com.simplefanc.voj.backend.service.oj.CommonService;\nimport com.simplefanc.voj.common.constants.Constant;\nimport com.simplefanc.voj.common.pojo.entity.problem.CodeTemplate;\nimport com.simplefanc.voj.common.pojo.entity.problem.Language;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/12 23:25\n * @Description: 通用的请求控制处理类\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class CommonController {\n\n    private final CommonService commonService;\n\n    @GetMapping(\"/captcha\")\n    public CommonResult<CaptchaVO> getCaptcha() {\n        return CommonResult.successResponse(commonService.getCaptcha());\n    }\n\n    @GetMapping(\"/get-training-category\")\n    public CommonResult<List<TrainingCategory>> getTrainingCategory() {\n        return CommonResult.successResponse(commonService.getTrainingCategory());\n    }\n\n    @GetMapping(\"/get-all-problem-tags\")\n    public CommonResult<List<Tag>> getAllProblemTagsList(\n            @RequestParam(value = \"oj\", defaultValue = Constant.LOCAL) String oj) {\n        return CommonResult.successResponse(commonService.getAllProblemTagsList(oj));\n    }\n\n    @GetMapping(\"/get-problem-tags-and-classification\")\n    public CommonResult<List<ProblemTagVO>> getProblemTagsAndClassification(@RequestParam(value = \"oj\", defaultValue = Constant.LOCAL) String oj) {\n        return CommonResult.successResponse(commonService.getProblemTagsAndClassification(oj));\n    }\n\n    @GetMapping(\"/get-problem-tags\")\n    public CommonResult<Collection<Tag>> getProblemTags(Long pid) {\n        return CommonResult.successResponse(commonService.getProblemTags(pid));\n    }\n\n    @GetMapping(\"/languages\")\n    public CommonResult<List<Language>> getLanguages(@RequestParam(value = \"pid\", required = false) Long pid,\n                                                     @RequestParam(value = \"all\", required = false) Boolean all) {\n        return CommonResult.successResponse(commonService.getLanguages(pid, all));\n    }\n\n    @GetMapping(\"/get-Problem-languages\")\n    public CommonResult<Collection<Language>> getProblemLanguages(@RequestParam(\"pid\") Long pid) {\n        return CommonResult.successResponse(commonService.getProblemLanguages(pid));\n    }\n\n    @GetMapping(\"/get-problem-code-template\")\n    public CommonResult<List<CodeTemplate>> getProblemCodeTemplate(@RequestParam(\"pid\") Long pid) {\n        return CommonResult.successResponse(commonService.getProblemCodeTemplate(pid));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/ContestAdminController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.CheckAcDTO;\nimport com.simplefanc.voj.backend.service.oj.ContestAdminService;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestPrint;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * @Author: chenfan\n * @Date: 2021/9/20 13:15\n * @Description: 处理比赛管理模块的相关数据请求\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class ContestAdminController {\n\n    private final ContestAdminService contestAdminService;\n\n    /**\n     * @MethodName getContestACInfo\n     * @Params * @param null\n     * @Description 获取各个用户的ac情况，仅限于比赛管理者可查看\n     * @Return\n     * @Since 2021/1/17\n     */\n    @GetMapping(\"/get-contest-ac-info\")\n    @RequiresAuthentication\n    public CommonResult<IPage<ContestRecord>> getContestACInfo(@RequestParam(\"cid\") Long cid,\n                                                               @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                                               @RequestParam(value = \"limit\", required = false) Integer limit) {\n        return CommonResult.successResponse(contestAdminService.getContestACInfo(cid, currentPage, limit));\n    }\n\n    /**\n     * @MethodName checkContestACInfo\n     * @Params * @param null\n     * @Description 比赛管理员确定该次提交的ac情况\n     * @Return\n     * @Since 2021/1/17\n     */\n    @PutMapping(\"/check-contest-ac-info\")\n    @RequiresAuthentication\n    public CommonResult<Void> checkContestACInfo(@RequestBody CheckAcDTO checkACDTO) {\n        contestAdminService.checkContestAcInfo(checkACDTO);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/get-contest-print\")\n    @RequiresAuthentication\n    public CommonResult<IPage<ContestPrint>> getContestPrint(@RequestParam(\"cid\") Long cid,\n                                                             @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                                             @RequestParam(value = \"limit\", required = false) Integer limit) {\n        return CommonResult.successResponse(contestAdminService.getContestPrint(cid, currentPage, limit));\n    }\n\n    /**\n     * @param id\n     * @param cid\n     * @MethodName checkContestStatus\n     * @Description 更新该打印为确定状态\n     * @Return\n     * @Since 2021/9/20\n     */\n    @PutMapping(\"/check-contest-print-status\")\n    @RequiresAuthentication\n    public CommonResult<Void> checkContestPrintStatus(@RequestParam(\"id\") Long id, @RequestParam(\"cid\") Long cid) {\n        contestAdminService.checkContestPrintStatus(id, cid);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/ContestController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.ContestPrintDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ContestRankDTO;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterContestDTO;\nimport com.simplefanc.voj.backend.pojo.dto.UserReadContestAnnouncementDTO;\nimport com.simplefanc.voj.backend.pojo.vo.*;\nimport com.simplefanc.voj.backend.service.oj.ContestService;\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/27 21:40\n * @Description: 处理比赛模块的相关数据请求\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class ContestController {\n    private final ContestService contestService;\n\n    /**\n     * @MethodName getContestList\n     * @Params * @param null\n     * @Description 获取比赛列表分页数据\n     * @Return CommonResult\n     * @Since 2021/10/27\n     */\n    @GetMapping(\"/get-contest-list\")\n    public CommonResult<IPage<ContestVO>> getContestList(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                         @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                                         @RequestParam(value = \"status\", required = false) Integer status,\n                                                         @RequestParam(value = \"type\", required = false) Integer type,\n                                                         @RequestParam(value = \"keyword\", required = false) String keyword) {\n        return CommonResult.successResponse(contestService.getContestList(limit, currentPage, status, type, keyword));\n    }\n\n    /**\n     * @MethodName getContestInfo\n     * @Description 获得指定比赛的详细信息\n     * @Return\n     * @Since 2021/10/28\n     */\n    @GetMapping(\"/get-contest-info\")\n    @RequiresAuthentication\n    public CommonResult<ContestVO> getContestInfo(@RequestParam(value = \"cid\") Long cid) {\n        return CommonResult.successResponse(contestService.getContestInfo(cid));\n    }\n\n    /**\n     * @MethodName toRegisterContest\n     * @Description 注册比赛\n     * @Return\n     * @Since 2021/10/28\n     */\n    @PostMapping(\"/register-contest\")\n    @RequiresAuthentication\n    public CommonResult<Void> toRegisterContest(@RequestBody RegisterContestDTO registerContestDTO) {\n        contestService.toRegisterContest(registerContestDTO);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * @MethodName getContestAccess\n     * @Description 获得指定私有比赛的访问权限或保护比赛的提交权限\n     * @Return\n     * @Since 2021/10/28\n     */\n    @RequiresAuthentication\n    @GetMapping(\"/get-contest-access\")\n    public CommonResult<AccessVO> getContestAccess(@RequestParam(value = \"cid\") Long cid) {\n        return CommonResult.successResponse(contestService.getContestAccess(cid));\n    }\n\n    /**\n     * @MethodName getContestProblem\n     * @Description 获得指定比赛的题目列表\n     * @Return\n     * @Since 2021/10/28\n     */\n    @GetMapping(\"/get-contest-problem\")\n    @RequiresAuthentication\n    public CommonResult<List<ContestProblemVO>> getContestProblem(\n            @RequestParam(value = \"cid\") Long cid) {\n        return CommonResult.successResponse(contestService.getContestProblem(cid));\n    }\n\n    @GetMapping(\"/get-contest-problem-details\")\n    @RequiresAuthentication\n    public CommonResult<ProblemInfoVO> getContestProblemDetails(@RequestParam(value = \"cid\") Long cid,\n                                                                @RequestParam(value = \"displayId\") String displayId) {\n        return CommonResult.successResponse(contestService.getContestProblemDetails(cid, displayId));\n    }\n\n    @GetMapping(\"/contest-submissions\")\n    @RequiresAuthentication\n    public CommonResult<IPage<JudgeVO>> getContestSubmissionList(\n            @RequestParam(value = \"limit\", required = false) Integer limit,\n            @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n            @RequestParam(value = \"onlyMine\", required = false) Boolean onlyMine,\n            @RequestParam(value = \"problemID\", required = false) String displayId,\n            @RequestParam(value = \"status\", required = false) Integer searchStatus,\n            @RequestParam(value = \"username\", required = false) String searchUsername,\n            @RequestParam(value = \"contestID\") Long searchCid,\n            @RequestParam(value = \"beforeContestSubmit\") Boolean beforeContestSubmit,\n            @RequestParam(value = \"completeProblemID\", defaultValue = \"false\") Boolean completeProblemId) {\n        // TODO 参数过多\n        return CommonResult.successResponse(contestService.getContestSubmissionList(limit, currentPage, onlyMine,\n                displayId, searchStatus, searchUsername, searchCid, beforeContestSubmit, completeProblemId));\n    }\n\n    /**\n     * @MethodName getContestRank\n     * @Description 获得比赛做题记录以用来排名\n     * @Return\n     * @Since 2021/10/28\n     */\n    @PostMapping(\"/get-contest-rank\")\n    @RequiresAuthentication\n    public CommonResult<IPage> getContestRank(@RequestBody ContestRankDTO contestRankDTO) {\n        return CommonResult.successResponse(contestService.getContestRank(contestRankDTO));\n    }\n\n    /**\n     * @MethodName getContestAnnouncement\n     * @Description 获得比赛的通知列表\n     * @Return CommonResult\n     * @Since 2021/10/28\n     */\n    @GetMapping(\"/get-contest-announcement\")\n    @RequiresAuthentication\n    public CommonResult<IPage<AnnouncementVO>> getContestAnnouncement(\n            @RequestParam(value = \"cid\") Long cid,\n            @RequestParam(value = \"limit\", required = false) Integer limit,\n            @RequestParam(value = \"currentPage\", required = false) Integer currentPage) {\n        return CommonResult.successResponse(contestService.getContestAnnouncement(cid, limit, currentPage));\n    }\n\n    /**\n     * @param userReadContestAnnouncementDTO\n     * @MethodName getContestUserNotReadAnnouncement\n     * @Description 根据前端传过来的比赛id以及已阅读的公告提示id列表，排除后获取未阅读的公告\n     * @Return\n     * @Since 2021/7/17\n     */\n    @PostMapping(\"/get-contest-not-read-announcement\")\n    @RequiresAuthentication\n    public CommonResult<List<Announcement>> getContestUserNotReadAnnouncement(\n            @RequestBody UserReadContestAnnouncementDTO userReadContestAnnouncementDTO) {\n        return CommonResult\n                .successResponse(contestService.getContestUserNotReadAnnouncement(userReadContestAnnouncementDTO));\n    }\n\n    /**\n     * @param contestPrintDTO\n     * @MethodName submitPrintText\n     * @Description 提交比赛文本打印内容\n     * @Return\n     * @Since 2021/9/20\n     */\n    @PostMapping(\"/submit-print-text\")\n    @RequiresAuthentication\n    public CommonResult<Void> submitPrintText(@RequestBody ContestPrintDTO contestPrintDTO) {\n        contestService.submitPrintText(contestPrintDTO);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/ContestScoreboardController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.ContestRankDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ContestOutsideInfo;\nimport com.simplefanc.voj.backend.service.oj.ContestScoreboardService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 22:11\n * @Description: 处理比赛外榜的相关请求\n */\n\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class ContestScoreboardController {\n\n    private final ContestScoreboardService contestScoreboardService;\n\n    /**\n     * @param cid 比赛id\n     * @MethodName getContestOutsideInfo\n     * @Description 提供比赛外榜所需的比赛信息和题目信息\n     * @Return\n     * @Since 2021/12/8\n     */\n    @GetMapping(\"/get-contest-outsize-info\")\n    public CommonResult<ContestOutsideInfo> getContestOutsideInfo(\n            @RequestParam(value = \"cid\") Long cid) {\n        return CommonResult.successResponse(contestScoreboardService.getContestOutsideInfo(cid));\n    }\n\n    /**\n     * @MethodName getContestScoreBoard\n     * @Description 提供比赛外榜排名数据\n     * @Return\n     * @Since 2021/12/07\n     */\n    @PostMapping(\"/get-contest-outside-scoreboard\")\n    public CommonResult<IPage> getContestOutsideScoreboard(@RequestBody ContestRankDTO contestRankDTO) {\n        return CommonResult.successResponse(contestScoreboardService.getContestOutsideScoreboard(contestRankDTO));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/DiscussionController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.DiscussionVO;\nimport com.simplefanc.voj.backend.service.oj.DiscussionService;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionReport;\nimport com.simplefanc.voj.common.pojo.entity.problem.Category;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresPermissions;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/05/04 10:14\n * @Description: 负责讨论与评论模块的数据接口\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class DiscussionController {\n\n    private final DiscussionService discussionService;\n\n    @GetMapping(\"/discussions\")\n    public CommonResult<IPage<Discussion>> getDiscussionList(\n            @RequestParam(value = \"limit\", required = false, defaultValue = \"10\") Integer limit,\n            @RequestParam(value = \"currentPage\", required = false, defaultValue = \"1\") Integer currentPage,\n            @RequestParam(value = \"cid\", required = false) Integer categoryId,\n            @RequestParam(value = \"pid\", required = false) String pid,\n            @RequestParam(value = \"onlyMine\", required = false, defaultValue = \"false\") Boolean onlyMine,\n            @RequestParam(value = \"keyword\", required = false) String keyword,\n            @RequestParam(value = \"admin\", defaultValue = \"false\") Boolean admin) {\n\n        return CommonResult.successResponse(\n                discussionService.getDiscussionList(limit, currentPage, categoryId, pid, onlyMine, keyword, admin));\n\n    }\n\n    @GetMapping(\"/discussion\")\n    public CommonResult<DiscussionVO> getDiscussion(@RequestParam(value = \"did\") Integer did) {\n        return CommonResult.successResponse(discussionService.getDiscussion(did));\n    }\n\n    @PostMapping(\"/discussion\")\n    @RequiresPermissions(\"discussion_add\")\n    @RequiresAuthentication\n    public CommonResult<Void> addDiscussion(@RequestBody Discussion discussion) {\n        discussionService.addDiscussion(discussion);\n        return CommonResult.successResponse();\n    }\n\n    @PutMapping(\"/discussion\")\n    @RequiresPermissions(\"discussion_edit\")\n    @RequiresAuthentication\n    public CommonResult<Void> updateDiscussion(@RequestBody Discussion discussion) {\n        discussionService.updateDiscussion(discussion);\n        return CommonResult.successResponse();\n    }\n\n    @DeleteMapping(\"/discussion\")\n    @RequiresPermissions(\"discussion_del\")\n    @RequiresAuthentication\n    public CommonResult<Void> removeDiscussion(@RequestParam(\"did\") Integer did) {\n        discussionService.removeDiscussion(did);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/discussion-like\")\n    @RequiresAuthentication\n    public CommonResult<Void> addDiscussionLike(@RequestParam(\"did\") Integer did,\n                                                @RequestParam(\"toLike\") Boolean toLike) {\n        discussionService.addDiscussionLike(did, toLike);\n        return CommonResult.successResponse();\n    }\n\n    @GetMapping(\"/discussion-category\")\n    public CommonResult<List<Category>> getDiscussionCategory() {\n        return CommonResult.successResponse(discussionService.getDiscussionCategory());\n    }\n\n    @PostMapping(\"/discussion-report\")\n    @RequiresAuthentication\n    public CommonResult<Void> addDiscussionReport(@RequestBody DiscussionReport discussionReport) {\n        discussionService.addDiscussionReport(discussionReport);\n        return CommonResult.successResponse();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/HomeController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.WebConfigDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ACMRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.backend.pojo.vo.ContestVO;\nimport com.simplefanc.voj.backend.service.admin.system.ConfigService;\nimport com.simplefanc.voj.backend.service.oj.HomeService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/26 14:12\n * @Description: 处理首页的请求\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class HomeController {\n\n    private final HomeService homeService;\n\n    private final ConfigService configService;\n\n    /**\n     * @MethodName getRecentContest\n     * @Params * @param null\n     * @Description 获取最近14天的比赛信息列表\n     * @Return CommonResult\n     * @Since 2021/12/29\n     */\n    @GetMapping(\"/get-recent-contest\")\n    public CommonResult<List<ContestVO>> getRecentContest() {\n        return CommonResult.successResponse(homeService.getRecentContest());\n    }\n\n    /**\n     * @MethodName getHomeCarousel\n     * @Params\n     * @Description 获取主页轮播图\n     * @Return\n     * @Since 2021/9/4\n     */\n    @GetMapping(\"/home-carousel\")\n    public CommonResult<List<HashMap<String, Object>>> getHomeCarousel() {\n        return CommonResult.successResponse(homeService.getHomeCarousel());\n    }\n\n    /**\n     * @MethodName getRecentSevenACRank\n     * @Params * @param null\n     * @Description 获取最近7天用户做题榜单\n     * @Return\n     * @Since 2021/1/15\n     */\n    @GetMapping(\"/get-recent-seven-ac-rank\")\n    public CommonResult<List<ACMRankVO>> getRecentSevenACRank() {\n        return CommonResult.successResponse(homeService.getRecentSevenACRank());\n    }\n\n    /**\n     * @MethodName getRecentOtherContest\n     * @Params * @param null\n     * @Description 获取最近其他OJ的比赛信息列表\n     * @Return CommonResult\n     * @Since 2021/1/15\n     */\n    @GetMapping(\"/get-recent-other-contest\")\n    public CommonResult<List<HashMap<String, Object>>> getRecentOtherContest() {\n        return CommonResult.successResponse(homeService.getRecentOtherContest());\n    }\n\n    /**\n     * @MethodName getCommonAnnouncement\n     * @Params * @param null\n     * @Description 获取主页公告列表\n     * @Return CommonResult\n     * @Since 2021/12/29\n     */\n    @GetMapping(\"/get-common-announcement\")\n    public CommonResult<IPage<AnnouncementVO>> getCommonAnnouncement(\n            @RequestParam(value = \"limit\", required = false) Integer limit,\n            @RequestParam(value = \"currentPage\", required = false) Integer currentPage) {\n        return CommonResult.successResponse(homeService.getCommonAnnouncement(limit, currentPage));\n    }\n\n    /**\n     * @MethodName getWebConfig\n     * @Params * @param null\n     * @Description 获取网站的基础配置。例如名字，缩写名字等等。\n     * @Return CommonResult\n     * @Since 2021/12/29\n     */\n    @GetMapping(\"/get-website-config\")\n    public CommonResult<WebConfigDTO> getWebConfig() {\n        return CommonResult.successResponse(configService.getWebConfig());\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/JudgeController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.SubmitIdListDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ToJudgeDTO;\nimport com.simplefanc.voj.backend.pojo.vo.JudgeVO;\nimport com.simplefanc.voj.backend.pojo.vo.SubmissionInfoVO;\nimport com.simplefanc.voj.backend.service.oj.JudgeService;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.apache.shiro.authz.annotation.RequiresPermissions;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/27 20:52\n * @Description: 处理代码评判相关业务\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class JudgeController {\n\n    private final JudgeService judgeService;\n\n    /**\n     * @MethodName submitProblemJudge\n     * @Description 核心方法 判题通过openfeign调用判题系统服务\n     * @Return CommonResult\n     * @Since 2021/10/30\n     */\n    @RequiresAuthentication\n    @RequiresPermissions(\"submit\")\n    @RequestMapping(value = \"/submit-problem-judge\", method = RequestMethod.POST)\n    public CommonResult<Judge> submitProblemJudge(@RequestBody ToJudgeDTO judgeDTO) {\n        return CommonResult.successResponse(judgeService.submitProblemJudge(judgeDTO));\n    }\n\n    /**\n     * @MethodName resubmit\n     * @Description 调用判题服务器提交失败超过60s后，用户点击按钮重新提交判题进入的方法\n     * @Return\n     * @Since 2021/2/12\n     */\n    @RequiresAuthentication\n    @GetMapping(value = \"/resubmit\")\n    public CommonResult<Judge> resubmit(@RequestParam(\"submitId\") Long submitId) {\n        return CommonResult.successResponse(judgeService.resubmit(submitId));\n    }\n\n    /**\n     * @MethodName getSubmission\n     * @Description 获取单个提交记录的详情\n     * @Return CommonResult\n     * @Since 2021/1/2\n     */\n    @GetMapping(\"/submission\")\n    public CommonResult<SubmissionInfoVO> getSubmission(\n            @RequestParam(value = \"submitId\") Long submitId) {\n        return CommonResult.successResponse(judgeService.getSubmission(submitId));\n    }\n\n    /**\n     * @MethodName updateSubmission\n     * @Description 修改单个提交详情的分享权限\n     * @Return CommonResult\n     * @Since 2021/1/2\n     */\n    @PutMapping(\"/submission\")\n    @RequiresAuthentication\n    public CommonResult<Void> updateSubmission(@RequestBody Judge judge) {\n        judgeService.updateSubmission(judge);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * @param limit\n     * @param currentPage\n     * @param onlyMine\n     * @param searchPid\n     * @param searchStatus\n     * @param searchUsername\n     * @param completeProblemId\n     * @MethodName getJudgeList\n     * @Description 通用查询判题记录列表\n     * @Return CommonResult\n     * @Since 2021/10/29\n     */\n    @RequestMapping(value = \"/submissions\", method = RequestMethod.GET)\n    public CommonResult<IPage<JudgeVO>> getJudgeList(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                     @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                                     @RequestParam(value = \"onlyMine\", required = false) Boolean onlyMine,\n                                                     @RequestParam(value = \"problemID\", required = false) String searchPid,\n                                                     @RequestParam(value = \"status\", required = false) Integer searchStatus,\n                                                     @RequestParam(value = \"username\", required = false) String searchUsername,\n                                                     @RequestParam(value = \"completeProblemID\", defaultValue = \"false\") Boolean completeProblemId) {\n        return CommonResult.successResponse(judgeService.getJudgeList(limit, currentPage, onlyMine, searchPid,\n                searchStatus, searchUsername, completeProblemId));\n    }\n\n    /**\n     * @MethodName checkJudgeResult\n     * @Description 对提交列表状态为Pending和Judging的提交进行更新检查\n     * @Return\n     * @Since 2021/1/3\n     */\n    @RequestMapping(value = \"/check-submissions-status\", method = RequestMethod.POST)\n    public CommonResult<HashMap<Long, Object>> checkCommonJudgeResult(@RequestBody SubmitIdListDTO submitIdListDTO) {\n        return CommonResult.successResponse(judgeService.checkCommonJudgeResult(submitIdListDTO));\n    }\n\n    /**\n     * @param submitIdListDTO\n     * @MethodName checkContestJudgeResult\n     * @Description 需要检查是否为封榜，是否可以查询结果，避免有人恶意查询\n     * @Return\n     * @Since 2021/6/11\n     */\n    @RequestMapping(value = \"/check-contest-submissions-status\", method = RequestMethod.POST)\n    @RequiresAuthentication\n    public CommonResult<HashMap<Long, Object>> checkContestJudgeResult(@RequestBody SubmitIdListDTO submitIdListDTO) {\n        return CommonResult.successResponse(judgeService.checkContestJudgeResult(submitIdListDTO));\n    }\n\n    /**\n     * @param submitId\n     * @MethodName getJudgeCase\n     * @Description 获得指定提交id的测试样例结果，暂不支持查看测试数据，只可看测试点结果，时间，空间，或者IO得分\n     * @Return\n     * @Since 2021/10/29\n     */\n    @GetMapping(\"/get-all-case-result\")\n    public CommonResult<List<JudgeCase>> getALLCaseResult(\n            @RequestParam(value = \"submitId\") Long submitId) {\n        return CommonResult.successResponse(judgeService.getAllCaseResult(submitId));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/PassportController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.simplefanc.voj.backend.pojo.dto.ApplyResetPasswordDTO;\nimport com.simplefanc.voj.backend.pojo.dto.LoginDTO;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ResetPasswordDTO;\nimport com.simplefanc.voj.backend.pojo.vo.RegisterCodeVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserInfoVO;\nimport com.simplefanc.voj.backend.service.account.PassportService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 17:00\n * @Description: 处理登录、注册、重置密码\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class PassportController {\n\n    private final PassportService passportService;\n\n    /**\n     * @param loginDTO\n     * @MethodName login\n     * @Description 处理登录逻辑\n     * @Return CommonResult\n     * @Since 2021/10/24\n     */\n    @PostMapping(\"/login\")\n    public CommonResult<UserInfoVO> login(@Validated @RequestBody LoginDTO loginDTO, HttpServletResponse response,\n                                          HttpServletRequest request) {\n        return CommonResult.successResponse(passportService.login(loginDTO, response, request));\n    }\n\n    /**\n     * @MethodName getRegisterCode\n     * @Description 调用邮件服务，发送注册流程的6位随机验证码\n     * @Return\n     * @Since 2021/10/26\n     */\n    @RequestMapping(value = \"/get-register-code\", method = RequestMethod.GET)\n    public CommonResult<RegisterCodeVO> getRegisterCode(@RequestParam(value = \"email\") String email) {\n        return CommonResult.successResponse(passportService.getRegisterCode(email));\n    }\n\n    /**\n     * @param registerDTO\n     * @MethodName register\n     * @Description 注册逻辑，具体参数请看RegisterDTO类\n     * @Return\n     * @Since 2021/10/24\n     */\n    @PostMapping(\"/register\")\n    public CommonResult<Void> register(@Validated @RequestBody RegisterDTO registerDTO) {\n        passportService.register(registerDTO);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * @param applyResetPasswordDTO\n     * @MethodName applyResetPassword\n     * @Description 发送重置密码的链接邮件\n     * @Return\n     * @Since 2021/11/6\n     */\n    @PostMapping(\"/apply-reset-password\")\n    public CommonResult<Void> applyResetPassword(@RequestBody ApplyResetPasswordDTO applyResetPasswordDTO) {\n        passportService.applyResetPassword(applyResetPasswordDTO);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * @param resetPasswordDTO\n     * @MethodName resetPassword\n     * @Description 用户重置密码\n     * @Return\n     * @Since 2021/11/6\n     */\n    @PostMapping(\"/reset-password\")\n    public CommonResult<Void> resetPassword(@RequestBody ResetPasswordDTO resetPasswordDTO) {\n        passportService.resetPassword(resetPasswordDTO);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * @MethodName logout\n     * @Description 退出逻辑，将jwt在redis中清除，下次需要再次登录。\n     * @Return CommonResult\n     * @Since 2021/10/24\n     */\n    @GetMapping(\"/logout\")\n    @RequiresAuthentication\n    public CommonResult<Void> logout() {\n        UserSessionUtil.logout();\n        return CommonResult.successResponse(\"登出成功！\");\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/ProblemController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.dto.PidListDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemInfoVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.backend.pojo.vo.RandomProblemVO;\nimport com.simplefanc.voj.backend.service.oj.ProblemService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/27 13:24\n * @Description: 问题数据控制类，处理题目列表请求，题目内容请求。\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class ProblemController {\n\n    private final ProblemService problemService;\n\n    /**\n     * @param currentPage\n     * @param keyword\n     * @param tagId\n     * @param difficulty\n     * @param oj\n     * @MethodName getProblemList\n     * @Description 获取题目列表分页\n     * @Return CommonResult\n     * @Since 2021/10/27\n     */\n    @GetMapping(value = \"/get-problem-list\")\n    public CommonResult<Page<ProblemVO>> getProblemList(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                                        @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                                        @RequestParam(value = \"keyword\", required = false) String keyword,\n                                                        @RequestParam(value = \"tagId\", required = false) List<Long> tagId,\n                                                        @RequestParam(value = \"difficulty\", required = false) Integer difficulty,\n                                                        @RequestParam(value = \"oj\", required = false) String oj,\n                                                        @RequestParam(value = \"problemVisible\", required = false) Boolean problemVisible) {\n        return CommonResult.successResponse(problemService.getProblemList(limit, currentPage, keyword, tagId, difficulty, oj, problemVisible));\n    }\n\n    /**\n     * @MethodName getRandomProblem\n     * @Description 随机选取一道题目\n     * @Return CommonResult\n     * @Since 2021/10/27\n     */\n    @GetMapping(\"/get-random-problem\")\n    public CommonResult<RandomProblemVO> getRandomProblem() {\n        return CommonResult.successResponse(problemService.getRandomProblem());\n    }\n\n    /**\n     * @param pidListDTO\n     * @MethodName getUserProblemStatus\n     * @Description 获取用户对应该题目列表中各个题目的做题情况\n     * @Return CommonResult\n     * @Since 2021/12/29\n     */\n    @RequiresAuthentication\n    @PostMapping(\"/get-user-problem-status\")\n    public CommonResult<HashMap<Long, Object>> getUserProblemStatus(@Validated @RequestBody PidListDTO pidListDTO) {\n        return CommonResult.successResponse(problemService.getUserProblemStatus(pidListDTO));\n    }\n\n    /**\n     * @param problemId\n     * @MethodName getProblemInfo\n     * @Description 获取指定题目的详情信息，标签，所支持语言，做题情况（只能查询公开题目 也就是auth为1）\n     * @Return CommonResult\n     * @Since 2021/10/27\n     */\n    @RequestMapping(value = \"/get-problem\", method = RequestMethod.GET)\n    public CommonResult<ProblemInfoVO> getProblemInfo(\n            @RequestParam(value = \"problemId\") String problemId) {\n        return CommonResult.successResponse(problemService.getProblemInfo(problemId));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/RankController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.service.oj.RankService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/27 20:53\n * @Description: 处理排行榜数据\n */\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class RankController {\n\n    private final RankService rankService;\n\n    /**\n     * @MethodName get-rank-list\n     * @Params * @param null\n     * @Description 获取排行榜数据\n     * @Return CommonResult\n     * @Since 2021/10/27\n     */\n    @GetMapping(\"/get-rank-list\")\n    public CommonResult<IPage> getRankList(@RequestParam(value = \"limit\", required = false) Integer limit,\n                                           @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n                                           @RequestParam(value = \"searchUser\", required = false) String searchUser,\n                                           @RequestParam(value = \"type\") Integer type) {\n        return CommonResult.successResponse(rankService.getRankList(limit, currentPage, searchUser, type));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/controller/oj/TrainingController.java",
    "content": "package com.simplefanc.voj.backend.controller.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterTrainingDTO;\nimport com.simplefanc.voj.backend.pojo.vo.AccessVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingVO;\nimport com.simplefanc.voj.backend.service.oj.TrainingService;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authz.annotation.RequiresAuthentication;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/19 21:42\n * @Description: 处理训练题单的请求\n */\n\n@RestController\n@RequestMapping(\"/api\")\n@RequiredArgsConstructor\npublic class TrainingController {\n\n    private final TrainingService trainingService;\n\n    /**\n     * @param limit\n     * @param currentPage\n     * @param keyword\n     * @param categoryId\n     * @param auth\n     * @MethodName getTrainingList\n     * @Description 获取训练题单列表，可根据关键词、类别、权限、类型过滤\n     * @Return\n     * @Since 2021/11/20\n     */\n    @GetMapping(\"/get-training-list\")\n    public CommonResult<IPage<TrainingVO>> getTrainingList(\n            @RequestParam(value = \"limit\", required = false) Integer limit,\n            @RequestParam(value = \"currentPage\", required = false) Integer currentPage,\n            @RequestParam(value = \"keyword\", required = false) String keyword,\n            @RequestParam(value = \"categoryId\", required = false) Long categoryId,\n            @RequestParam(value = \"auth\", required = false) String auth) {\n        return CommonResult\n                .successResponse(trainingService.getTrainingList(limit, currentPage, keyword, categoryId, auth));\n    }\n\n    /**\n     * @param tid\n     * @MethodName getTraining\n     * @Description 根据tid获取指定训练详情\n     * @Return\n     * @Since 2021/11/20\n     */\n    @GetMapping(\"/get-training-detail\")\n    @RequiresAuthentication\n    public CommonResult<TrainingVO> getTraining(@RequestParam(value = \"tid\") Long tid) {\n        return CommonResult.successResponse(trainingService.getTraining(tid));\n    }\n\n    /**\n     * @param tid\n     * @MethodName getTrainingProblemList\n     * @Description 根据tid获取指定训练的题单题目列表\n     * @Return\n     * @Since 2021/11/20\n     */\n    @GetMapping(\"/get-training-problem-list\")\n    @RequiresAuthentication\n    public CommonResult<List<ProblemVO>> getTrainingProblemList(@RequestParam(value = \"tid\") Long tid) {\n        return CommonResult.successResponse(trainingService.getTrainingProblemList(tid));\n    }\n\n    /**\n     * @param registerTrainingDTO\n     * @MethodName toRegisterTraining\n     * @Description 注册校验私有权限的训练\n     * @Return\n     * @Since 2021/11/20\n     */\n    @PostMapping(\"/register-training\")\n    @RequiresAuthentication\n    public CommonResult<Void> toRegisterTraining(@RequestBody RegisterTrainingDTO registerTrainingDTO) {\n        trainingService.toRegisterTraining(registerTrainingDTO);\n        return CommonResult.successResponse();\n    }\n\n    /**\n     * @param tid\n     * @MethodName getTrainingAccess\n     * @Description 私有权限的训练需要获取当前用户是否有进入训练的权限\n     * @Return\n     * @Since 2021/11/20\n     */\n    @RequiresAuthentication\n    @GetMapping(\"/get-training-access\")\n    public CommonResult<AccessVO> getTrainingAccess(@RequestParam(value = \"tid\") Long tid) {\n        return CommonResult.successResponse(trainingService.getTrainingAccess(tid));\n    }\n\n    /**\n     * @param tid\n     * @param limit\n     * @param currentPage\n     * @MethodName getTrainingRank\n     * @Description 获取训练的排行榜分页\n     * @Return\n     * @Since 2021/11/22\n     */\n    @GetMapping(\"/get-training-rank\")\n    @RequiresAuthentication\n    public CommonResult<IPage<TrainingRankVO>> getTrainingRank(@RequestParam(value = \"tid\") Long tid,\n                                                               @RequestParam(value = \"limit\", required = false) Integer limit,\n                                                               @RequestParam(value = \"currentPage\", required = false) Integer currentPage) {\n        return CommonResult.successResponse(trainingService.getTrainingRank(tid, limit, currentPage));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/common/AnnouncementEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.common;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface AnnouncementEntityService extends IService<Announcement> {\n\n    IPage<AnnouncementVO> getAnnouncementList(int limit, int currentPage, Boolean notAdmin);\n\n    IPage<AnnouncementVO> getContestAnnouncement(Long cid, Boolean notAdmin, int limit, int currentPage);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/common/FileEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.common;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.common.File;\n\nimport java.util.List;\n\npublic interface FileEntityService extends IService<File> {\n\n    int updateFileToDeleteByUidAndType(String uid, String type);\n\n    List<File> queryDeleteAvatarList();\n\n    List<File> queryCarouselFileList();\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/common/impl/AnnouncementEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.common.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.common.AnnouncementEntityService;\nimport com.simplefanc.voj.backend.mapper.AnnouncementMapper;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class AnnouncementEntityServiceImpl extends ServiceImpl<AnnouncementMapper, Announcement>\n        implements AnnouncementEntityService {\n\n    private final AnnouncementMapper announcementMapper;\n\n    @Override\n    public IPage<AnnouncementVO> getAnnouncementList(int limit, int currentPage, Boolean notAdmin) {\n        // 新建分页\n        Page<AnnouncementVO> page = new Page<>(currentPage, limit);\n        return announcementMapper.getAnnouncementList(page, notAdmin);\n    }\n\n    @Override\n    public IPage<AnnouncementVO> getContestAnnouncement(Long cid, Boolean notAdmin, int limit, int currentPage) {\n        Page<AnnouncementVO> page = new Page<>(currentPage, limit);\n        return announcementMapper.getContestAnnouncement(page, cid, notAdmin);\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/common/impl/FileEntityEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.common.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.common.FileEntityService;\nimport com.simplefanc.voj.backend.mapper.FileMapper;\nimport com.simplefanc.voj.common.pojo.entity.common.File;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/11 14:05\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class FileEntityEntityServiceImpl extends ServiceImpl<FileMapper, File> implements FileEntityService {\n\n    private final FileMapper fileMapper;\n\n    @Override\n    public int updateFileToDeleteByUidAndType(String uid, String type) {\n        return fileMapper.updateFileToDeleteByUidAndType(uid, type);\n    }\n\n    @Override\n    public List<File> queryDeleteAvatarList() {\n        return fileMapper.queryDeleteAvatarList();\n    }\n\n    @Override\n    public List<File> queryCarouselFileList() {\n        return fileMapper.queryCarouselFileList();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/ContestAnnouncementEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.contest;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestAnnouncement;\n\npublic interface ContestAnnouncementEntityService extends IService<ContestAnnouncement> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/ContestEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.contest;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.ContestVO;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\n\nimport java.util.List;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface ContestEntityService extends IService<Contest> {\n\n    List<ContestVO> getWithinNext14DaysContests();\n\n    IPage<ContestVO> getContestList(Integer limit, Integer currentPage, Integer type, Integer status, String keyword);\n\n    ContestVO getContestInfoById(long cid);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/ContestPrintEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.contest;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestPrint;\n\n/**\n * @Author: chenfan\n * @Date: 2021/9/19 21:05\n * @Description:\n */\npublic interface ContestPrintEntityService extends IService<ContestPrint> {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/ContestProblemEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.contest;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.ContestProblemVO;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface ContestProblemEntityService extends IService<ContestProblem> {\n\n    List<ContestProblemVO> getContestProblemList(Long cid, Date startTime, Date endTime, Date sealTime, Boolean isAdmin,\n                                                 String contestAuthorUid);\n\n    void syncContestRecord(Long pid, Long cid, String displayId);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/ContestRecordEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.contest;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.ContestRecordVO;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface ContestRecordEntityService extends IService<ContestRecord> {\n\n    IPage<ContestRecord> getACInfo(Integer currentPage, Integer limit, Integer status, Long cid,\n                                   String contestCreatorId);\n\n    List<ContestRecordVO> getOIContestRecord(Contest contest, Boolean isOpenSealRank);\n\n    List<ContestRecordVO> getACMContestRecord(Long cid, Date startTime);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/ContestRegisterEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.contest;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRegister;\n\nimport java.util.Set;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface ContestRegisterEntityService extends IService<ContestRegister> {\n    Set<String> getRegisteredUsers(Long cid);\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/impl/ContestAnnouncementEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.contest.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.contest.ContestAnnouncementEntityService;\nimport com.simplefanc.voj.backend.mapper.ContestAnnouncementMapper;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestAnnouncement;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/21 22:59\n * @Description:\n */\n@Service\npublic class ContestAnnouncementEntityServiceImpl extends ServiceImpl<ContestAnnouncementMapper, ContestAnnouncement>\n        implements ContestAnnouncementEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/impl/ContestEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.contest.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.mapper.ContestMapper;\nimport com.simplefanc.voj.backend.pojo.vo.ContestRegisterCountVO;\nimport com.simplefanc.voj.backend.pojo.vo.ContestVO;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class ContestEntityServiceImpl extends ServiceImpl<ContestMapper, Contest> implements ContestEntityService {\n\n    private final ContestMapper contestMapper;\n    private final ContestValidator contestValidator;\n\n    @Override\n    public List<ContestVO> getWithinNext14DaysContests() {\n        List<Contest> contestList = contestMapper.getWithinNext14DaysContests();\n\n        final List<ContestVO> contestVOList = contestList.stream()\n                .filter(contestValidator::checkVisible)\n                .map(contest -> BeanUtil.copyProperties(contest, ContestVO.class))\n                .collect(Collectors.toList());\n\n        setRegisterCount(contestVOList);\n\n        return contestVOList;\n    }\n\n    @Override\n    public IPage<ContestVO> getContestList(Integer limit, Integer currentPage, Integer type, Integer status,\n                                           String keyword) {\n        // 新建分页\n        IPage<ContestVO> page = new Page<>(currentPage, limit);\n\n        List<Contest> contestList = contestMapper.getContestList(page, type, status, keyword);\n\n        final List<ContestVO> contestVOList = contestList.stream()\n                .filter(contestValidator::checkVisible)\n                .map(contest -> BeanUtil.copyProperties(contest, ContestVO.class))\n                .collect(Collectors.toList());\n\n        setRegisterCount(contestVOList);\n\n        return page.setRecords(contestVOList);\n    }\n\n    @Override\n    public ContestVO getContestInfoById(long cid) {\n        List<Long> cidList = Collections.singletonList(cid);\n        Contest contest = contestMapper.selectById(cid);\n        if (contestValidator.checkVisible(contest)) {\n            ContestVO contestVO = BeanUtil.copyProperties(contest, ContestVO.class);\n            List<ContestRegisterCountVO> contestRegisterCountVOList = contestMapper.getContestRegisterCount(cidList);\n            if (!CollectionUtils.isEmpty(contestRegisterCountVOList)) {\n                ContestRegisterCountVO contestRegisterCountVO = contestRegisterCountVOList.get(0);\n                contestVO.setCount(contestRegisterCountVO.getCount());\n            }\n            return contestVO;\n        }\n        return null;\n    }\n\n    /**\n     * 获取比赛注册人数\n     * @param contestList\n     */\n    private void setRegisterCount(List<ContestVO> contestList) {\n        List<Long> cidList = contestList.stream().map(ContestVO::getId).collect(Collectors.toList());\n        if (!CollectionUtils.isEmpty(cidList)) {\n            List<ContestRegisterCountVO> contestRegisterCountVOList = contestMapper.getContestRegisterCount(cidList);\n            for (ContestRegisterCountVO contestRegisterCountVO : contestRegisterCountVOList) {\n                for (ContestVO contestVO : contestList) {\n                    if (contestRegisterCountVO.getCid().equals(contestVO.getId())) {\n                        contestVO.setCount(contestRegisterCountVO.getCount());\n                        break;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/impl/ContestPrintEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.contest.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.contest.ContestPrintEntityService;\nimport com.simplefanc.voj.backend.mapper.ContestPrintMapper;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestPrint;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/9/19 21:05\n * @Description:\n */\n@Service\npublic class ContestPrintEntityServiceImpl extends ServiceImpl<ContestPrintMapper, ContestPrint>\n        implements ContestPrintEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/impl/ContestProblemEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.contest.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.contest.ContestProblemEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestRecordEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.mapper.ContestProblemMapper;\nimport com.simplefanc.voj.backend.pojo.vo.ContestProblemVO;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class ContestProblemEntityServiceImpl extends ServiceImpl<ContestProblemMapper, ContestProblem>\n        implements ContestProblemEntityService {\n\n    private final ContestProblemMapper contestProblemMapper;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    @Override\n    public List<ContestProblemVO> getContestProblemList(Long cid, Date startTime, Date endTime, Date sealTime,\n                                                        Boolean isAdmin, String contestAuthorUid) {\n        List<String> superAdminUidList = userInfoEntityService.getSuperAdminUidList();\n        superAdminUidList.add(contestAuthorUid);\n\n        return contestProblemMapper.getContestProblemList(cid, startTime, endTime, sealTime, isAdmin,\n                superAdminUidList);\n    }\n\n    @Async\n    @Override\n    public void syncContestRecord(Long pid, Long cid, String displayId) {\n\n        UpdateWrapper<ContestRecord> updateWrapper = new UpdateWrapper<>();\n        updateWrapper.eq(\"pid\", pid).eq(\"cid\", cid).set(\"display_id\", displayId);\n        contestRecordEntityService.update(updateWrapper);\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/impl/ContestRecordEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.contest.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.contest.ContestRecordEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.mapper.ContestRecordMapper;\nimport com.simplefanc.voj.backend.pojo.vo.ContestRecordVO;\nimport com.simplefanc.voj.common.constants.ContestConstant;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\n\nimport java.util.*;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class ContestRecordEntityServiceImpl extends ServiceImpl<ContestRecordMapper, ContestRecord>\n        implements ContestRecordEntityService {\n\n    private final ContestRecordMapper contestRecordMapper;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final RedisUtil redisUtil;\n\n    @Override\n    public IPage<ContestRecord> getACInfo(Integer currentPage, Integer limit, Integer status, Long cid,\n                                          String contestCreatorId) {\n\n        List<ContestRecord> acInfo = contestRecordMapper.getACInfo(status, cid);\n\n        HashMap<Long, String> pidMapUidAndPid = new HashMap<>(12);\n        HashMap<String, Long> UidAndPidMapTime = new HashMap<>(12);\n\n        List<String> superAdminUidList = userInfoEntityService.getSuperAdminUidList();\n\n        List<ContestRecord> userACInfo = new LinkedList<>();\n\n        for (ContestRecord contestRecord : acInfo) {\n            // 超级管理员和比赛创建者的提交跳过\n            if (contestRecord.getUid().equals(contestCreatorId) || superAdminUidList.contains(contestRecord.getUid())) {\n                continue;\n            }\n\n            contestRecord.setFirstBlood(false);\n            String uidAndPid = pidMapUidAndPid.get(contestRecord.getPid());\n            if (uidAndPid == null) {\n                pidMapUidAndPid.put(contestRecord.getPid(), contestRecord.getUid() + contestRecord.getPid());\n                UidAndPidMapTime.put(contestRecord.getUid() + contestRecord.getPid(), contestRecord.getTime());\n            } else {\n                Long firstTime = UidAndPidMapTime.get(uidAndPid);\n                Long tmpTime = contestRecord.getTime();\n                if (tmpTime < firstTime) {\n                    pidMapUidAndPid.put(contestRecord.getPid(), contestRecord.getUid() + contestRecord.getPid());\n                    UidAndPidMapTime.put(contestRecord.getUid() + contestRecord.getPid(), tmpTime);\n                }\n            }\n            userACInfo.add(contestRecord);\n        }\n\n        List<ContestRecord> pageList = new ArrayList<>();\n\n        int count = userACInfo.size();\n\n        // 计算当前页第一条数据的下标\n        int currId = currentPage > 1 ? (currentPage - 1) * limit : 0;\n        for (int i = 0; i < limit && i < count - currId; i++) {\n            ContestRecord contestRecord = userACInfo.get(currId + i);\n            if (pidMapUidAndPid.get(contestRecord.getPid()).equals(contestRecord.getUid() + contestRecord.getPid())) {\n                contestRecord.setFirstBlood(true);\n            }\n            pageList.add(contestRecord);\n        }\n\n        Page<ContestRecord> page = new Page<>(currentPage, limit);\n        page.setSize(limit);\n        page.setCurrent(currentPage);\n        page.setTotal(count);\n        page.setRecords(pageList);\n\n        return page;\n    }\n\n    @Override\n    @Cacheable(value = RedisConstant.OI_CONTEST_RANK_CACHE, key = \"#contest.oiRankScoreType+'-'+#contest.id\", condition = \"#isOpenSealRank\")\n    public List<ContestRecordVO> getOIContestRecord(Contest contest, Boolean isOpenSealRank) {\n        String oiRankScoreType = contest.getOiRankScoreType();\n        Long cid = contest.getId();\n        Date sealTime = contest.getSealRankTime();\n        Date startTime = contest.getStartTime();\n        Date endTime = contest.getEndTime();\n\n        if (Objects.equals(ContestConstant.OI_RANK_RECENT_SCORE, oiRankScoreType)) {\n            return contestRecordMapper.getOIContestRecordByRecentSubmission(cid, isOpenSealRank, sealTime,\n                    startTime, endTime);\n        } else {\n            return contestRecordMapper.getOIContestRecordByHighestSubmission(cid, isOpenSealRank, sealTime,\n                    startTime, endTime);\n        }\n\n//        if (!isOpenSealRank) {\n//            // 封榜解除 获取最新数据\n//            // 获取每个用户每道题最近一次提交\n//            if (Objects.equals(ContestConstant.OI_RANK_RECENT_SCORE, oiRankScoreType)) {\n//                return contestRecordMapper.getOIContestRecordByRecentSubmission(cid, false, sealTime,\n//                        startTime, endTime);\n//            } else {\n//                return contestRecordMapper.getOIContestRecordByHighestSubmission(cid, false, sealTime,\n//                        startTime, endTime);\n//            }\n//        } else {\n//            String key = ContestConstant.OI_CONTEST_RANK_CACHE + \"_\" + oiRankScoreType + \"_\" + cid;\n//            List<ContestRecordVO> oiContestRecordList = (List<ContestRecordVO>) redisUtil.get(key);\n//            if (oiContestRecordList == null) {\n//                if (Objects.equals(ContestConstant.OI_RANK_RECENT_SCORE, oiRankScoreType)) {\n//                    return contestRecordMapper.getOIContestRecordByRecentSubmission(cid,\n//                            true, sealTime, startTime, endTime);\n//                } else {\n//                    return contestRecordMapper.getOIContestRecordByHighestSubmission(cid,\n//                            true, sealTime, startTime, endTime);\n//                }\n//                redisUtil.set(key, oiContestRecordList, 2 * 3600);\n//            }\n//            return oiContestRecordList;\n    }\n\n\n    @Override\n    public List<ContestRecordVO> getACMContestRecord(Long cid, Date startTime) {\n        return contestRecordMapper.getACMContestRecord(cid, startTime);\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/contest/impl/ContestRegisterEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.contest.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.contest.ContestRegisterEntityService;\nimport com.simplefanc.voj.backend.mapper.ContestRegisterMapper;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRegister;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\npublic class ContestRegisterEntityServiceImpl extends ServiceImpl<ContestRegisterMapper, ContestRegister>\n        implements ContestRegisterEntityService {\n\n    @Override\n    public Set<String> getRegisteredUsers(Long cid) {\n        return this.lambdaQuery()\n                .select(ContestRegister::getUid)\n                .eq(ContestRegister::getCid, cid)\n                .list()\n                .stream()\n                .map(ContestRegister::getUid)\n                .collect(Collectors.toSet());\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/CommentEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.CommentVO;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Comment;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\n\nimport java.util.List;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface CommentEntityService extends IService<Comment> {\n\n    IPage<CommentVO> getCommentList(int limit, int currentPage, Long cid, Integer did, Boolean isRoot, String uid);\n\n    List<Reply> getAllReplyByCommentId(Long cid, String uid, Boolean isRoot, Integer commentId);\n\n    void updateCommentMsg(String recipientId, String senderId, String content, Integer discussionId);\n\n    void updateCommentLikeMsg(String recipientId, String senderId, Integer sourceId, String sourceType);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/CommentLikeEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.discussion.CommentLike;\n\npublic interface CommentLikeEntityService extends IService<CommentLike> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/DiscussionEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.DiscussionVO;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\n\npublic interface DiscussionEntityService extends IService<Discussion> {\n\n    DiscussionVO getDiscussion(Integer did, String uid);\n\n    void updatePostLikeMsg(String recipientId, String senderId, Integer discussionId);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/DiscussionLikeEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionLike;\n\npublic interface DiscussionLikeEntityService extends IService<DiscussionLike> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/DiscussionReportEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionReport;\n\npublic interface DiscussionReportEntityService extends IService<DiscussionReport> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/ReplyEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/5 22:08\n * @Description:\n */\npublic interface ReplyEntityService extends IService<Reply> {\n\n    public void updateReplyMsg(Integer sourceId, String sourceType, String content, Integer quoteId, String quoteType,\n                               String recipientId, String senderId);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/impl/CommentEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.CommentEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.ReplyEntityService;\nimport com.simplefanc.voj.backend.dao.msg.MsgRemindEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.mapper.CommentMapper;\nimport com.simplefanc.voj.backend.pojo.vo.CommentVO;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Comment;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\nimport com.simplefanc.voj.common.pojo.entity.msg.MsgRemind;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class CommentEntityServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentEntityService {\n\n    private final CommentMapper commentMapper;\n\n    private final ContestEntityService contestEntityService;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final ReplyEntityService replyEntityService;\n\n    private final MsgRemindEntityService msgRemindEntityService;\n\n    @Override\n    public IPage<CommentVO> getCommentList(int limit, int currentPage, Long cid, Integer did, Boolean isRoot,\n                                           String uid) {\n        // 新建分页\n        Page<CommentVO> page = new Page<>(currentPage, limit);\n\n        if (cid != null) {\n            Contest contest = contestEntityService.getById(cid);\n\n            boolean onlyMineAndAdmin = contest.getStatus().equals(ContestEnum.STATUS_RUNNING.getCode()) && !isRoot\n                    && !contest.getUid().equals(uid);\n            // 自己和比赛管理者评论可看\n            if (onlyMineAndAdmin) {\n                List<String> myAndAdminUidList = userInfoEntityService.getSuperAdminUidList();\n                myAndAdminUidList.add(uid);\n                myAndAdminUidList.add(contest.getUid());\n                return commentMapper.getCommentList(page, cid, did, true, myAndAdminUidList);\n            }\n\n        }\n        return commentMapper.getCommentList(page, cid, did, false, null);\n    }\n\n    @Override\n    public List<Reply> getAllReplyByCommentId(Long cid, String uid, Boolean isRoot, Integer commentId) {\n        QueryWrapper<Reply> replyQueryWrapper = new QueryWrapper<>();\n        replyQueryWrapper.eq(\"comment_id\", commentId);\n\n        if (cid != null) {\n            Contest contest = contestEntityService.getById(cid);\n            boolean onlyMineAndAdmin = contest.getStatus().equals(ContestEnum.STATUS_RUNNING.getCode()) && !isRoot\n                    && !contest.getUid().equals(uid);\n            // 自己和比赛管理者评论可看\n            if (onlyMineAndAdmin) {\n                List<String> myAndAdminUidList = userInfoEntityService.getSuperAdminUidList();\n                myAndAdminUidList.add(uid);\n                myAndAdminUidList.add(contest.getUid());\n                replyQueryWrapper.in(\"from_uid\", myAndAdminUidList);\n            }\n\n        }\n        replyQueryWrapper.orderByDesc(\"gmt_create\");\n        return replyEntityService.list(replyQueryWrapper);\n    }\n\n    @Async\n    @Override\n    public void updateCommentMsg(String recipientId, String senderId, String content, Integer discussionId) {\n\n        if (content.length() > 200) {\n            content = content.substring(0, 200) + \"...\";\n        }\n\n        MsgRemind msgRemind = new MsgRemind();\n        msgRemind.setAction(\"Discuss\").setRecipientId(recipientId).setSenderId(senderId).setSourceContent(content)\n                .setSourceId(discussionId).setSourceType(\"Discussion\").setUrl(\"/discussion-detail/\" + discussionId);\n        msgRemindEntityService.saveOrUpdate(msgRemind);\n    }\n\n    @Async\n    @Override\n    public void updateCommentLikeMsg(String recipientId, String senderId, Integer sourceId, String sourceType) {\n\n        MsgRemind msgRemind = new MsgRemind();\n        msgRemind.setAction(\"Like_Discuss\").setRecipientId(recipientId).setSenderId(senderId).setSourceId(sourceId)\n                .setSourceType(sourceType).setUrl(\"Discussion\".equals(sourceType) ? \"/discussion-detail/\" + sourceId\n                : \"/contest/\" + sourceId + \"/comment\");\n        msgRemindEntityService.saveOrUpdate(msgRemind);\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/impl/CommentLikeEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.discussion.CommentLikeEntityService;\nimport com.simplefanc.voj.backend.mapper.CommentLikeMapper;\nimport com.simplefanc.voj.common.pojo.entity.discussion.CommentLike;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/4 22:31\n * @Description:\n */\n@Service\npublic class CommentLikeEntityServiceImpl extends ServiceImpl<CommentLikeMapper, CommentLike>\n        implements CommentLikeEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/impl/DiscussionEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionEntityService;\nimport com.simplefanc.voj.backend.dao.msg.MsgRemindEntityService;\nimport com.simplefanc.voj.backend.mapper.DiscussionMapper;\nimport com.simplefanc.voj.backend.pojo.vo.DiscussionVO;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport com.simplefanc.voj.common.pojo.entity.msg.MsgRemind;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/4 22:31\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class DiscussionEntityServiceImpl extends ServiceImpl<DiscussionMapper, Discussion>\n        implements DiscussionEntityService {\n\n    private final DiscussionMapper discussionMapper;\n\n    private final MsgRemindEntityService msgRemindEntityService;\n\n    @Override\n    public DiscussionVO getDiscussion(Integer did, String uid) {\n        return discussionMapper.getDiscussion(did, uid);\n    }\n\n    @Override\n    @Async\n    public void updatePostLikeMsg(String recipientId, String senderId, Integer discussionId) {\n\n        MsgRemind msgRemind = new MsgRemind();\n        msgRemind.setAction(\"Like_Post\").setRecipientId(recipientId).setSenderId(senderId).setSourceId(discussionId)\n                .setSourceType(\"Discussion\").setUrl(\"/discussion-detail/\" + discussionId);\n        msgRemindEntityService.saveOrUpdate(msgRemind);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/impl/DiscussionLikeEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionLikeEntityService;\nimport com.simplefanc.voj.backend.mapper.DiscussionLikeMapper;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionLike;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/4 22:31\n * @Description:\n */\n@Service\npublic class DiscussionLikeEntityServiceImpl extends ServiceImpl<DiscussionLikeMapper, DiscussionLike>\n        implements DiscussionLikeEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/impl/DiscussionReportEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionReportEntityService;\nimport com.simplefanc.voj.backend.mapper.DiscussionReportMapper;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionReport;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/11 21:46\n * @Description:\n */\n@Service\npublic class DiscussionReportEntityServiceImpl extends ServiceImpl<DiscussionReportMapper, DiscussionReport>\n        implements DiscussionReportEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/discussion/impl/ReplyEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.discussion.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.discussion.ReplyEntityService;\nimport com.simplefanc.voj.backend.dao.msg.MsgRemindEntityService;\nimport com.simplefanc.voj.backend.mapper.ReplyMapper;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\nimport com.simplefanc.voj.common.pojo.entity.msg.MsgRemind;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/5 22:09\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class ReplyEntityServiceImpl extends ServiceImpl<ReplyMapper, Reply> implements ReplyEntityService {\n\n    private final MsgRemindEntityService msgRemindEntityService;\n\n    @Async\n    @Override\n    public void updateReplyMsg(Integer sourceId, String sourceType, String content, Integer quoteId, String quoteType,\n                               String recipientId, String senderId) {\n        if (content.length() > 200) {\n            content = content.substring(0, 200) + \"...\";\n        }\n\n        MsgRemind msgRemind = new MsgRemind();\n        msgRemind.setAction(\"Reply\").setSourceId(sourceId).setSourceType(sourceType).setSourceContent(content)\n                .setQuoteId(quoteId).setQuoteType(quoteType).setUrl(\"Discussion\".equals(sourceType)\n                ? \"/discussion-detail/\" + sourceId : \"/contest/\" + sourceId + \"/comment\")\n                .setRecipientId(recipientId).setSenderId(senderId);\n\n        msgRemindEntityService.saveOrUpdate(msgRemind);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/judge/JudgeCaseEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.judge;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface JudgeCaseEntityService extends IService<JudgeCase> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/judge/JudgeEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.judge;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.JudgeVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemCountVO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n\npublic interface JudgeEntityService extends IService<Judge> {\n\n    IPage<JudgeVO> getCommonJudgeList(Integer limit, Integer currentPage, String searchPid, Integer status,\n                                      String username, String uid, Boolean completeProblemId);\n\n    // TODO 参数过多\n    IPage<JudgeVO> getContestJudgeList(Integer limit, Integer currentPage, String displayId, Long cid, Integer status,\n                                       String username, String uid, Boolean beforeContestSubmit, String rule, Date startTime, Date sealRankTime,\n                                       String sealTimeUid, Boolean completeProblemId);\n\n    void failToUseRedisPublishJudge(Long submitId, Long pid, Boolean isContest);\n\n    ProblemCountVO getContestProblemCount(Long pid, Long cpid, Long cid, Date startTime, Date sealRankTime,\n                                          List<String> adminList);\n\n    ProblemCountVO getProblemCount(Long pid);\n\n    int getTodayJudgeNum();\n\n    List<ProblemCountVO> getProblemListCount(List<Long> pidList);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/judge/JudgeServerEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.judge;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeServer;\n\npublic interface JudgeServerEntityService extends IService<JudgeServer> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/judge/RemoteJudgeAccountEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.judge;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\n\npublic interface RemoteJudgeAccountEntityService extends IService<RemoteJudgeAccount> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/judge/impl/JudgeCaseEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.judge.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.judge.JudgeCaseEntityService;\nimport com.simplefanc.voj.backend.mapper.JudgeCaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\npublic class JudgeCaseEntityServiceImpl extends ServiceImpl<JudgeCaseMapper, JudgeCase>\n        implements JudgeCaseEntityService {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/judge/impl/JudgeEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.judge.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.contest.ContestRecordEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.mapper.JudgeMapper;\nimport com.simplefanc.voj.backend.pojo.vo.JudgeVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemCountVO;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class JudgeEntityServiceImpl extends ServiceImpl<JudgeMapper, Judge> implements JudgeEntityService {\n\n    private final JudgeMapper judgeMapper;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    @Override\n    public IPage<JudgeVO> getCommonJudgeList(Integer limit, Integer currentPage, String searchPid, Integer status,\n                                             String username, String uid, Boolean completeProblemId) {\n        // 新建分页\n        Page<JudgeVO> page = new Page<>(currentPage, limit);\n\n        return judgeMapper.getCommonJudgeList(page, searchPid, status, username, uid, completeProblemId);\n    }\n\n    // TODO 参数过多\n    @Override\n    public IPage<JudgeVO> getContestJudgeList(Integer limit, Integer currentPage, String displayId, Long cid,\n                                              Integer status, String username, String uid, Boolean beforeContestSubmit, String rule, Date startTime,\n                                              Date sealRankTime, String sealTimeUid, Boolean completeProblemId) {\n        // 新建分页\n        Page<JudgeVO> page = new Page<>(currentPage, limit);\n\n        return judgeMapper.getContestJudgeList(page, displayId, cid, status, username, uid, beforeContestSubmit, rule,\n                startTime, sealRankTime, sealTimeUid, completeProblemId);\n    }\n\n    @Override\n    public void failToUseRedisPublishJudge(Long submitId, Long pid, Boolean isContest) {\n        UpdateWrapper<Judge> judgeUpdateWrapper = new UpdateWrapper<>();\n        judgeUpdateWrapper.eq(\"submit_id\", submitId).set(\"error_message\",\n                \"The something has gone wrong with the data Backup server. Please report this to administrator.\")\n                .set(\"status\", JudgeStatus.STATUS_SYSTEM_ERROR.getStatus());\n        judgeMapper.update(null, judgeUpdateWrapper);\n        // 更新contest_record表\n        if (isContest) {\n            UpdateWrapper<ContestRecord> updateWrapper = new UpdateWrapper<>();\n            // submit_id一定只有一个\n            updateWrapper.eq(\"submit_id\", submitId).set(\"first_blood\", false).set(\"status\",\n                    ContestEnum.RECORD_NOT_AC_NOT_PENALTY.getCode());\n            contestRecordEntityService.update(updateWrapper);\n        }\n    }\n\n    @Override\n    public ProblemCountVO getContestProblemCount(Long pid, Long cpid, Long cid, Date startTime, Date sealRankTime,\n                                                 List<String> adminList) {\n        return judgeMapper.getContestProblemCount(pid, cpid, cid, startTime, sealRankTime, adminList);\n    }\n\n    @Override\n    public ProblemCountVO getProblemCount(Long pid) {\n        return judgeMapper.getProblemCount(pid);\n    }\n\n    @Override\n    public int getTodayJudgeNum() {\n        return judgeMapper.getTodayJudgeNum();\n    }\n\n    @Override\n    public List<ProblemCountVO> getProblemListCount(List<Long> pidList) {\n        return judgeMapper.getProblemListCount(pidList);\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/judge/impl/JudgeServerEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.judge.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.judge.JudgeServerEntityService;\nimport com.simplefanc.voj.backend.mapper.JudgeServerMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeServer;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/15 11:27\n * @Description:\n */\n@Service\npublic class JudgeServerEntityServiceImpl extends ServiceImpl<JudgeServerMapper, JudgeServer>\n        implements JudgeServerEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/judge/impl/RemoteJudgeAccountEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.judge.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.judge.RemoteJudgeAccountEntityService;\nimport com.simplefanc.voj.backend.mapper.RemoteJudgeAccountMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/18 17:46\n * @Description:\n */\n@Service\npublic class RemoteJudgeAccountEntityServiceImpl extends ServiceImpl<RemoteJudgeAccountMapper, RemoteJudgeAccount>\n        implements RemoteJudgeAccountEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/msg/AdminSysNoticeEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.msg;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.AdminSysNoticeVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.AdminSysNotice;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:33\n * @Description:\n */\npublic interface AdminSysNoticeEntityService extends IService<AdminSysNotice> {\n\n    IPage<AdminSysNoticeVO> getSysNotice(int limit, int currentPage, String type);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/msg/MsgRemindEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.msg;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.UserMsgVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserUnreadMsgCountVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.MsgRemind;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:32\n * @Description:\n */\npublic interface MsgRemindEntityService extends IService<MsgRemind> {\n\n    UserUnreadMsgCountVO getUserUnreadMsgCount(String uid);\n\n    IPage<UserMsgVO> getUserMsg(Page<UserMsgVO> page, String uid, String action);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/msg/UserSysNoticeEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.msg;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.SysMsgVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.UserSysNotice;\n\npublic interface UserSysNoticeEntityService extends IService<UserSysNotice> {\n\n    IPage<SysMsgVO> getSysNotice(int limit, int currentPage, String uid);\n\n    IPage<SysMsgVO> getMineNotice(int limit, int currentPage, String uid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/msg/impl/AdminSysNoticeEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.msg.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.msg.AdminSysNoticeEntityService;\nimport com.simplefanc.voj.backend.mapper.AdminSysNoticeMapper;\nimport com.simplefanc.voj.backend.pojo.vo.AdminSysNoticeVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.AdminSysNotice;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:34\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class AdminSysNoticeEntityServiceImpl extends ServiceImpl<AdminSysNoticeMapper, AdminSysNotice>\n        implements AdminSysNoticeEntityService {\n\n    private final AdminSysNoticeMapper adminSysNoticeMapper;\n\n    @Override\n    public IPage<AdminSysNoticeVO> getSysNotice(int limit, int currentPage, String type) {\n        Page<AdminSysNoticeVO> page = new Page<>(currentPage, limit);\n        return adminSysNoticeMapper.getAdminSysNotice(page, type);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/msg/impl/MsgRemindEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.msg.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.msg.MsgRemindEntityService;\nimport com.simplefanc.voj.backend.mapper.MsgRemindMapper;\nimport com.simplefanc.voj.backend.pojo.vo.UserMsgVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserUnreadMsgCountVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.MsgRemind;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:36\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class MsgRemindEntityServiceImpl extends ServiceImpl<MsgRemindMapper, MsgRemind>\n        implements MsgRemindEntityService {\n\n    private final MsgRemindMapper msgRemindMapper;\n\n    @Override\n    public UserUnreadMsgCountVO getUserUnreadMsgCount(String uid) {\n        return msgRemindMapper.getUserUnreadMsgCount(uid);\n    }\n\n    @Override\n    public IPage<UserMsgVO> getUserMsg(Page<UserMsgVO> page, String uid, String action) {\n        return msgRemindMapper.getUserMsg(page, uid, action);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/msg/impl/UserSysNoticeEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.msg.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.msg.UserSysNoticeEntityService;\nimport com.simplefanc.voj.backend.mapper.UserSysNoticeMapper;\nimport com.simplefanc.voj.backend.pojo.vo.SysMsgVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.UserSysNotice;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:35\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class UserSysNoticeEntityServiceImpl extends ServiceImpl<UserSysNoticeMapper, UserSysNotice>\n        implements UserSysNoticeEntityService {\n\n    private final UserSysNoticeMapper userSysNoticeMapper;\n\n    @Override\n    public IPage<SysMsgVO> getSysNotice(int limit, int currentPage, String uid) {\n        Page<SysMsgVO> page = new Page<>(currentPage, limit);\n        return userSysNoticeMapper.getSysOrMineNotice(page, uid, \"Sys\");\n    }\n\n    @Override\n    public IPage<SysMsgVO> getMineNotice(int limit, int currentPage, String uid) {\n        Page<SysMsgVO> page = new Page<>(currentPage, limit);\n        return userSysNoticeMapper.getSysOrMineNotice(page, uid, \"Mine\");\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/CategoryEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.problem;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.Category;\n\npublic interface CategoryEntityService extends IService<Category> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/CodeTemplateEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.problem;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.CodeTemplate;\n\npublic interface CodeTemplateEntityService extends IService<CodeTemplate> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/LanguageEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.problem;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.Language;\n\npublic interface LanguageEntityService extends IService<Language> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/ProblemCaseEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.problem;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/14 19:58\n * @Description:\n */\npublic interface ProblemCaseEntityService extends IService<ProblemCase> {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/ProblemEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.problem;\n\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ImportProblemVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n\npublic interface ProblemEntityService extends IService<Problem> {\n\n    Page<ProblemVO> getProblemList(int limit, int currentPage, String title, Integer difficulty,\n                                   List<Long> tagIds, String oj, boolean isAdmin);\n\n    boolean adminUpdateProblem(ProblemDTO problemDTO);\n\n    boolean adminAddProblem(ProblemDTO problemDTO);\n\n    ImportProblemVO buildExportProblem(Long pid, List<HashMap<String, Object>> problemCaseList,\n                                       HashMap<Long, String> languageMap, HashMap<Long, String> tagMap);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/ProblemLanguageEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.problem;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemLanguage;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/13 00:03\n * @Description:\n */\npublic interface ProblemLanguageEntityService extends IService<ProblemLanguage> {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/ProblemTagEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.problem;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemTag;\n\npublic interface ProblemTagEntityService extends IService<ProblemTag> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/TagClassificationEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.problem;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.TagClassification;\n\n/**\n * @Author chenfan\n * @Date 2022/8/3\n */\npublic interface TagClassificationEntityService extends IService<TagClassification> {\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/TagEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.problem;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface TagEntityService extends IService<Tag> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/impl/CategoryEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.problem.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.problem.CategoryEntityService;\nimport com.simplefanc.voj.backend.mapper.CategoryMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.Category;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/4 22:30\n * @Description:\n */\n@Service\npublic class CategoryEntityServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/impl/CodeTemplateEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.problem.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.problem.CodeTemplateEntityService;\nimport com.simplefanc.voj.backend.mapper.CodeTemplateMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.CodeTemplate;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/24 10:27\n * @Description:\n */\n@Service\npublic class CodeTemplateEntityServiceImpl extends ServiceImpl<CodeTemplateMapper, CodeTemplate>\n        implements CodeTemplateEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/impl/LanguageEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.problem.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.problem.LanguageEntityService;\nimport com.simplefanc.voj.backend.mapper.LanguageMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.Language;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/12 23:23\n * @Description:\n */\n@Service\npublic class LanguageEntityServiceImpl extends ServiceImpl<LanguageMapper, Language> implements LanguageEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/impl/ProblemCaseEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.problem.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.problem.ProblemCaseEntityService;\nimport com.simplefanc.voj.backend.mapper.ProblemCaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/14 19:59\n * @Description:\n */\n@Service\npublic class ProblemCaseEntityServiceImpl extends ServiceImpl<ProblemCaseMapper, ProblemCase>\n        implements ProblemCaseEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/impl/ProblemEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.problem.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileReader;\nimport cn.hutool.core.io.file.FileWriter;\nimport cn.hutool.core.util.CharsetUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.problem.*;\nimport com.simplefanc.voj.backend.mapper.ProblemMapper;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ImportProblemVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemCountVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.common.constants.Constant;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.JudgeMode;\nimport com.simplefanc.voj.common.constants.ProblemEnum;\nimport com.simplefanc.voj.common.pojo.entity.problem.*;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.DigestUtils;\n\nimport java.io.File;\nimport java.io.UnsupportedEncodingException;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class ProblemEntityServiceImpl extends ServiceImpl<ProblemMapper, Problem> implements ProblemEntityService {\n\n    private final ProblemMapper problemMapper;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final ProblemCaseEntityService problemCaseEntityService;\n\n    private final ProblemLanguageEntityService problemLanguageEntityService;\n\n    private final TagEntityService tagEntityService;\n\n    private final ProblemTagEntityService problemTagEntityService;\n\n    private final ApplicationContext applicationContext;\n\n    private final CodeTemplateEntityService codeTemplateEntityService;\n\n    private final FilePathProperties filePathProps;\n\n    // 去除每行末尾的空白符\n    public static String rtrim(String value) {\n        if (value == null) {\n            return null;\n        }\n        return value.replaceAll(\"[^\\\\S\\\\r\\\\n]+(?=\\\\n|\\\\r)|\\\\s+(?=$)\", \"\");\n    }\n\n    @Override\n    public Page<ProblemVO> getProblemList(int limit, int currentPage, String title, Integer difficulty,\n                                          List<Long> tagIds, String oj, boolean allProblemVisible) {\n\n        // 新建分页\n        Page<ProblemVO> page = new Page<>(currentPage, limit);\n        Integer tagListSize = null;\n        if (tagIds != null) {\n            tagIds = tagIds.stream().distinct().collect(Collectors.toList());\n            tagListSize = tagIds.size();\n        }\n\n        List<ProblemVO> problemList = problemMapper.getProblemList(page, title, difficulty, tagIds, tagListSize, oj, allProblemVisible);\n\n        if (problemList.size() > 0) {\n            List<Long> pidList = problemList.stream().map(ProblemVO::getPid).collect(Collectors.toList());\n            List<ProblemCountVO> problemListCount = judgeEntityService.getProblemListCount(pidList);\n            for (ProblemVO problemVO : problemList) {\n                for (ProblemCountVO problemCountVO : problemListCount) {\n                    if (problemVO.getPid().equals(problemCountVO.getPid())) {\n                        problemVO.setProblemCountVO(problemCountVO);\n                        break;\n                    }\n                }\n            }\n        }\n\n        return page.setRecords(problemList);\n    }\n\n    /**\n     * 处理tag表与problem_tag表的删除与更新\n     */\n    public boolean processTag(Long pid, ProblemDTO problemDTO, String ojName) {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"pid\", pid);\n        // 与前端上传的数据进行对比，添加或删除！\n        List<ProblemTag> oldProblemTags = (List<ProblemTag>) problemTagEntityService.listByMap(map);\n        Map<Long, Integer> mapOldPT = new HashMap<>();\n        // 登记一下原有的tag的id\n        oldProblemTags.forEach(problemTag -> {\n            mapOldPT.put(problemTag.getTid(), 0);\n        });\n\n        // 存储新的problem_tag表数据\n        List<ProblemTag> problemTagList = new LinkedList<>();\n        for (Tag tag : problemDTO.getTags()) {\n            // 没有主键表示为新添加的标签\n            if (tag.getId() == null) {\n                tag.setOj(ojName);\n                boolean addTagResult = tagEntityService.save(tag);\n                if (addTagResult) {\n                    problemTagList.add(new ProblemTag().setPid(pid).setTid(tag.getId()));\n                }\n                // 已存在tag 但是新添加的\n            } else if (mapOldPT.getOrDefault(tag.getId(), null) == null) {\n                problemTagList.add(new ProblemTag().setPid(pid).setTid(tag.getId()));\n            } else {\n                // 已有主键的需要记录一下，若原先在problem_tag有的，现在不见了，表明需要删除\n                // 更新记录，说明该tag未删除\n                mapOldPT.put(tag.getId(), 1);\n            }\n        }\n        // 放入需要删除的tagId列表\n        List<Long> needDeleteTids = new LinkedList<>();\n        for (Long key : mapOldPT.keySet()) {\n            // 记录表中没有更新原来的存在Tid，则表明该tag已不被该problem使用\n            if (mapOldPT.get(key) == 0) {\n                needDeleteTids.add(key);\n            }\n        }\n        boolean deleteTagsFromProblemResult = true;\n        if (needDeleteTids.size() > 0) {\n            QueryWrapper<ProblemTag> tagWrapper = new QueryWrapper<>();\n            tagWrapper.eq(\"pid\", pid).in(\"tid\", needDeleteTids);\n            // 执行批量删除操作\n            deleteTagsFromProblemResult = problemTagEntityService.remove(tagWrapper);\n        }\n        // 执行批量插入操作\n        boolean addTagsToProblemResult = true;\n        if (problemTagList.size() > 0) {\n            addTagsToProblemResult = problemTagEntityService.saveOrUpdateBatch(problemTagList);\n        }\n\n        return deleteTagsFromProblemResult && addTagsToProblemResult;\n    }\n\n\n    /**\n     * 处理problem_language表的更新与删除\n     */\n    public boolean processLanguage(Long pid, ProblemDTO problemDTO) {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"pid\", pid);\n        List<ProblemLanguage> oldProblemLanguages = (List<ProblemLanguage>) problemLanguageEntityService.listByMap(map);\n        Map<Long, Integer> mapOldPL = new HashMap<>();\n        // 登记一下原有的language的id\n        oldProblemLanguages.forEach(problemLanguage -> {\n            mapOldPL.put(problemLanguage.getLid(), 0);\n        });\n        // 根据上传来的language列表的每一个name字段查询对应的language表的id，更新problem_language\n        // 构建problem_language实体列表\n        List<ProblemLanguage> problemLanguageList = new LinkedList<>();\n        // 遍历插入\n        for (Language language : problemDTO.getLanguages()) {\n            // 如果记录中有，则表式该language原来已有选中。\n            if (mapOldPL.get(language.getId()) != null) {\n                // 记录一下，新数据也有该language\n                mapOldPL.put(language.getId(), 1);\n            } else {\n                // 没有记录，则表明为新添加的language\n                problemLanguageList.add(new ProblemLanguage().setLid(language.getId()).setPid(pid));\n            }\n        }\n        // 放入需要删除的languageId列表\n        List<Long> needDeleteLids = new LinkedList<>();\n        for (Long key : mapOldPL.keySet()) {\n            // 记录表中没有更新原来的存在Lid，则表明该language已不被该problem使用\n            if (mapOldPL.get(key) == 0) {\n                needDeleteLids.add(key);\n            }\n        }\n        boolean deleteLanguagesFromProblemResult = true;\n        if (needDeleteLids.size() > 0) {\n            QueryWrapper<ProblemLanguage> LangWrapper = new QueryWrapper<>();\n            LangWrapper.eq(\"pid\", pid).in(\"lid\", needDeleteLids);\n            // 执行批量删除操作\n            deleteLanguagesFromProblemResult = problemLanguageEntityService.remove(LangWrapper);\n        }\n        // 执行批量添加操作\n        boolean addLanguagesToProblemResult = true;\n        if (problemLanguageList.size() > 0) {\n            addLanguagesToProblemResult = problemLanguageEntityService.saveOrUpdateBatch(problemLanguageList);\n        }\n\n        return deleteLanguagesFromProblemResult && addLanguagesToProblemResult;\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public boolean adminUpdateProblem(ProblemDTO problemDTO) {\n        Problem problem = problemDTO.getProblem();\n        if (JudgeMode.DEFAULT.getMode().equals(problemDTO.getJudgeMode())) {\n            problem.setSpjLanguage(null).setSpjCode(null);\n        }\n\n        String ojName = Constant.LOCAL;\n        if (problem.getIsRemote()) {\n            String problemId = problem.getProblemId();\n            ojName = problemId.split(\"-\")[0];\n        }\n\n        long pid = checkUniquePid(problemDTO, problem);\n\n        if (problemMapper.updateById(problem) == 1 &&\n                processTag(pid, problemDTO, ojName) &&\n                processCodeTemplate(pid, problemDTO) &&\n                processLanguage(pid, problemDTO) &&\n                // 处理problem_case表的增加与删除\n                processProblemCase(pid, problemDTO, problem)) {\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * 处理problem_case表的增加与删除\n     */\n    public boolean processProblemCase(long pid, ProblemDTO problemDTO, Problem problem) {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"pid\", pid);\n        List<ProblemCase> oldProblemCases = (List<ProblemCase>) problemCaseEntityService.listByMap(map);\n        HashMap<Long, ProblemCase> oldProblemMap = new HashMap<>();\n        List<Long> needDeleteProblemCases = new LinkedList<>();\n        // 登记一下原有的case的id\n        oldProblemCases.forEach(problemCase -> {\n            needDeleteProblemCases.add(problemCase.getId());\n            oldProblemMap.put(problemCase.getId(), problemCase);\n        });\n        boolean checkProblemCase = true;\n        // 如果是自家的题目才有测试数据\n        if (!problem.getIsRemote() && problemDTO.getSamples().size() > 0) {\n            // 新增加的case列表\n            List<ProblemCase> newProblemCaseList = new LinkedList<>();\n            // 需要修改的case列表\n            List<ProblemCase> needUpdateProblemCaseList = new LinkedList<>();\n            // 遍历上传的case列表，如果还存在，则从需要删除的测试样例列表移除该id\n            for (ProblemCase problemCase : problemDTO.getSamples()) {\n                // 已存在的case\n                if (problemCase.getId() != null) {\n                    needDeleteProblemCases.remove(problemCase.getId());\n                    // 跟原先的数据做对比，如果变动 则加入需要修改的case列表\n                    ProblemCase oldProblemCase = oldProblemMap.get(problemCase.getId());\n                    if (!oldProblemCase.getInput().equals(problemCase.getInput())\n                            || !oldProblemCase.getOutput().equals(problemCase.getOutput())) {\n                        needUpdateProblemCaseList.add(problemCase);\n                    } else if (problem.getType().intValue() == ContestEnum.TYPE_OI.getCode()) {\n                        // 分数变动\n                        if (!Objects.equals(oldProblemCase.getScore(), problemCase.getScore())) {\n                            needUpdateProblemCaseList.add(problemCase);\n                        }\n                    }\n                } else {\n                    newProblemCaseList.add(problemCase.setPid(pid));\n                }\n            }\n            // 执行批量删除操作\n            boolean deleteCasesFromProblemResult = true;\n            if (needDeleteProblemCases.size() > 0) {\n                deleteCasesFromProblemResult = problemCaseEntityService.removeByIds(needDeleteProblemCases);\n            }\n            // 执行批量添加操作\n            boolean addCasesToProblemResult = true;\n            if (newProblemCaseList.size() > 0) {\n                addCasesToProblemResult = problemCaseEntityService.saveBatch(newProblemCaseList);\n            }\n            // 执行批量修改操作\n            boolean updateCasesToProblemResult = true;\n            if (needUpdateProblemCaseList.size() > 0) {\n                updateCasesToProblemResult = problemCaseEntityService.saveOrUpdateBatch(needUpdateProblemCaseList);\n            }\n            checkProblemCase = addCasesToProblemResult && deleteCasesFromProblemResult && updateCasesToProblemResult;\n\n\n            // 只要有新添加，修改，删除都需要更新版本号 同时更新测试数据\n            String caseVersion = String.valueOf(System.currentTimeMillis());\n            String testcaseDir = problemDTO.getUploadTestcaseDir();\n            if (needDeleteProblemCases.size() > 0 || newProblemCaseList.size() > 0\n                    || needUpdateProblemCaseList.size() > 0 || StrUtil.isNotEmpty(testcaseDir)) {\n                problem.setCaseVersion(caseVersion);\n                // 如果是选择上传测试文件的，则需要遍历对应文件夹，读取数据，写入数据库,先前的题目数据一并清空。\n                if (problemDTO.getIsUploadTestCase()) {\n                    // 获取代理bean对象执行异步方法===》根据测试文件初始info\n                    applicationContext.getBean(ProblemEntityServiceImpl.class).initUploadTestCase(\n                            problemDTO.getJudgeMode(), caseVersion, pid, testcaseDir, problemDTO.getSamples());\n                } else {\n                    applicationContext.getBean(ProblemEntityServiceImpl.class).initHandTestCase(\n                            problemDTO.getJudgeMode(), problem.getCaseVersion(), pid, problemDTO.getSamples());\n                }\n            } else if (problemDTO.getChangeModeCode() != null && problemDTO.getChangeModeCode()) {\n                // 变化成spj或interactive或者取消 同时更新测试数据\n                problem.setCaseVersion(caseVersion);\n                if (problemDTO.getIsUploadTestCase()) {\n                    // 获取代理bean对象执行异步方法===》根据测试文件初始info\n                    applicationContext.getBean(ProblemEntityServiceImpl.class).initUploadTestCase(\n                            problemDTO.getJudgeMode(), caseVersion, pid, null, problemDTO.getSamples());\n                } else {\n                    applicationContext.getBean(ProblemEntityServiceImpl.class).initHandTestCase(\n                            problemDTO.getJudgeMode(), problem.getCaseVersion(), pid, problemDTO.getSamples());\n                }\n            }\n        }\n        return checkProblemCase;\n    }\n\n\n    /**\n     * 处理code_template表\n     */\n    public boolean processCodeTemplate(Long pid, ProblemDTO problemDTO) {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"pid\", pid);\n        List<CodeTemplate> oldProblemTemplate = (List<CodeTemplate>) codeTemplateEntityService.listByMap(map);\n        Map<Integer, Integer> mapOldPCT = new HashMap<>();\n        // 登记一下原有的codeTemplate的id\n        oldProblemTemplate.forEach(codeTemplate -> {\n            mapOldPCT.put(codeTemplate.getId(), 0);\n        });\n        boolean deleteTemplate = true;\n        boolean saveOrUpdateCodeTemplate = true;\n        for (CodeTemplate codeTemplate : problemDTO.getCodeTemplates()) {\n            if (codeTemplate.getId() != null) {\n                mapOldPCT.put(codeTemplate.getId(), 1);\n            }\n        }\n        // 需要删除的模板\n        List<Integer> needDeleteCTs = new LinkedList<>();\n        for (Integer key : mapOldPCT.keySet()) {\n            if (mapOldPCT.get(key) == 0) {\n                needDeleteCTs.add(key);\n            }\n        }\n        if (needDeleteCTs.size() > 0) {\n            deleteTemplate = codeTemplateEntityService.removeByIds(needDeleteCTs);\n        }\n        if (problemDTO.getCodeTemplates().size() > 0) {\n            saveOrUpdateCodeTemplate = codeTemplateEntityService.saveOrUpdateBatch(problemDTO.getCodeTemplates());\n        }\n        return deleteTemplate && saveOrUpdateCodeTemplate;\n    }\n\n    /**\n     * problem_id唯一性检查\n     */\n    public long checkUniquePid(ProblemDTO problemDTO, Problem problem) {\n        String problemId = problem.getProblemId().toUpperCase();\n        QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();\n        problemQueryWrapper.eq(\"problem_id\", problemId);\n        Problem existedProblem = problemMapper.selectOne(problemQueryWrapper);\n\n        problem.setProblemId(problem.getProblemId().toUpperCase());\n        // 后面许多表的更新或删除需要用到题目id\n        long pid = problemDTO.getProblem().getId();\n\n        if (existedProblem != null && existedProblem.getId() != pid) {\n            throw new RuntimeException(\"The problem_id [\" + problemId + \"] already exists. Do not reuse it!\");\n        }\n        return pid;\n    }\n\n    // TODO 行数过多\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public boolean adminAddProblem(ProblemDTO problemDTO) {\n        Problem problem = problemDTO.getProblem();\n        \n        // problem_id唯一性检查\n        String problemId = problem.getProblemId().toUpperCase();\n        QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();\n        problemQueryWrapper.eq(\"problem_id\", problemId);\n        int existedProblem = problemMapper.selectCount(problemQueryWrapper);\n        if (existedProblem > 0) {\n            throw new RuntimeException(\"The problem_id [\" + problemId + \"] already exists. Do not reuse it!\");\n        }\n\n        problem.setProblemId(problemId);\n        problem.setCaseVersion(String.valueOf(System.currentTimeMillis()));\n        if (JudgeMode.DEFAULT.getMode().equals(problemDTO.getJudgeMode())) {\n            problem.setSpjLanguage(null).setSpjCode(null);\n        }\n\n        // 1. 插入到数据库\n        boolean addProblemResult = problemMapper.insert(problem) == 1;\n        long pid = problem.getId();\n\n        // 2. 为新的题目添加对应的language\n        List<ProblemLanguage> problemLanguageList = new LinkedList<>();\n        for (Language language : problemDTO.getLanguages()) {\n            problemLanguageList.add(new ProblemLanguage().setPid(pid).setLid(language.getId()));\n        }\n        boolean addLangToProblemResult = problemLanguageEntityService.saveOrUpdateBatch(problemLanguageList);\n\n        // 3. 为新的题目添加对应的codeTemplate\n        boolean addProblemCodeTemplate = true;\n        if (problemDTO.getCodeTemplates() != null && problemDTO.getCodeTemplates().size() > 0) {\n            for (CodeTemplate codeTemplate : problemDTO.getCodeTemplates()) {\n                codeTemplate.setPid(pid);\n            }\n            addProblemCodeTemplate = codeTemplateEntityService.saveOrUpdateBatch(problemDTO.getCodeTemplates());\n        }\n\n        // 4. 为新的题目添加对应的case\n        boolean addCasesToProblemResult = addCasesToProblem(problemDTO, problem, pid);\n\n        // 5. 为新的题目添加对应的tag，可能tag是原表已有，也可能是新的，所以需要判断。\n        List<ProblemTag> problemTagList = new LinkedList<>();\n        if (problemDTO.getTags() != null) {\n            for (Tag tag : problemDTO.getTags()) {\n                // id为空 表示为原tag表中不存在的 插入后可以获取到对应的tagId\n                if (tag.getId() == null) {\n                    tag.setOj(Constant.LOCAL);\n                    try {\n                        tagEntityService.save(tag);\n                    } catch (Exception ignored) {\n                        tag = tagEntityService\n                                .getOne(new QueryWrapper<Tag>().eq(\"name\", tag.getName()).eq(\"oj\", Constant.LOCAL), false);\n                    }\n                }\n                problemTagList.add(new ProblemTag().setTid(tag.getId()).setPid(pid));\n            }\n        }\n        boolean addTagsToProblemResult = true;\n        if (problemTagList.size() > 0) {\n            addTagsToProblemResult = problemTagEntityService.saveOrUpdateBatch(problemTagList);\n        }\n\n        if (addProblemResult && addCasesToProblemResult && addLangToProblemResult && addTagsToProblemResult\n                && addProblemCodeTemplate) {\n            return true;\n        }\n        return false;\n    }\n\n    private boolean addCasesToProblem(ProblemDTO problemDTO, Problem problem, long pid) {\n        boolean addCasesToProblemResult;\n        // 为新的题目添加对应的case\n        // 如果是选择上传测试文件的，则需要遍历对应文件夹，读取数据。\n        if (problemDTO.getIsUploadTestCase()) {\n            String testcaseDir = problemDTO.getUploadTestcaseDir();\n            // 如果是oi题目统计总分\n            List<ProblemCase> problemCases = problemDTO.getSamples();\n            if (problemCases.size() == 0) {\n                throw new RuntimeException(\"The test cases of problem must not be empty!\");\n            }\n            for (ProblemCase problemCase : problemCases) {\n                if (StrUtil.isEmpty(problemCase.getOutput())) {\n                    String filePreName = problemCase.getInput().split(\"\\\\.\")[0];\n                    problemCase.setOutput(filePreName + \".out\");\n                }\n                problemCase.setPid(pid);\n            }\n            addCasesToProblemResult = problemCaseEntityService.saveOrUpdateBatch(problemCases);\n            // 获取代理bean对象 执行异步方法 ===》根据测试文件初始info\n            applicationContext.getBean(ProblemEntityServiceImpl.class).initUploadTestCase(problemDTO.getJudgeMode(),\n                    problem.getCaseVersion(), pid, testcaseDir, problemDTO.getSamples());\n        } else {\n            // oi题目需要求取平均值，给每个测试点初始oi的score值，默认总分100分\n            if (problem.getType().intValue() == ContestEnum.TYPE_OI.getCode()) {\n                final int averScore = 100 / problemDTO.getSamples().size();\n                // 设置好新题目的pid及分数\n                problemDTO.getSamples().forEach(problemCase -> problemCase.setPid(pid).setScore(averScore));\n                addCasesToProblemResult = problemCaseEntityService.saveOrUpdateBatch(problemDTO.getSamples());\n            } else {\n                // 设置好新题目的pid\n                problemDTO.getSamples().forEach(problemCase -> problemCase.setPid(pid));\n                addCasesToProblemResult = problemCaseEntityService.saveOrUpdateBatch(problemDTO.getSamples());\n            }\n            initHandTestCase(problemDTO.getJudgeMode(), problem.getCaseVersion(), pid, problemDTO.getSamples());\n        }\n        return addCasesToProblemResult;\n    }\n\n    /**\n     * 初始化上传文件的测试数据，写成json文件\n     * @param mode\n     * @param version\n     * @param problemId\n     * @param tmpTestcaseDir\n     * @param problemCaseList\n     */\n    @Async\n    public void initUploadTestCase(String mode, String version, Long problemId, String tmpTestcaseDir,\n                                   List<ProblemCase> problemCaseList) {\n\n        String testCasesDir = filePathProps.getTestcaseBaseFolder() + File.separator + \"problem_\" + problemId;\n\n        // 将之前的临时文件夹里面的评测文件全部复制到指定文件夹(覆盖)\n        if (StrUtil.isNotEmpty(tmpTestcaseDir)) {\n            FileUtil.clean(testCasesDir);\n            FileUtil.copyFilesFromDir(new File(tmpTestcaseDir), new File(testCasesDir), true);\n        }\n\n        JSONObject result = new JSONObject();\n        result.set(\"mode\", mode);\n        result.set(\"version\", version);\n        result.set(\"testCasesSize\", problemCaseList.size());\n\n        JSONArray testCaseList = new JSONArray(problemCaseList.size());\n\n        for (ProblemCase problemCase : problemCaseList) {\n            JSONObject jsonObject = new JSONObject();\n            jsonObject.set(\"caseId\", problemCase.getId());\n            jsonObject.set(\"score\", problemCase.getScore());\n            jsonObject.set(\"inputName\", problemCase.getInput());\n            jsonObject.set(\"outputName\", problemCase.getOutput());\n\n            // 读取输入文件\n            FileReader inputFile = new FileReader(testCasesDir + File.separator + problemCase.getInput(),\n                    CharsetUtil.UTF_8);\n            String input = inputFile.readString().replaceAll(\"\\r\\n\", \"\\n\");\n\n            FileWriter inputFileWriter = new FileWriter(testCasesDir + File.separator + problemCase.getInput(),\n                    CharsetUtil.UTF_8);\n            inputFileWriter.write(input);\n\n            // 读取输出文件\n            FileReader outputFile = new FileReader(testCasesDir + File.separator + problemCase.getOutput(),\n                    CharsetUtil.UTF_8);\n            String output = outputFile.readString().replaceAll(\"\\r\\n\", \"\\n\");\n\n            FileWriter outFileWriter = new FileWriter(testCasesDir + File.separator + problemCase.getOutput(),\n                    CharsetUtil.UTF_8);\n            outFileWriter.write(output);\n\n            // spj和interactive是根据特判程序输出判断结果，所以无需初始化测试数据\n            if (JudgeMode.DEFAULT.getMode().equals(mode)) {\n                // 原数据MD5\n                jsonObject.set(\"outputMd5\", DigestUtils.md5DigestAsHex(output.getBytes()));\n                // 原数据大小\n                jsonObject.set(\"outputSize\", output.getBytes().length);\n                // 去掉全部空格的MD5，用来判断pe\n                jsonObject.set(\"allStrippedOutputMd5\",\n                        DigestUtils.md5DigestAsHex(output.replaceAll(\"\\\\s+\", \"\").getBytes()));\n                // 默认去掉文末空格的MD5\n                jsonObject.set(\"EOFStrippedOutputMd5\", DigestUtils.md5DigestAsHex(rtrim(output).getBytes()));\n            }\n\n            testCaseList.add(jsonObject);\n        }\n\n        result.set(\"testCases\", testCaseList);\n\n        FileWriter infoFile = new FileWriter(testCasesDir + \"/info\", CharsetUtil.UTF_8);\n        // 写入记录文件\n        infoFile.write(JSONUtil.toJsonStr(result));\n        // 删除临时上传文件夹\n        FileUtil.del(tmpTestcaseDir);\n    }\n\n    /**\n     * 初始化手动输入上传的测试数据，写成json文件\n     * @param mode\n     * @param version\n     * @param problemId\n     * @param problemCaseList\n     */\n    @Async\n    public void initHandTestCase(String mode, String version, Long problemId, List<ProblemCase> problemCaseList) {\n\n        JSONObject result = new JSONObject();\n        result.set(\"mode\", mode);\n        result.set(\"version\", version);\n        result.set(\"testCasesSize\", problemCaseList.size());\n\n        JSONArray testCaseList = new JSONArray(problemCaseList.size());\n\n        String testCasesDir = filePathProps.getTestcaseBaseFolder() + File.separator + \"problem_\" + problemId;\n        FileUtil.del(testCasesDir);\n        for (int index = 0; index < problemCaseList.size(); index++) {\n            JSONObject jsonObject = new JSONObject();\n            String inputName = (index + 1) + \".in\";\n            jsonObject.set(\"caseId\", problemCaseList.get(index).getId());\n            jsonObject.set(\"score\", problemCaseList.get(index).getScore());\n            jsonObject.set(\"inputName\", inputName);\n            // 生成对应文件\n            FileWriter infileWriter = new FileWriter(testCasesDir + \"/\" + inputName, CharsetUtil.UTF_8);\n            // 将该测试数据的输入写入到文件\n            String inputData = problemCaseList.get(index).getInput().replaceAll(\"\\r\\n\", \"\\n\");\n            infileWriter.write(inputData);\n\n            String outputName = (index + 1) + \".out\";\n            jsonObject.set(\"outputName\", outputName);\n            // 生成对应文件\n            String outputData = problemCaseList.get(index).getOutput().replaceAll(\"\\r\\n\", \"\\n\");\n            FileWriter outFile = new FileWriter(testCasesDir + \"/\" + outputName, CharsetUtil.UTF_8);\n            outFile.write(outputData);\n\n            // spj和interactive是根据特判程序输出判断结果，所以无需初始化测试数据\n            if (JudgeMode.DEFAULT.getMode().equals(mode)) {\n                // 原数据MD5\n                jsonObject.set(\"outputMd5\", DigestUtils.md5DigestAsHex(outputData.getBytes()));\n                // 原数据大小\n                try {\n                    jsonObject.set(\"outputSize\", outputData.getBytes(CharsetUtil.UTF_8).length);\n                } catch (UnsupportedEncodingException e) {\n                    e.printStackTrace();\n                }\n                // 去掉全部空格的MD5，用来判断pe\n                jsonObject.set(\"allStrippedOutputMd5\",\n                        DigestUtils.md5DigestAsHex(outputData.replaceAll(\"\\\\s+\", \"\").getBytes()));\n                // 默认去掉文末空格的MD5\n                jsonObject.set(\"EOFStrippedOutputMd5\", DigestUtils.md5DigestAsHex(rtrim(outputData).getBytes()));\n            }\n\n            testCaseList.add(jsonObject);\n        }\n\n        result.set(\"testCases\", testCaseList);\n\n        FileWriter infoFile = new FileWriter(testCasesDir + \"/info\", CharsetUtil.UTF_8);\n        // 写入记录文件\n        infoFile.write(JSONUtil.toJsonStr(result));\n    }\n\n    @Override\n    // 如果是有提交记录的\n    @SuppressWarnings(\"All\")\n    public ImportProblemVO buildExportProblem(Long pid, List<HashMap<String, Object>> problemCaseList,\n                                              HashMap<Long, String> languageMap, HashMap<Long, String> tagMap) {\n        // TODO 参数\n        // 导出相当于导入\n        ImportProblemVO importProblemVO = new ImportProblemVO();\n        Problem problem = problemMapper.selectById(pid);\n        problem.setCaseVersion(null).setGmtCreate(null).setId(null).setAuth(ProblemEnum.AUTH_PUBLIC.getCode()).setIsUploadCase(true).setAuthor(null)\n                .setGmtModified(null);\n        HashMap<String, Object> problemMap = new HashMap<>();\n        BeanUtil.beanToMap(problem, problemMap, false, true);\n        importProblemVO.setProblem(problemMap);\n        QueryWrapper<CodeTemplate> codeTemplateQueryWrapper = new QueryWrapper<>();\n        codeTemplateQueryWrapper.eq(\"pid\", pid).eq(\"status\", true);\n        List<CodeTemplate> codeTemplates = codeTemplateEntityService.list(codeTemplateQueryWrapper);\n        List<HashMap<String, String>> codeTemplateList = new LinkedList<>();\n        for (CodeTemplate codeTemplate : codeTemplates) {\n            HashMap<String, String> tmp = new HashMap<>();\n            tmp.put(\"language\", languageMap.get(codeTemplate.getLid()));\n            tmp.put(\"code\", codeTemplate.getCode());\n            codeTemplateList.add(tmp);\n        }\n        importProblemVO.setCodeTemplates(codeTemplateList);\n        importProblemVO.setJudgeMode(problem.getJudgeMode());\n        importProblemVO.setSamples(problemCaseList);\n\n        if (StrUtil.isNotEmpty(problem.getUserExtraFile())) {\n            HashMap<String, String> userExtraFileMap = (HashMap<String, String>) JSONUtil\n                    .toBean(problem.getUserExtraFile(), Map.class);\n            importProblemVO.setUserExtraFile(userExtraFileMap);\n        }\n\n        if (StrUtil.isNotEmpty(problem.getJudgeExtraFile())) {\n            HashMap<String, String> judgeExtraFileMap = (HashMap<String, String>) JSONUtil\n                    .toBean(problem.getJudgeExtraFile(), Map.class);\n            importProblemVO.setUserExtraFile(judgeExtraFileMap);\n        }\n\n        QueryWrapper<ProblemTag> problemTagQueryWrapper = new QueryWrapper<>();\n        problemTagQueryWrapper.eq(\"pid\", pid);\n        List<ProblemTag> problemTags = problemTagEntityService.list(problemTagQueryWrapper);\n        importProblemVO.setTags(\n                problemTags.stream().map(problemTag -> tagMap.get(problemTag.getTid())).collect(Collectors.toList()));\n\n        QueryWrapper<ProblemLanguage> problemLanguageQueryWrapper = new QueryWrapper<>();\n        problemLanguageQueryWrapper.eq(\"pid\", pid);\n        List<ProblemLanguage> problemLanguages = problemLanguageEntityService.list(problemLanguageQueryWrapper);\n        importProblemVO.setLanguages(problemLanguages.stream()\n                .map(problemLanguage -> languageMap.get(problemLanguage.getLid())).collect(Collectors.toList()));\n\n        return importProblemVO;\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/impl/ProblemLanguageEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.problem.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.problem.ProblemLanguageEntityService;\nimport com.simplefanc.voj.backend.mapper.ProblemLanguageMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemLanguage;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/13 00:04\n * @Description:\n */\n@Service\npublic class ProblemLanguageEntityServiceImpl extends ServiceImpl<ProblemLanguageMapper, ProblemLanguage>\n        implements ProblemLanguageEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/impl/ProblemTagEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.problem.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.problem.ProblemTagEntityService;\nimport com.simplefanc.voj.backend.mapper.ProblemTagMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemTag;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/13 23:22\n * @Description:\n */\n@Service\npublic class ProblemTagEntityServiceImpl extends ServiceImpl<ProblemTagMapper, ProblemTag>\n        implements ProblemTagEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/impl/TagClassificationEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.problem.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.problem.TagClassificationEntityService;\nimport com.simplefanc.voj.backend.mapper.TagClassificationMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.TagClassification;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author chenfan\n * @Date 2022/8/3\n */\n@Service\npublic class TagClassificationEntityServiceImpl extends ServiceImpl<TagClassificationMapper, TagClassification> implements TagClassificationEntityService {\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/problem/impl/TagEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.problem.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.problem.TagEntityService;\nimport com.simplefanc.voj.backend.mapper.TagMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\npublic class TagEntityServiceImpl extends ServiceImpl<TagMapper, Tag> implements TagEntityService {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/MappingTrainingCategoryEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.training;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.training.MappingTrainingCategory;\n\npublic interface MappingTrainingCategoryEntityService extends IService<MappingTrainingCategory> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/TrainingCategoryEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.training;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\n\npublic interface TrainingCategoryEntityService extends IService<TrainingCategory> {\n\n    TrainingCategory getTrainingCategoryByTrainingId(Long tid);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/TrainingEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.training;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingVO;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\n\npublic interface TrainingEntityService extends IService<Training> {\n\n    IPage<TrainingVO> getTrainingList(int limit, int currentPage, Long categoryId, String auth, String keyword);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/TrainingProblemEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.training;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingProblem;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/20 12:24\n * @Description:\n */\npublic interface TrainingProblemEntityService extends IService<TrainingProblem> {\n\n    List<Long> getTrainingProblemIdList(Long tid);\n\n    List<ProblemVO> getTrainingProblemList(Long tid);\n\n    Integer getUserTrainingACProblemCount(String uid, List<Long> pidList);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/TrainingRecordEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.training;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingRecordVO;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRecord;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/21 23:38\n * @Description:\n */\npublic interface TrainingRecordEntityService extends IService<TrainingRecord> {\n\n    List<TrainingRecordVO> getTrainingRecord(Long tid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/TrainingRegisterEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.training;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRegister;\n\nimport java.util.List;\n\npublic interface TrainingRegisterEntityService extends IService<TrainingRegister> {\n\n    List<String> getAlreadyRegisterUidList(Long tid);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/impl/MappingTrainingCategoryEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.training.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.training.MappingTrainingCategoryEntityService;\nimport com.simplefanc.voj.backend.mapper.MappingTrainingCategoryMapper;\nimport com.simplefanc.voj.common.pojo.entity.training.MappingTrainingCategory;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 19:53\n * @Description:\n */\n@Service\npublic class MappingTrainingCategoryEntityServiceImpl\n        extends ServiceImpl<MappingTrainingCategoryMapper, MappingTrainingCategory>\n        implements MappingTrainingCategoryEntityService {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/impl/TrainingCategoryEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.training.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.training.TrainingCategoryEntityService;\nimport com.simplefanc.voj.backend.mapper.TrainingCategoryMapper;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/20 12:15\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class TrainingCategoryEntityServiceImpl extends ServiceImpl<TrainingCategoryMapper, TrainingCategory>\n        implements TrainingCategoryEntityService {\n\n    private final TrainingCategoryMapper trainingCategoryMapper;\n\n    @Override\n    public TrainingCategory getTrainingCategoryByTrainingId(Long tid) {\n        return trainingCategoryMapper.getTrainingCategoryByTrainingId(tid);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/impl/TrainingEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.training.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.training.TrainingEntityService;\nimport com.simplefanc.voj.backend.mapper.TrainingMapper;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingVO;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/19 22:01\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class TrainingEntityServiceImpl extends ServiceImpl<TrainingMapper, Training> implements TrainingEntityService {\n\n    private final TrainingMapper trainingMapper;\n\n    @Override\n    public IPage<TrainingVO> getTrainingList(int limit, int currentPage, Long categoryId, String auth, String keyword) {\n        List<TrainingVO> trainingList = trainingMapper.getTrainingList(categoryId, auth, keyword);\n        Page<TrainingVO> page = new Page<>(currentPage, limit);\n        int count = trainingList.size();\n        List<TrainingVO> pageList = new ArrayList<>();\n        // 计算当前页第一条数据的下标\n        int currId = currentPage > 1 ? (currentPage - 1) * limit : 0;\n        for (int i = 0; i < limit && i < count - currId; i++) {\n            pageList.add(trainingList.get(currId + i));\n        }\n        page.setSize(limit);\n        page.setCurrent(currentPage);\n        page.setTotal(count);\n        page.setRecords(pageList);\n        return page;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/impl/TrainingProblemEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.training.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingProblemEntityService;\nimport com.simplefanc.voj.backend.mapper.TrainingProblemMapper;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingProblem;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/20 12:25\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class TrainingProblemEntityServiceImpl extends ServiceImpl<TrainingProblemMapper, TrainingProblem>\n        implements TrainingProblemEntityService {\n\n    private final TrainingProblemMapper trainingProblemMapper;\n\n    private final JudgeEntityService judgeEntityService;\n\n    static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {\n        Map<Object, Boolean> seen = new ConcurrentHashMap<>();\n        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;\n    }\n\n    @Override\n    public List<Long> getTrainingProblemIdList(Long tid) {\n        return trainingProblemMapper.getTrainingProblemCount(tid);\n    }\n\n    @Override\n    public List<ProblemVO> getTrainingProblemList(Long tid) {\n        List<ProblemVO> trainingProblemList = trainingProblemMapper.getTrainingProblemList(tid);\n        return trainingProblemList.stream().filter(distinctByKey(ProblemVO::getPid)).collect(Collectors.toList());\n    }\n\n    @Override\n    public Integer getUserTrainingACProblemCount(String uid, List<Long> pidList) {\n        if (CollectionUtils.isEmpty(pidList)) {\n            return 0;\n        }\n        QueryWrapper<Judge> judgeQueryWrapper = new QueryWrapper<>();\n        judgeQueryWrapper.select(\"DISTINCT pid\").in(\"pid\", pidList).eq(\"cid\", 0).eq(\"uid\", uid).eq(\"status\", 0);\n        return judgeEntityService.count(judgeQueryWrapper);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/impl/TrainingRecordEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.training.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.training.TrainingRecordEntityService;\nimport com.simplefanc.voj.backend.mapper.TrainingRecordMapper;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingRecordVO;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRecord;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/21 23:39\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class TrainingRecordEntityServiceImpl extends ServiceImpl<TrainingRecordMapper, TrainingRecord>\n        implements TrainingRecordEntityService {\n\n    private final TrainingRecordMapper trainingRecordMapper;\n\n    @Override\n    public List<TrainingRecordVO> getTrainingRecord(Long tid) {\n        return trainingRecordMapper.getTrainingRecord(tid);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/training/impl/TrainingRegisterEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.training.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.training.TrainingRegisterEntityService;\nimport com.simplefanc.voj.backend.mapper.TrainingRegisterMapper;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRegister;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/20 11:30\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class TrainingRegisterEntityServiceImpl extends ServiceImpl<TrainingRegisterMapper, TrainingRegister>\n        implements TrainingRegisterEntityService {\n\n    private final TrainingRegisterMapper trainingRegisterMapper;\n\n    @Override\n    public List<String> getAlreadyRegisterUidList(Long tid) {\n        QueryWrapper<TrainingRegister> trainingRegisterQueryWrapper = new QueryWrapper<>();\n        trainingRegisterQueryWrapper.eq(\"tid\", tid);\n        return trainingRegisterMapper.selectList(trainingRegisterQueryWrapper).stream().map(TrainingRegister::getUid)\n                .collect(Collectors.toList());\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/AuthEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.user;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.user.Auth;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface AuthEntityService extends IService<Auth> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/RoleAuthEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.user;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.user.RoleAuth;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface RoleAuthEntityService extends IService<RoleAuth> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/RoleEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.user;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.user.Role;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface RoleEntityService extends IService<Role> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/SessionEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.user;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.user.Session;\n\npublic interface SessionEntityService extends IService<Session> {\n\n    void checkRemoteLogin(String uid);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/UserAcproblemEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.user;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface UserAcproblemEntityService extends IService<UserAcproblem> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/UserInfoEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.user;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterDTO;\nimport com.simplefanc.voj.common.pojo.entity.user.UserInfo;\n\nimport java.util.List;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface UserInfoEntityService extends IService<UserInfo> {\n\n    Boolean addUser(RegisterDTO registerDTO);\n\n    List<String> getSuperAdminUidList();\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/UserRoleEntityService.java",
    "content": "package com.simplefanc.voj.backend.dao.user;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.common.pojo.entity.user.UserRole;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface UserRoleEntityService extends IService<UserRole> {\n\n    UserRolesVO getUserRoles(String uid, String username);\n\n    IPage<UserRolesVO> getUserList(int limit, int currentPage, String keyword, Long role, Integer status);\n\n    void deleteCache(String uid, boolean isRemoveSession);\n\n    String getAuthChangeContent(int oldType, int newType);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/impl/AuthEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.user.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.user.AuthEntityService;\nimport com.simplefanc.voj.backend.mapper.AuthMapper;\nimport com.simplefanc.voj.common.pojo.entity.user.Auth;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\npublic class AuthEntityServiceImpl extends ServiceImpl<AuthMapper, Auth> implements AuthEntityService {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/impl/RoleAuthEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.user.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.user.RoleAuthEntityService;\nimport com.simplefanc.voj.backend.mapper.RoleAuthMapper;\nimport com.simplefanc.voj.common.pojo.entity.user.RoleAuth;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\npublic class RoleAuthEntityServiceImpl extends ServiceImpl<RoleAuthMapper, RoleAuth> implements RoleAuthEntityService {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/impl/RoleEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.user.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.user.RoleEntityService;\nimport com.simplefanc.voj.backend.mapper.RoleMapper;\nimport com.simplefanc.voj.common.pojo.entity.user.Role;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\npublic class RoleEntityServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleEntityService {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/impl/SessionEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.user.impl;\n\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.http.HttpUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.msg.AdminSysNoticeEntityService;\nimport com.simplefanc.voj.backend.dao.msg.UserSysNoticeEntityService;\nimport com.simplefanc.voj.backend.dao.user.SessionEntityService;\nimport com.simplefanc.voj.backend.mapper.SessionMapper;\nimport com.simplefanc.voj.common.pojo.entity.msg.AdminSysNotice;\nimport com.simplefanc.voj.common.pojo.entity.msg.UserSysNotice;\nimport com.simplefanc.voj.common.pojo.entity.user.Session;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/3 22:46\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class SessionEntityServiceImpl extends ServiceImpl<SessionMapper, Session> implements SessionEntityService {\n\n    private final SessionMapper sessionMapper;\n\n    private final AdminSysNoticeEntityService adminSysNoticeEntityService;\n\n    private final UserSysNoticeEntityService userSysNoticeEntityService;\n\n    @Override\n    @Async\n    public void checkRemoteLogin(String uid) {\n        QueryWrapper<Session> sessionQueryWrapper = new QueryWrapper<>();\n        sessionQueryWrapper.eq(\"uid\", uid).orderByDesc(\"gmt_create\").last(\"limit 2\");\n        List<Session> sessionList = sessionMapper.selectList(sessionQueryWrapper);\n        if (sessionList.size() < 2) {\n            return;\n        }\n        Session nowSession = sessionList.get(0);\n        Session lastSession = sessionList.get(1);\n        // 如果两次登录的ip不相同，需要发通知给用户\n        if (!nowSession.getIp().equals(lastSession.getIp())) {\n            String remoteLoginContent = getRemoteLoginContent(lastSession.getIp(), nowSession.getIp(),\n                    nowSession.getGmtCreate());\n            if (remoteLoginContent == null) {\n                return;\n            }\n            AdminSysNotice adminSysNotice = new AdminSysNotice();\n            adminSysNotice.setType(\"Single\").setContent(remoteLoginContent)\n                    .setTitle(\"账号异地登录通知(Account Remote Login Notice)\").setAdminId(\"1\").setState(false)\n                    .setRecipientId(uid);\n            boolean isSaveOk = adminSysNoticeEntityService.save(adminSysNotice);\n            if (isSaveOk) {\n                UserSysNotice userSysNotice = new UserSysNotice();\n                userSysNotice.setType(\"Sys\").setSysNoticeId(adminSysNotice.getId()).setRecipientId(uid).setState(false);\n                boolean isOk = userSysNoticeEntityService.save(userSysNotice);\n                if (isOk) {\n                    adminSysNotice.setState(true);\n                    adminSysNoticeEntityService.saveOrUpdate(adminSysNotice);\n                }\n            }\n        }\n    }\n\n    private String getRemoteLoginContent(String oldIp, String newIp, Date loginDate) {\n        String dateStr = DateUtil.format(loginDate, \"yyyy-MM-dd HH:mm:ss\");\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"亲爱的用户，您好！您的账号于\").append(dateStr);\n        String addr = null;\n        try {\n            String newRes = HttpUtil.get(\"https://whois.pconline.com.cn/ipJson.jsp?ip=\" + newIp + \"&json=true\");\n            JSONObject newResJson = JSONUtil.parseObj(newRes);\n            addr = newResJson.getStr(\"addr\");\n\n            String newCityCode = newResJson.getStr(\"cityCode\");\n\n            String oldRes = HttpUtil.get(\"https://whois.pconline.com.cn/ipJson.jsp?ip=\" + oldIp + \"&json=true\");\n            JSONObject oldResJson = JSONUtil.parseObj(oldRes);\n\n            String oldCityCode = oldResJson.getStr(\"cityCode\");\n\n            if (newCityCode == null || oldCityCode == null || newCityCode.equals(oldCityCode)) {\n                return null;\n            }\n\n        } catch (Exception ignored) {\n            return null;\n        }\n        if (StrUtil.isNotEmpty(addr)) {\n            sb.append(\"在【\").append(addr).append(\"】\");\n        }\n        sb.append(\"登录，登录IP为：【\").append(newIp).append(\"】，若非本人操作，请立即修改密码。\").append(\"\\n\\n\")\n                .append(\"Hello! Dear user, Your account was logged in in\");\n\n        if (StrUtil.isNotEmpty(addr)) {\n            sb.append(\" 【\").append(addr).append(\"】 on \").append(dateStr)\n                    .append(\". If you do not operate by yourself, please change your password immediately.\");\n        }\n\n        return sb.toString();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/impl/UserAcproblemEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.user.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.user.UserAcproblemEntityService;\nimport com.simplefanc.voj.backend.mapper.UserAcproblemMapper;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\npublic class UserAcproblemEntityServiceImpl extends ServiceImpl<UserAcproblemMapper, UserAcproblem>\n        implements UserAcproblemEntityService {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/impl/UserInfoEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.user.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.common.constants.RoleEnum;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.mapper.UserInfoMapper;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterDTO;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.user.UserInfo;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class UserInfoEntityServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoEntityService {\n\n    private final UserInfoMapper userInfoMapper;\n\n    private final RedisUtil redisUtil;\n\n    @Override\n    public Boolean addUser(RegisterDTO registerDTO) {\n        return userInfoMapper.addUser(registerDTO) == 1;\n    }\n\n    @Override\n    @Cacheable(value = RedisConstant.SUPER_ADMIN_UID_LIST_CACHE)\n    public List<String> getSuperAdminUidList() {\n//        List<String> superAdminUidList = (List<String>) redisUtil.get(AccountConstant.SUPER_ADMIN_UID_LIST_CACHE);\n//        if (superAdminUidList == null) {\n//            superAdminUidList = userInfoMapper.getSuperAdminUidList(RoleEnum.ROOT.getId());\n//            redisUtil.set(AccountConstant.SUPER_ADMIN_UID_LIST_CACHE, superAdminUidList, 12 * 3600);\n//        }\n//        return superAdminUidList;\n        return userInfoMapper.getSuperAdminUidList(RoleEnum.ROOT.getId());\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/dao/user/impl/UserRoleEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.dao.user.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.backend.dao.user.UserRoleEntityService;\nimport com.simplefanc.voj.backend.mapper.UserRoleMapper;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.shiro.AccountProfile;\nimport com.simplefanc.voj.common.pojo.entity.user.UserRole;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.SecurityUtils;\nimport org.apache.shiro.authc.Authenticator;\nimport org.apache.shiro.authc.LogoutAware;\nimport org.apache.shiro.session.Session;\nimport org.apache.shiro.subject.SimplePrincipalCollection;\nimport org.apache.shiro.subject.support.DefaultSubjectContext;\nimport org.apache.shiro.web.mgt.DefaultWebSecurityManager;\nimport org.crazycake.shiro.RedisSessionDAO;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class UserRoleEntityServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> implements UserRoleEntityService {\n\n    private final static List<String> CHINESE_ROLE = Arrays.asList(\"超级管理员\", \"普通管理员\", \"普通用户(默认)\", \"普通用户(禁止提交)\",\n            \"普通用户(禁止发讨论)\", \"普通用户(禁言)\", \"普通用户(禁止提交&禁止发讨论)\", \"用户(禁止提交&禁言)\", \"题目管理员\");\n\n    private final static List<String> ENGLISH_ROLE = Arrays.asList(\"Super Administrator\", \"General Administrator\",\n            \"Normal User(Default)\", \"Normal User(No Submission)\", \"Normal User(No Discussion)\",\n            \"Normal User(Forbidden Words)\", \"Normal User(No Submission & No Discussion)\",\n            \"Normal User(No Submission & Forbidden Words)\", \"Problem Administrator\");\n\n    private final UserRoleMapper userRoleMapper;\n\n    private final RedisSessionDAO redisSessionDAO;\n\n    @Override\n    public UserRolesVO getUserRoles(String uid, String username) {\n        return userRoleMapper.getUserRoles(uid, username);\n    }\n\n    @Override\n    public IPage<UserRolesVO> getUserList(int limit, int currentPage, String keyword, Long roleId, Integer status) {\n        // 新建分页\n        Page<UserRolesVO> page = new Page<>(currentPage, limit);\n//        if (onlyAdmin) {\n//            return userRoleMapper.getAdminUserList(page, limit, currentPage, keyword,\n//                    Arrays.asList(RoleEnum.ROOT.getId(), RoleEnum.PROBLEM_ADMIN.getId(), RoleEnum.ADMIN.getId()));\n//        } else {\n            return userRoleMapper.getUserList(page, limit, currentPage, keyword, roleId, status);\n//        }\n    }\n\n    /**\n     * @param uid             当前需要操作的用户id\n     * @param isRemoveSession 如果为true则会强行删除该用户session，必须重新登陆，false的话 在访问受限接口时会重新授权\n     * @MethodName deleteCache\n     * @Description\n     * @Return\n     * @Since 2021/6/12\n     */\n    @Override\n    public void deleteCache(String uid, boolean isRemoveSession) {\n        // 从缓存中获取Session\n        Collection<Session> sessions = redisSessionDAO.getActiveSessions();\n        for (Session sessionInfo : sessions) {\n            // 遍历Session,找到该用户名称对应的Session\n            Object attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);\n            if (attribute == null) {\n                continue;\n            }\n            AccountProfile accountProfile = (AccountProfile) ((SimplePrincipalCollection) attribute)\n                    .getPrimaryPrincipal();\n            if (accountProfile == null) {\n                continue;\n            }\n            // 如果该session是指定的uid用户的\n            if (Objects.equals(accountProfile.getUid(), uid)) {\n                deleteSession(isRemoveSession, sessionInfo, attribute);\n            }\n        }\n\n    }\n\n    private void deleteSession(boolean isRemoveSession, Session session, Object attribute) {\n        // 删除session 会强制退出！主要是在禁用用户或角色时，强制用户退出的\n        if (isRemoveSession) {\n            redisSessionDAO.delete(session);\n        }\n\n        // 删除Cache，在访问受限接口时会重新授权\n        DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();\n        Authenticator authc = securityManager.getAuthenticator();\n        ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);\n    }\n\n    @Override\n    public String getAuthChangeContent(int oldType, int newType) {\n        String msg = \"您好，您的权限产生了变更，由【\" + CHINESE_ROLE.get(oldType - 1000) + \"】变更为【\" + CHINESE_ROLE.get(newType - 1000)\n                + \"】。部分权限可能与之前有所不同，请您注意！\" + \"\\n\\n\" + \"Hello, your permission has been changed from 【\"\n                + ENGLISH_ROLE.get(oldType - 1000) + \"】 to 【\" + ENGLISH_ROLE.get(newType - 1000)\n                + \"】. Some permissions may be different from before. Please note!\";\n        return msg;\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/AbstractTaskReceiver.java",
    "content": "package com.simplefanc.voj.backend.judge;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/22 12:40\n * @Description:\n */\npublic abstract class AbstractTaskReceiver {\n\n    public void handleWaitingTask(String... queues) {\n        for (String queue : queues) {\n            String taskJsonStr = getTaskFromRedis(queue);\n            if (taskJsonStr != null) {\n                handleTask(taskJsonStr);\n            }\n        }\n    }\n\n    public abstract String getTaskFromRedis(String queue);\n\n    public abstract void handleTask(String taskJsonStr);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/ChooseUtils.java",
    "content": "package com.simplefanc.voj.backend.judge;\n\nimport com.alibaba.cloud.nacos.NacosDiscoveryProperties;\nimport com.alibaba.cloud.nacos.NacosServiceManager;\nimport com.alibaba.nacos.api.exception.NacosException;\nimport com.alibaba.nacos.api.naming.NamingService;\nimport com.alibaba.nacos.api.naming.pojo.Instance;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.dao.judge.JudgeServerEntityService;\nimport com.simplefanc.voj.backend.mapper.RemoteJudgeAccountMapper;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeServer;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/24 17:30\n * @Description: 筛选可用判题机\n */\n@Component\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class ChooseUtils {\n\n    private final NacosServiceManager nacosServiceManager;\n\n    private final NacosDiscoveryProperties discoveryProperties;\n\n    @Value(\"${service-url.name}\")\n    private String judgeServiceName;\n\n    private final JudgeServerEntityService judgeServerEntityService;\n\n    private final RemoteJudgeAccountMapper remoteJudgeAccountMapper;\n\n    /**\n     * @param\n     * @MethodName chooseServer\n     * @Description 选择可以用调用判题的判题服务器\n     * @Return\n     * @Since 2021/4/15\n     */\n    @Transactional(rollbackFor = Exception.class)\n    public JudgeServer chooseJudgeServer(Boolean isRemote) {\n        // 获取该微服务的所有健康实例\n        List<Instance> instances = getInstances(judgeServiceName);\n        if (instances.isEmpty()) {\n            return null;\n        }\n        List<String> keyList = new ArrayList<>();\n        // 获取当前健康实例取出ip和port拼接\n        for (Instance instance : instances) {\n            keyList.add(instance.getIp() + \":\" + instance.getPort());\n        }\n\n        // 过滤出小于或等于规定最大并发判题任务数的服务实例且健康的判题机\n        QueryWrapper<JudgeServer> judgeServerQueryWrapper = new QueryWrapper<>();\n        judgeServerQueryWrapper.in(\"url\", keyList)\n                .eq(\"is_remote\", isRemote)\n                .orderByAsc(\"task_number\")\n                // 开启排他锁\n                .last(\"for update\");\n\n        // 如果一个条件无法通过索引快速过滤，存储引擎层面就会将所有记录加锁后返回，再由MySQL Server层进行过滤\n        // 但在实际使用过程当中，MySQL做了一些改进，在MySQL Server过滤条件，发现不满足后，会调用unlock_row方法，把不满足条件的记录释放锁 (违背了二段锁协议的约束)。\n        List<JudgeServer> judgeServerList = judgeServerEntityService.list(judgeServerQueryWrapper);\n\n        // 获取可用判题机\n        for (JudgeServer judgeServer : judgeServerList) {\n            if (judgeServer.getTaskNumber() < judgeServer.getMaxTaskNumber()) {\n                judgeServer.setTaskNumber(judgeServer.getTaskNumber() + 1);\n                boolean isOk = judgeServerEntityService.updateById(judgeServer);\n                if (isOk) {\n                    return judgeServer;\n                }\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * @param serviceId\n     * @MethodName getInstances\n     * @Description 根据服务id获取对应的健康实例列表\n     * @Return\n     * @Since 2021/4/15\n     */\n    private List<Instance> getInstances(String serviceId) {\n        // 获取服务发现的相关API\n        NamingService namingService = nacosServiceManager.getNamingService(discoveryProperties.getNacosProperties());\n        try {\n            // 获取该微服务的所有健康实例\n            return namingService.selectInstances(serviceId, true);\n        } catch (NacosException e) {\n            log.error(\"获取微服务健康实例发生异常--------->\", e);\n            return Collections.emptyList();\n        }\n    }\n\n    @Transactional(rollbackFor = Exception.class)\n    public RemoteJudgeAccount chooseRemoteAccount(String remoteOjName) {\n        if (RemoteOj.GYM.getName().equals(remoteOjName)) {\n            remoteOjName = RemoteOj.CF.getName();\n        }\n\n        // 过滤出当前远程oj可用的账号列表 悲观锁\n        List<RemoteJudgeAccount> remoteJudgeAccountList = remoteJudgeAccountMapper.getAvailableAccount(remoteOjName);\n\n        for (RemoteJudgeAccount remoteJudgeAccount : remoteJudgeAccountList) {\n            int count = remoteJudgeAccountMapper.updateAccountStatusById(remoteJudgeAccount.getId());\n            if (count > 0) {\n                return remoteJudgeAccount;\n            }\n        }\n\n        return null;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/Dispatcher.java",
    "content": "package com.simplefanc.voj.backend.judge;\n\nimport cn.hutool.core.lang.UUID;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.simplefanc.voj.backend.common.constants.CallJudgerType;\nimport com.simplefanc.voj.backend.common.utils.RestTemplateUtil;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeServerEntityService;\nimport com.simplefanc.voj.backend.dao.judge.RemoteJudgeAccountEntityService;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.pojo.dto.CompileDTO;\nimport com.simplefanc.voj.common.pojo.dto.JudgeDTO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeServer;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport com.simplefanc.voj.common.result.ResultStatus;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/15 17:29\n * @Description:\n */\n@Component\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class Dispatcher {\n\n    private final static ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(20);\n\n    private final static Map<String, ScheduledFuture<?>> FUTURE_TASK_MAP = new ConcurrentHashMap<>(20);\n\n    private final JudgeServerEntityService judgeServerEntityService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final ChooseUtils chooseUtils;\n\n    private final RemoteJudgeAccountEntityService remoteJudgeAccountService;\n\n    private final RestTemplateUtil restTemplateUtil;\n\n    public CommonResult dispatcher(CallJudgerType type, String path, Object data) {\n        switch (type) {\n            case JUDGE:\n                toJudge(path, (JudgeDTO) data);\n                break;\n            case COMPILE:\n                return toCompile(path, (CompileDTO) data);\n            default:\n                throw new IllegalArgumentException(\"判题机不支持此调用类型\");\n        }\n        return null;\n    }\n\n    /**\n     * @param path /compile-spj or /compile-interactive\n     * @param data\n     * @return\n     */\n    public CommonResult toCompile(String path, CompileDTO data) {\n        CommonResult result = CommonResult.errorResponse(\"没有可用的判题服务器，请重新尝试！\");\n        JudgeServer judgeServer = chooseUtils.chooseJudgeServer(false);\n        if (judgeServer != null) {\n            try {\n                result = restTemplateUtil.post(judgeServer.getUrl(), path, data, CommonResult.class);\n            } catch (Exception e) {\n                log.error(\"调用判题服务器[\" + judgeServer.getUrl() + \"]发送异常-------------->\", e);\n            } finally {\n                // 无论成功与否，都要将对应的当前判题机当前判题数减1\n                reduceCurrentTaskNum(judgeServer.getId());\n            }\n        }\n        return result;\n    }\n\n    public void toJudge(String path, JudgeDTO data) {\n        String key = UUID.randomUUID().toString() + data.getJudge().getSubmitId();\n        ScheduledFuture<?> scheduledFuture = SCHEDULER.scheduleWithFixedDelay(\n                new SubmitTask(path, data, key), 0, 2, TimeUnit.SECONDS);\n        FUTURE_TASK_MAP.put(key, scheduledFuture);\n    }\n\n    class SubmitTask implements Runnable {\n        /**\n         * /judge or /remote-judge\n         */\n        String path;\n\n        JudgeDTO data;\n\n        Long submitId;\n\n        Boolean isRemote;\n\n        String oj;\n\n        String key;\n\n        // 尝试600s\n        AtomicInteger count = new AtomicInteger(0);\n\n        public SubmitTask(String path, JudgeDTO data, String key) {\n            this.path = path;\n            this.data = data;\n            this.submitId = data.getJudge().getSubmitId();\n            this.isRemote = data.getRemoteJudgeProblem() != null;\n            String oj = null;\n            if (isRemote) {\n                oj = data.getRemoteJudgeProblem().split(\"-\")[0];\n            }\n            this.oj = oj;\n            this.key = key;\n        }\n\n        @Override\n        public void run() {\n            // 300次失败则判为提交失败\n            if (count.get() > 300) {\n                handleSubmitFailure();\n                return;\n            }\n            count.getAndIncrement();\n            JudgeServer judgeServer = chooseUtils.chooseJudgeServer(isRemote);\n            // 获取到判题机资源\n            if (judgeServer != null) {\n                handleJudgeProcess(judgeServer);\n            }\n        }\n\n        private void handleJudgeProcess(JudgeServer judgeServer) {\n            data.setJudgeServerIp(judgeServer.getIp());\n            data.setJudgeServerPort(judgeServer.getPort());\n            CommonResult result = null;\n            try {\n                // https://blog.csdn.net/qq_35893120/article/details/118637987\n                result = restTemplateUtil.post(judgeServer.getUrl(), path, data, CommonResult.class);\n            } catch (Exception e) {\n                log.error(\"调用判题服务器[\" + judgeServer.getUrl() + \"]发送异常-------------->\", e);\n            } finally {\n                checkResult(result, submitId);\n                // 无论成功与否，都要将对应的当前判题机当前判题数减1\n                reduceCurrentTaskNum(judgeServer.getId());\n                if (isRemote) {\n                    changeRemoteJudgeStatus(oj, data.getUsername());\n                }\n                cancelFutureTask(key);\n            }\n        }\n\n        private void handleSubmitFailure() {\n            // 远程判题需要将账号归为可用\n            if (isRemote) {\n                changeRemoteJudgeStatus(oj, data.getUsername());\n            }\n            checkResult(null, submitId);\n            cancelFutureTask(key);\n        }\n\n    }\n\n    private void checkResult(CommonResult<Void> result, Long submitId) {\n        Judge judge = new Judge();\n        // 调用失败\n        if (result == null) {\n            judge.setSubmitId(submitId);\n            judge.setStatus(JudgeStatus.STATUS_SUBMITTED_FAILED.getStatus());\n            judge.setErrorMessage(\"Failed to connect the JudgeServer. Please resubmit this submission again!\");\n            judgeEntityService.updateById(judge);\n        } else {\n            // 如果是结果码不是200 说明调用有错误\n            if (result.getStatus() != ResultStatus.SUCCESS.getStatus()) {\n                // 判为系统错误\n                judge.setStatus(JudgeStatus.STATUS_SYSTEM_ERROR.getStatus()).setErrorMessage(result.getMsg());\n                judgeEntityService.updateById(judge);\n            }\n        }\n    }\n\n    private void cancelFutureTask(String key) {\n        ScheduledFuture<?> future = FUTURE_TASK_MAP.get(key);\n        if (future != null) {\n            boolean isCanceled = future.cancel(true);\n            if (isCanceled) {\n                FUTURE_TASK_MAP.remove(key);\n            }\n        }\n    }\n\n    public void reduceCurrentTaskNum(Integer id) {\n        UpdateWrapper<JudgeServer> judgeServerUpdateWrapper = new UpdateWrapper<>();\n        judgeServerUpdateWrapper.setSql(\"task_number = task_number-1\").eq(\"id\", id);\n        boolean isOk = judgeServerEntityService.update(judgeServerUpdateWrapper);\n        if (!isOk) {\n            // 重试八次\n            tryAgainUpdateJudgeServer(judgeServerUpdateWrapper);\n        }\n    }\n\n    public void tryAgainUpdateJudgeServer(UpdateWrapper<JudgeServer> updateWrapper) {\n        boolean retryable;\n        int attemptNumber = 0;\n        do {\n            boolean success = judgeServerEntityService.update(updateWrapper);\n            if (success) {\n                return;\n            } else {\n                attemptNumber++;\n                retryable = attemptNumber < 8;\n                if (attemptNumber == 8) {\n                    break;\n                }\n                try {\n                    Thread.sleep(300);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        while (retryable);\n    }\n\n    public void changeRemoteJudgeStatus(String remoteOjName, String username) {\n        if (RemoteOj.GYM.getName().equals(remoteOjName)) {\n            remoteOjName = RemoteOj.CF.getName();\n        }\n\n        UpdateWrapper<RemoteJudgeAccount> remoteJudgeAccountUpdateWrapper = new UpdateWrapper<>();\n        remoteJudgeAccountUpdateWrapper.set(\"status\", true)\n                .eq(\"status\", false)\n                .eq(\"username\", username)\n                .eq(\"oj\", remoteOjName);\n\n        boolean isOk = remoteJudgeAccountService.update(remoteJudgeAccountUpdateWrapper);\n\n        if (!isOk) {\n            // 重试8次\n            tryAgainUpdateAccount(remoteJudgeAccountUpdateWrapper, remoteOjName, username);\n        }\n    }\n\n    private void tryAgainUpdateAccount(UpdateWrapper<RemoteJudgeAccount> updateWrapper, String remoteJudge,\n                                       String username) {\n        boolean retryable;\n        int attemptNumber = 0;\n        do {\n            boolean success = remoteJudgeAccountService.update(updateWrapper);\n            if (success) {\n                return;\n            } else {\n                attemptNumber++;\n                retryable = attemptNumber < 8;\n                if (attemptNumber == 8) {\n                    log.error(\"远程判题：修正账号为可用状态失败----------->{}\", \"oj:\" + remoteJudge + \",username:\" + username);\n                    break;\n                }\n                try {\n                    Thread.sleep(300);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        while (retryable);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/local/JudgeTaskDispatcher.java",
    "content": "package com.simplefanc.voj.backend.judge.local;\n\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.backend.common.constants.QueueConstant;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.stereotype.Component;\n\n/**\n * @Author: chenfan\n * @Date: 2021/2/5 16:44\n * @Description:\n */\n@Component\n@Slf4j(topic = \"voj\")\n@RefreshScope\n@RequiredArgsConstructor\npublic class JudgeTaskDispatcher {\n\n    private final RedisUtil redisUtil;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final JudgeTaskTaskReceiver judgeTaskReceiver;\n\n    @Value(\"${voj.judge.token}\")\n    private String judgeToken;\n\n    public void sendTask(Judge judge, Boolean isContest) {\n        JSONObject task = new JSONObject();\n        task.set(\"judge\", judge);\n        task.set(\"token\", judgeToken);\n        task.set(\"isContest\", isContest);\n        try {\n            boolean isOk;\n            if (isContest) {\n                isOk = redisUtil.llPush(QueueConstant.CONTEST_JUDGE_WAITING, JSONUtil.toJsonStr(task));\n            } else {\n                isOk = redisUtil.llPush(QueueConstant.GENERAL_JUDGE_WAITING, JSONUtil.toJsonStr(task));\n            }\n            if (!isOk) {\n                judgeEntityService.updateById(new Judge().setSubmitId(judge.getSubmitId())\n                        .setStatus(JudgeStatus.STATUS_SUBMITTED_FAILED.getStatus())\n                        .setErrorMessage(\"Please try to submit again!\"));\n            }\n            // 调用判题任务处理\n            judgeTaskReceiver.processWaitingTask();\n        } catch (Exception e) {\n            log.error(\"调用Redis将判题纳入判题等待队列异常，此次判题任务判为系统错误--------------->\", e);\n            judgeEntityService.failToUseRedisPublishJudge(judge.getSubmitId(), judge.getPid(), isContest);\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/local/JudgeTaskTaskReceiver.java",
    "content": "package com.simplefanc.voj.backend.judge.local;\n\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.backend.common.constants.CallJudgerType;\nimport com.simplefanc.voj.backend.common.constants.QueueConstant;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.judge.AbstractTaskReceiver;\nimport com.simplefanc.voj.backend.judge.Dispatcher;\nimport com.simplefanc.voj.common.pojo.dto.JudgeDTO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Component;\n\n/**\n * @Author: chenfan\n * @Date: 2021/2/5 16:43\n * @Description:\n */\n@Component\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class JudgeTaskTaskReceiver extends AbstractTaskReceiver {\n\n    private final Dispatcher dispatcher;\n\n    private final RedisUtil redisUtil;\n\n    @Async(\"judgeTaskAsyncPool\")\n    public void processWaitingTask() {\n        // 优先处理比赛的提交\n        // 其次处理普通提交的提交\n        this.handleWaitingTask(QueueConstant.CONTEST_JUDGE_WAITING, QueueConstant.GENERAL_JUDGE_WAITING);\n    }\n\n    @Override\n    public String getTaskFromRedis(String queue) {\n        long size = redisUtil.lGetListSize(queue);\n        if (size > 0) {\n            return (String) redisUtil.lrPop(queue);\n        } else {\n            return null;\n        }\n    }\n\n    @Override\n    public void handleTask(String taskJsonStr) {\n        JSONObject task = JSONUtil.parseObj(taskJsonStr);\n        Judge judge = task.get(\"judge\", Judge.class);\n        String token = task.getStr(\"token\");\n        // 调用判题服务\n        dispatcher.dispatcher(CallJudgerType.JUDGE, \"/judge\",\n                new JudgeDTO().setJudge(judge).setToken(token).setRemoteJudgeProblem(null));\n        // 接着处理任务\n        processWaitingTask();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/RemoteJudgeTaskDispatcher.java",
    "content": "package com.simplefanc.voj.backend.judge.remote;\n\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.backend.common.constants.QueueConstant;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.stereotype.Component;\n\n@Component\n@Slf4j(topic = \"voj\")\n@RefreshScope\n@RequiredArgsConstructor\npublic class RemoteJudgeTaskDispatcher {\n\n    private final RedisUtil redisUtil;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final RemoteJudgeTaskReceiver remoteJudgeTaskReceiver;\n\n    @Value(\"${voj.judge.token}\")\n    private String judgeToken;\n\n    public void sendTask(Judge judge, String remoteJudgeProblem, Boolean isContest) {\n        JSONObject task = new JSONObject();\n        task.set(\"judge\", judge);\n        task.set(\"remoteJudgeProblem\", remoteJudgeProblem);\n        task.set(\"token\", judgeToken);\n        task.set(\"isContest\", isContest);\n        try {\n            boolean isOk;\n            if (isContest) {\n                isOk = redisUtil.llPush(QueueConstant.CONTEST_REMOTE_JUDGE_WAITING_HANDLE, JSONUtil.toJsonStr(task));\n            } else {\n                isOk = redisUtil.llPush(QueueConstant.GENERAL_REMOTE_JUDGE_WAITING_HANDLE, JSONUtil.toJsonStr(task));\n            }\n            if (!isOk) {\n                judgeEntityService.updateById(new Judge().setSubmitId(judge.getSubmitId())\n                        .setStatus(JudgeStatus.STATUS_SUBMITTED_FAILED.getStatus())\n                        .setErrorMessage(\"Please try to submit again!\"));\n            }\n            remoteJudgeTaskReceiver.processWaitingTask();\n        } catch (Exception e) {\n            log.error(\"调用redis将判题纳入判题等待队列异常,此次判题任务判为系统错误--------------->\", e);\n            judgeEntityService.failToUseRedisPublishJudge(judge.getSubmitId(), judge.getPid(), isContest);\n        }\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/RemoteJudgeTaskReceiver.java",
    "content": "package com.simplefanc.voj.backend.judge.remote;\n\nimport cn.hutool.core.lang.UUID;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.backend.common.constants.CallJudgerType;\nimport com.simplefanc.voj.backend.common.constants.QueueConstant;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.judge.AbstractTaskReceiver;\nimport com.simplefanc.voj.backend.judge.ChooseUtils;\nimport com.simplefanc.voj.backend.judge.Dispatcher;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.dto.JudgeDTO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n@Component\n@RequiredArgsConstructor\npublic class RemoteJudgeTaskReceiver extends AbstractTaskReceiver {\n\n    private final static ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(10);\n\n    private final static Map<String, ScheduledFuture<?>> FUTURE_TASK_MAP = new ConcurrentHashMap<>(10);\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final Dispatcher dispatcher;\n\n    private final RedisUtil redisUtil;\n\n    private final ChooseUtils chooseUtils;\n\n    @Async(\"judgeTaskAsyncPool\")\n    public void processWaitingTask() {\n        // 优先处理比赛的提交\n        // 其次处理普通提交的提交\n        this.handleWaitingTask(QueueConstant.CONTEST_REMOTE_JUDGE_WAITING_HANDLE,\n                QueueConstant.GENERAL_REMOTE_JUDGE_WAITING_HANDLE);\n    }\n\n    @Override\n    public String getTaskFromRedis(String queue) {\n        if (redisUtil.lGetListSize(queue) > 0) {\n            return (String) redisUtil.lrPop(queue);\n        } else {\n            return null;\n        }\n    }\n\n    @Override\n    public void handleTask(String taskJsonStr) {\n        JSONObject task = JSONUtil.parseObj(taskJsonStr);\n\n        Judge judge = task.get(\"judge\", Judge.class);\n        String token = task.getStr(\"token\");\n        String remoteJudgeProblem = task.getStr(\"remoteJudgeProblem\");\n        JudgeDTO toJudge = new JudgeDTO();\n        toJudge.setJudge(judge).setToken(token).setRemoteJudgeProblem(remoteJudgeProblem);\n        dispatchRemoteJudge(toJudge);\n    }\n\n    private void dispatchRemoteJudge(JudgeDTO toJudge) {\n        commonJudge(toJudge);\n        // 如果队列中还有任务，则继续处理\n        processWaitingTask();\n    }\n\n    private void commonJudge(JudgeDTO toJudge) {\n        String key = UUID.randomUUID().toString() + toJudge.getJudge().getSubmitId();\n        ScheduledFuture<?> scheduledFuture = SCHEDULER.scheduleWithFixedDelay(\n                new RemoteJudgeAccountTask(toJudge, key), 0, 3, TimeUnit.SECONDS);\n        FUTURE_TASK_MAP.put(key, scheduledFuture);\n    }\n\n    class RemoteJudgeAccountTask implements Runnable {\n\n        String ojName;\n\n        JudgeDTO toJudge;\n\n        Judge judge;\n\n        String key;\n\n        // 尝试600s\n        AtomicInteger tryNum = new AtomicInteger(0);\n\n        public RemoteJudgeAccountTask(JudgeDTO toJudge, String key) {\n            this.ojName = toJudge.getRemoteJudgeProblem().split(\"-\")[0].toUpperCase();\n            this.toJudge = toJudge;\n            this.judge = toJudge.getJudge();\n            this.key = key;\n        }\n\n        @Override\n        public void run() {\n            if (tryNum.get() > 200) {\n                // 获取调用多次失败可能为系统忙碌，判为提交失败\n                judge.setStatus(JudgeStatus.STATUS_SUBMITTED_FAILED.getStatus());\n                judge.setErrorMessage(\"Submission failed! Please resubmit this submission again!\"\n                        + \"Cause: Waiting for account scheduling timeout\");\n                judgeEntityService.updateById(judge);\n                cancelFutureTask();\n                return;\n            }\n            tryNum.getAndIncrement();\n            RemoteJudgeAccount account = chooseUtils.chooseRemoteAccount(ojName);\n            if (account != null) {\n                toJudge.setUsername(account.getUsername()).setPassword(account.getPassword());\n                // 调用判题服务\n                dispatcher.dispatcher(CallJudgerType.JUDGE, \"/remote-judge\", toJudge);\n                cancelFutureTask();\n            }\n        }\n\n        private void cancelFutureTask() {\n            ScheduledFuture<?> future = FUTURE_TASK_MAP.get(key);\n            if (future != null) {\n                boolean isCanceled = future.cancel(true);\n                if (isCanceled) {\n                    FUTURE_TASK_MAP.remove(key);\n                }\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/AbstractCFStyleProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.http.HtmlUtil;\nimport cn.hutool.http.HttpRequest;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport com.simplefanc.voj.common.utils.CodeForcesUtils;\nimport org.springframework.stereotype.Component;\n\nimport java.net.HttpCookie;\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\n@Component\npublic abstract class AbstractCFStyleProblemCrawler extends AbstractProblemCrawler {\n    public static final String HOST = \"https://codeforces.com\";\n\n    protected List<HttpCookie> cookies;\n    private final Pattern inputExamplePattern = Pattern.compile(\"<div class=\\\"input\\\">\\\\s*<div class=\\\"title\\\">\\\\s*Input\\\\s*</div>\\\\s*<pre>([\\\\s\\\\S]*?)</pre>\\\\s*</div>\");;\n    private final Pattern outputExamplePattern = Pattern.compile(\"<div class=\\\"output\\\">\\\\s*<div class=\\\"title\\\">\\\\s*Output\\\\s*</div>\\\\s*<pre>([\\\\s\\\\S]*?)</pre>\\\\s*</div>\");;\n    private final Pattern tagPattern = Pattern.compile(\"<span class=\\\"tag-box\\\" style=\\\"font-size:1\\\\.2rem;\\\" title=\\\"[\\\\s\\\\S]*?\\\">([\\\\s\\\\S]*?)</span>\");;\n\n    protected abstract String getProblemUrl(String contestId, String problemNum);\n\n    protected abstract String getProblemSource(String html, String problemId, String contestId, String problemNum);\n\n    @Override\n    public AbstractProblemCrawler.RemoteProblemInfo getProblemInfo(String problemId) throws Exception {\n        String contestId;\n        String problemNum;\n        if (NumberUtil.isInteger(problemId)) {\n            contestId = ReUtil.get(\"([0-9]+)[0-9]{2}\", problemId, 1);\n            problemNum = ReUtil.get(\"[0-9]+([0-9]{2})\", problemId, 1);\n        } else {\n            contestId = ReUtil.get(\"([0-9]+)[A-Z]{1}[0-9]{0,1}\", problemId, 1);\n            problemNum = ReUtil.get(\"[0-9]+([A-Z]{1}[0-9]{0,1})\", problemId, 1);\n        }\n\n        if (contestId == null || problemNum == null) {\n            throw new IllegalArgumentException(\"Codeforces: Incorrect problem id format!\");\n        }\n\n        HttpRequest request = HttpRequest.get(getProblemUrl(contestId, problemNum))\n                .header(\"cookie\", \"RCPC=\" + CodeForcesUtils.getRCPC())\n                .timeout(20000);\n        if (cookies != null) {\n            request.cookie(cookies);\n        }\n        String html = request\n                .execute()\n                .body();\n\n        // 重定向失效，更新RCPC\n        if (html.contains(\"Redirecting... Please, wait.\")) {\n            List<String> list = ReUtil.findAll(\"[a-z0-9]+[a-z0-9]{31}\", html, 0, new ArrayList<>());\n            CodeForcesUtils.updateRCPC(list);\n            html = request.execute()\n                    .body();\n        }\n\n        RemoteProblemInfo problemInfo = new RemoteProblemInfo();\n        Problem info = problemInfo.getProblem();\n        info.setProblemId(getOjInfo() + \"-\" + problemId);\n\n        info.setTitle(ReUtil.get(\"<div class=\\\"title\\\">\\\\s*\" + problemNum + \"\\\\. ([\\\\s\\\\S]*?)</div>\", html, 1).trim());\n\n        String timeLimitStr = ReUtil.get(\"</div>\\\\s*([\\\\d\\\\.]+) (seconds?|s)\\\\s*</div>\", html, 1);\n        if (StrUtil.isEmpty(timeLimitStr)) {\n            timeLimitStr = ReUtil.get(\"</div>\\\\s*<span .*?>(\\\\d+) (seconds?|s)\\\\s*</span>\\\\s*</div>\", html, 1);\n        }\n\n        double timeLimit = 1000 * Double.parseDouble(timeLimitStr);\n        info.setTimeLimit((int) timeLimit);\n\n        String memoryLimitStr = ReUtil.get(\"</div>\\\\s*(\\\\d+) (megabytes|MB)\\\\s*</div>\", html, 1);\n        if (StrUtil.isEmpty(memoryLimitStr)) {\n            memoryLimitStr = ReUtil.get(\"</div>\\\\s*<span .*?>(\\\\d+) (megabytes|MB)\\\\s*</span>\\\\s*</div>\", html, 1);\n        }\n\n        info.setMemoryLimit(Integer.parseInt(memoryLimitStr));\n\n        String tmpDesc = ReUtil.get(\"standard output\\\\s*</div>\\\\s*</div>\\\\s*<div>([\\\\s\\\\S]*?)</div>\\\\s*<div class=\\\"input-specification\",\n                html, 1);\n        if (StrUtil.isEmpty(tmpDesc)) {\n            tmpDesc = ReUtil.get(\"<div class=\\\"input-file\\\">([\\\\s\\\\S]*?)</div><div class=\\\"input-specification\", html, 1);\n        }\n\n        if (StrUtil.isEmpty(tmpDesc)) {\n            // 交互题\n            tmpDesc = ReUtil.get(\"standard output\\\\s*</div>\\\\s*</div>\\\\s*<div>([\\\\s\\\\S]*?)</div>\\\\s*<div>\\\\s*<div class=\\\"section-title\", html, 1);\n        }\n\n        if (StrUtil.isEmpty(tmpDesc)) {\n            // 单单只有题面描述\n            tmpDesc = ReUtil.get(\"standard output\\\\s*</div>\\\\s*</div>\\\\s*<div>([\\\\s\\\\S]*?)</div>\",\n                    html, 1);\n        }\n\n        if (StrUtil.isNotEmpty(tmpDesc)) {\n            tmpDesc = tmpDesc.replaceAll(\"\\\\$\\\\$\\\\$\", \"\\\\$\")\n                    .replaceAll(\"src=\\\"../../\", \"src=\\\"\" + HOST + \"/\")\n                    .trim();\n        }\n        info.setDescription(tmpDesc);\n\n        String inputDesc = ReUtil.get(\"<div class=\\\"section-title\\\">\\\\s*Input\\\\s*</div>([\\\\s\\\\S]*?)</div>\\\\s*<div class=\\\"output-specification\\\">\", html, 1);\n        if (StrUtil.isEmpty(inputDesc)) {\n            inputDesc = ReUtil.get(\"<div class=\\\"section-title\\\">\\\\s*Interaction\\\\s*</div>([\\\\s\\\\S]*?)</div>\\\\s*<div class=\\\"sample-tests\\\">\", html, 1);\n        }\n        if (StrUtil.isEmpty(inputDesc)) {\n            inputDesc = ReUtil.get(\"<div class=\\\"input-specification\\\">\\\\s*<div class=\\\"section-title\\\">\\\\s*Input\\\\s*</div>([\\\\s\\\\S]*?)</div>\", html, 1);\n        }\n        if (StrUtil.isNotEmpty(inputDesc)) {\n            inputDesc = inputDesc.replaceAll(\"\\\\$\\\\$\\\\$\", \"\\\\$\").trim();\n        }\n        info.setInput(inputDesc);\n\n        String outputDesc = ReUtil.get(\"<div class=\\\"section-title\\\">\\\\s*Output\\\\s*</div>([\\\\s\\\\S]*?)</div>\\\\s*<div class=\\\"sample-tests\\\">\", html, 1);\n        if (StrUtil.isNotEmpty(outputDesc)) {\n            outputDesc = outputDesc.replaceAll(\"\\\\$\\\\$\\\\$\", \"\\\\$\").trim();\n        }\n        info.setOutput(outputDesc);\n\n        StringBuilder sb = new StringBuilder();\n        List<String> inputExampleList = ReUtil.findAll(inputExamplePattern, html, 1);\n        List<String> outputExampleList = ReUtil.findAll(outputExamplePattern, html, 1);\n        for (int i = 0; i < inputExampleList.size() && i < outputExampleList.size(); i++) {\n            sb.append(\"<input>\");\n            String input = inputExampleList.get(i)\n                    .replaceAll(\"<br>\", \"\\n\")\n                    .replaceAll(\"<br />\", \"\\n\")\n                    .replaceAll(\"<div .*?>\", \"\")\n                    .replaceAll(\"</div>\", \"\\n\")\n                    .trim();\n            sb.append(HtmlUtil.unescape(input)).append(\"</input>\");\n            sb.append(\"<output>\");\n            String output = outputExampleList.get(i)\n                    .replaceAll(\"<br>\", \"\\n\")\n                    .replaceAll(\"<br />\", \"\\n\")\n                    .trim();\n            sb.append(HtmlUtil.unescape(output)).append(\"</output>\");\n        }\n        info.setExamples(sb.toString());\n\n        String tmpHint = ReUtil.get(\"<div class=\\\"section-title\\\">\\\\s*Note\\\\s*</div>([\\\\s\\\\S]*?)</div>\\\\s*</div>\", html, 1);\n        if (tmpHint != null) {\n            info.setHint(tmpHint.replaceAll(\"\\\\$\\\\$\\\\$\", \"\\\\$\").trim());\n        }\n\n        info.setSource(getProblemSource(html, problemId, contestId, problemNum));\n\n        List<String> allTags = ReUtil.findAll(tagPattern, html, 1);\n        List<Tag> tagList = new LinkedList<>();\n        for (String tmp : allTags) {\n            tagList.add(new Tag().setName(tmp.trim()));\n        }\n        return problemInfo.setTagList(tagList);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/AbstractProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.ProblemEnum;\nimport com.simplefanc.voj.common.constants.ProblemLevelEnum;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport java.util.List;\n\npublic abstract class AbstractProblemCrawler {\n\n    public abstract RemoteProblemInfo getProblemInfo(String problemId) throws Exception;\n\n    public abstract String getOjInfo();\n\n    @Data\n    @Accessors(chain = true)\n    public static class RemoteProblemInfo {\n        private Problem problem = Problem.builder()\n                .isRemote(true)\n                .type(ContestEnum.TYPE_ACM.getCode())\n                .auth(ProblemEnum.AUTH_PUBLIC.getCode())\n                .author(UserSessionUtil.getUserInfo().getUsername())\n                .openCaseResult(false)\n                .isRemoveEndBlank(false)\n                .difficulty(ProblemLevelEnum.PROBLEM_LEVEL_MID.getCode())\n                .build();\n\n        private List<Tag> tagList = null;\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/AtCoderProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpUtil;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Element;\nimport org.springframework.stereotype.Component;\n\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/26 9:00\n * @Description:\n */\n@Component\npublic class AtCoderProblemCrawler extends AbstractProblemCrawler {\n\n    public static final String HOST = \"https://atcoder.jp\";\n    public static final String PROBLEM_URL = \"/contests/%s/tasks/%s\";\n    private final Pattern pattern = Pattern.compile(\"Time Limit: (\\\\d+) sec / Memory Limit: (\\\\d+) MB\");\n\n    public String getProblemUrl(String problemId, String contestId) {\n        return HOST + String.format(PROBLEM_URL, contestId, problemId);\n    }\n\n    public String getProblemSource(String problemId, String contestId) {\n        return String.format(\"<a style='color:#1A5CC8' href='\" + getProblemUrl(problemId, contestId) + \"'>%s</a>\", \"AtCoder-\" + problemId);\n    }\n\n    @Override\n    public RemoteProblemInfo getProblemInfo(String problemId) throws Exception {\n        problemId = problemId.toLowerCase();\n        boolean isMatch = ReUtil.isMatch(\"[a-z]+[0-9]+_[a-z]*[0-9]*\", problemId);\n        if (!isMatch) {\n            throw new IllegalArgumentException(\"AtCoder: Incorrect problem id format! Must be like `abc110_a`\");\n        }\n\n        String contestId = problemId.split(\"_\")[0];\n\n        String body = HttpUtil.get(getProblemUrl(problemId, contestId));\n        Matcher matcher = pattern.matcher(body);\n        Assert.isTrue(matcher.find());\n        String timeLimit = matcher.group(1).trim();\n        String memoryLimit = matcher.group(2).trim();\n        String title = ReUtil.get(\"<title>[\\\\s\\\\S]*? - ([\\\\s\\\\S]*?)</title>\", body, 1);\n\n\n        Problem problem = new Problem();\n        problem.setProblemId(getOjInfo() + \"-\" + problemId)\n                .setTitle(title)\n                .setTimeLimit(Integer.parseInt(timeLimit) * 1000)\n                .setMemoryLimit(Integer.parseInt(memoryLimit))\n                .setSource(getProblemSource(problemId, contestId));\n\n        if (body.contains(\"Problem Statement\")) {\n            String desc = ReUtil.get(\"<h3>Problem Statement</h3>([\\\\s\\\\S]*?)</section>[\\\\s\\\\S]*?</div>\", body, 1);\n\n            desc = desc.replaceAll(\"<var>\", \"\\\\$\").replaceAll(\"</var>\", \"\\\\$\");\n            desc = desc.replaceAll(\"<pre>\", \"<pre style=\\\"padding:9px!important;background-color: #f5f5f5!important\\\">\");\n            desc = desc.replaceAll(\"src=\\\"/img\", \"src=\\\"\" + HOST + \"/img\");\n\n            StringBuilder sb = new StringBuilder();\n            String rawInput = ReUtil.get(\"<h3>Input</h3>([\\\\s\\\\S]*?)</section>[\\\\s\\\\S]*?</div>\", body, 1);\n            sb.append(rawInput);\n            String constrains = ReUtil.get(\"<h3>Constraints</h3>([\\\\s\\\\S]*?)</section>[\\\\s\\\\S]*?</div>\", body, 1);\n            sb.append(constrains);\n            String input = sb.toString().replaceAll(\"<var>\", \"\\\\$\").replaceAll(\"</var>\", \"\\\\$\");\n            input = input.replaceAll(\"<pre>\", \"<pre style=\\\"padding:9px!important;background-color: #f5f5f5!important\\\">\");\n\n\n            String rawOutput = ReUtil.get(\"<h3>Output</h3>([\\\\s\\\\S]*?)</section>[\\\\s\\\\S]*?</div>\", body, 1);\n            String output = rawOutput.replaceAll(\"<var>\", \"\\\\$\").replaceAll(\"</var>\", \"\\\\$\");\n            output = output.replaceAll(\"<pre>\", \"<pre style=\\\"padding:9px!important;background-color: #f5f5f5!important\\\">\");\n\n            List<String> sampleInput = ReUtil.findAll(\"<h3>Sample Input \\\\d+</h3><pre>([\\\\s\\\\S]*?)</pre>[\\\\s\\\\S]*?</section>[\\\\s\\\\S]*?</div>\", body, 1);\n            List<String> sampleOutput = ReUtil.findAll(\"<h3>Sample Output \\\\d+</h3><pre>([\\\\s\\\\S]*?)</pre>[\\\\s\\\\S]*?</section>[\\\\s\\\\S]*?</div>\", body, 1);\n\n\n            StringBuilder examples = new StringBuilder();\n            for (int i = 0; i < sampleInput.size() && i < sampleOutput.size(); i++) {\n                examples.append(\"<input>\");\n                String exampleInput = sampleInput.get(i).trim();\n                examples.append(exampleInput).append(\"</input>\");\n                examples.append(\"<output>\");\n                String exampleOutput = sampleOutput.get(i).trim();\n                examples.append(exampleOutput).append(\"</output>\");\n            }\n\n            problem.setInput(input.trim())\n                    .setOutput(output.trim())\n                    .setDescription(desc.trim())\n                    .setExamples(examples.toString());\n\n\n        } else {\n            Element element = Jsoup.parse(body).getElementById(\"task-statement\");\n            String desc = element.html();\n            desc = desc.replaceAll(\"src=\\\"/img\", \"src=\\\"https://atcoder.jp/img\");\n            desc = desc.replaceAll(\"<pre>\", \"<pre style=\\\"padding:9px!important;background-color: #f5f5f5!important\\\">\");\n            desc = desc.replaceAll(\"<var>\", \"\\\\$\").replaceAll(\"</var>\", \"\\\\$\");\n            desc = desc.replaceAll(\"<hr>\", \"\");\n            problem.setDescription(desc);\n        }\n        return new RemoteProblemInfo()\n                .setProblem(problem);\n    }\n\n    @Override\n    public String getOjInfo() {\n        return RemoteOj.AtCoder.getName();\n    }\n\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/CFProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.util.ReUtil;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport org.springframework.stereotype.Component;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/25 9:00\n * @Description:\n */\n@Component\npublic class CFProblemCrawler extends AbstractCFStyleProblemCrawler {\n    private static final String PROBLEM_URL = \"/problemset/problem/%s/%s\";\n\n    @Override\n    public String getOjInfo() {\n        return RemoteOj.CF.getName();\n    }\n\n    public String getProblemUrl(String contestId, String problemNum) {\n        return HOST + String.format(PROBLEM_URL, contestId, problemNum);\n    }\n\n    public String getProblemSource(String html, String problemId, String contestId, String problemNum) {\n        return String.format(\"<p>Problem：<a style='color:#1A5CC8' href='https://codeforces.com/problemset/problem/%s/%s'>%s</a></p><p>\" +\n                        \"Contest：\" + ReUtil.get(\"(<a[^<>]+/contest/\\\\d+\\\">.+?</a>)\", html, 1)\n                        .replace(\"/contest\", HOST + \"/contest\")\n                        .replace(\"color: black\", \"color: #009688;\") + \"</p>\",\n                contestId, problemNum, getOjInfo() + \"-\" + problemId);\n    }\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/CrawlersHolder.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport com.simplefanc.voj.common.utils.Tools;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n@Slf4j\npublic class CrawlersHolder {\n\n    private static HashMap<String, AbstractProblemCrawler> crawlers = new HashMap<>();\n\n    public static AbstractProblemCrawler getCrawler(String remoteOj) {\n        if (!crawlers.containsKey(remoteOj)) {\n            synchronized (crawlers) {\n                if (!crawlers.containsKey(remoteOj)) {\n                    try {\n                        List<Class<? extends AbstractProblemCrawler>> crawlerClasses = Tools.findSubClasses(\n                                \"com.simplefanc.voj.backend.judge.remote.crawler\", AbstractProblemCrawler.class);\n                        for (Class<? extends AbstractProblemCrawler> crawlerClass : crawlerClasses) {\n                            AbstractProblemCrawler crawler = SpringUtil.getBean(crawlerClass);\n                            crawlers.put(crawler.getOjInfo(), crawler);\n                        }\n                    } catch (Throwable t) {\n                        log.error(\"Get Crawler Failed\", t);\n                    }\n                }\n            }\n        }\n        return crawlers.get(remoteOj);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/GYMProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.http.HttpRequest;\nimport cn.hutool.http.HttpResponse;\nimport cn.hutool.http.HttpUtil;\nimport cn.hutool.http.Method;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.dao.judge.RemoteJudgeAccountEntityService;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.utils.CodeForcesUtils;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n@Component\n@RequiredArgsConstructor\npublic class GYMProblemCrawler extends AbstractCFStyleProblemCrawler {\n    private final FilePathProperties filePathProperties;\n    private final RemoteJudgeAccountEntityService remoteJudgeAccountEntityService;\n\n    private static final String PROBLEM_URL = \"/gym/%s/problem/%s\";\n    private static final String LOGIN_URL = \"/enter\";\n\n    @Override\n    public String getOjInfo() {\n        return RemoteOj.GYM.getName();\n    }\n\n    @Override\n    public String getProblemUrl(String contestId, String problemNum) {\n        return HOST + String.format(PROBLEM_URL, contestId, problemNum);\n    }\n\n    @Override\n    public String getProblemSource(String html, String problemId, String contestNum, String problemNum) {\n        return String.format(\"<p>Problem：<a style='color:#1A5CC8' href='https://codeforces.com/gym/%s/problem/%s'>%s</a></p><p>\" +\n                        \"Contest：\" + ReUtil.get(\"(<a[^<>]+/gym/\\\\d+\\\">.+?</a>)\", html, 1)\n                        .replace(\"/gym\", HOST + \"/gym\")\n                        .replace(\"color: black\", \"color: #009688;\") + \"</p>\",\n                contestNum, problemNum, getOjInfo() + \"-\" + problemId);\n    }\n\n    @Override\n    public AbstractProblemCrawler.RemoteProblemInfo getProblemInfo(String problemId) {\n        if (cookies == null) {\n            RemoteJudgeAccount account = remoteJudgeAccountEntityService.lambdaQuery()\n                    .eq(RemoteJudgeAccount::getOj, RemoteOj.CF.getName())\n                    .list().get(0);\n            if (account != null) {\n                login(account.getUsername(), account.getPassword());\n            }\n        }\n        try {\n            return super.getProblemInfo(problemId);\n        } catch (Exception ignored) {\n            String contestNum = ReUtil.get(\"([0-9]+)[A-Z]{1}[0-9]{0,1}\", problemId, 1);\n            String problemNum = ReUtil.get(\"[0-9]+([A-Z]{1}[0-9]{0,1})\", problemId, 1);\n            return getPDFHtml(problemId, contestNum, problemNum);\n        }\n    }\n\n    private RemoteProblemInfo getPDFHtml(String problemId, String contestNum, String problemNum) {\n        RemoteProblemInfo problemInfo = new RemoteProblemInfo();\n        Problem problem = problemInfo.getProblem();\n\n        String url = HOST + \"/gym/\" + contestNum;\n        HttpRequest request = HttpRequest.get(url)\n                .header(\"cookie\", \"RCPC=\" + CodeForcesUtils.getRCPC())\n                .timeout(20000);\n        if (cookies != null) {\n            request.cookie(cookies);\n        }\n        String html = request.execute().body();\n\n        // 重定向失效，更新RCPC\n        if (html.contains(\"Redirecting... Please, wait.\")) {\n            List<String> list = ReUtil.findAll(\"[a-z0-9]+[a-z0-9]{31}\", html, 0, new ArrayList<>());\n            CodeForcesUtils.updateRCPC(list);\n            html = request.execute().body();\n        }\n\n        String regex = \"<a href=\\\"/gym/\" + contestNum + \"/problem/\" + problemNum\n                + \"\\\"><!--\\\\s*-->([^<]+)(?:(?:.|\\\\s)*?<div){2}[^>]*>\\\\s*([^<]+)</div>\\\\s*([\\\\d.]+)\\\\D*(\\\\d+)\";\n\n        Matcher matcher = Pattern.compile(regex).matcher(html);\n        matcher.find();\n\n        problem.setProblemId(getOjInfo() + \"-\" + problemId);\n        problem.setTitle(matcher.group(1));\n        problem.setTimeLimit((int) (Double.parseDouble(matcher.group(3)) * 1000));\n        problem.setMemoryLimit(Integer.parseInt(matcher.group(4)));\n\n        problem.setSource(String.format(\"<p>Problem：<a style='color:#1A5CC8' href='https://codeforces.com/gym/%s/attachments'>%s</a></p><p>\" +\n                        \"Contest：\" + ReUtil.get(\"(<a[^<>]+/gym/\\\\d+\\\">.+?</a>)\", html, 1)\n                        .replace(\"/gym\", HOST + \"/gym\")\n                        .replace(\"color: black\", \"color: #009688;\") + \"</p>\",\n                contestNum, getOjInfo() + \"-\" + problemId));\n\n\n        regex = \"/gym/\" + contestNum + \"/attachments/download\\\\S*?\\\\.pdf\";\n\n        matcher = Pattern.compile(regex).matcher(html);\n        matcher.find();\n\n        String pdfURI;\n        try {\n            String fileName = IdUtil.fastSimpleUUID() + \".pdf\";\n            String filePath = filePathProperties.getProblemFileFolder() + File.separator + fileName;\n            HttpUtil.downloadFile(HOST + matcher.group(0), filePath);\n            pdfURI = filePathProperties.getFileApi() + fileName;\n        } catch (Exception e1) {\n            try {\n                pdfURI = HOST + matcher.group(0);\n            } catch (Exception e2) {\n                String fileName = IdUtil.fastSimpleUUID() + \".pdf\";\n                String filePath = filePathProperties.getProblemFileFolder() + File.separator + fileName;\n                CodeForcesUtils.downloadPDF(HOST + \"/gym/\" + contestNum + \"/problem/\" + problemNum, filePath);\n                pdfURI = filePathProperties.getFileApi() + fileName;\n            }\n        }\n        String description = \"<p><a style='color:#3091f2' href=\\\"\" + pdfURI + \"\\\">Click here to download the PDF file.</a></p>\";\n        problem.setDescription(description);\n        return problemInfo;\n    }\n\n    public void login(String username, String password) {\n        HashMap<String, Object> keyMap = getCsrfToken(HOST + LOGIN_URL, false);\n        HttpRequest httpRequest = HttpRequest.of(HOST + LOGIN_URL);\n        httpRequest.setConnectionTimeout(60000);\n        httpRequest.setReadTimeout(60000);\n        httpRequest.setMethod(Method.POST);\n        httpRequest.cookie(cookies);\n        HashMap<String, Object> hashMap = new HashMap<>();\n        hashMap.put(\"csrf_token\", keyMap.get(\"csrf_token\"));\n        hashMap.put(\"action\", \"enter\");\n        hashMap.put(\"ftaa\", keyMap.get(\"ftaa\"));\n        hashMap.put(\"bfaa\", keyMap.get(\"bfaa\"));\n        hashMap.put(\"handleOrEmail\", username);\n        hashMap.put(\"password\", password);\n        hashMap.put(\"remember\", \"on\");\n        httpRequest.form(hashMap);\n        HttpResponse response = httpRequest.execute();\n        cookies = response.getCookies();\n    }\n\n    public HashMap<String, Object> getCsrfToken(String url, boolean needTTA) {\n\n        HttpRequest request = HttpUtil.createGet(url);\n\n        request.header(\"cookie\", \"RCPC=\" + CodeForcesUtils.getRCPC());\n\n        HttpResponse response = request.execute();\n        String body = response.body();\n        if (body.contains(\"Redirecting... Please, wait.\")) {\n            List<String> list = ReUtil.findAll(\"[a-z0-9]+[a-z0-9]{31}\", body, 0, new ArrayList<>());\n            CodeForcesUtils.updateRCPC(list);\n            request.removeHeader(\"cookie\");\n            request.header(\"cookie\", \"RCPC=\" + CodeForcesUtils.getRCPC());\n            response = request.execute();\n            body = response.body();\n        }\n\n        HashMap<String, Object> res = new HashMap<>();\n        cookies = response.getCookies();\n        String ftaa = response.getCookieValue(\"70a7c28f3de\");\n        res.put(\"ftaa\", ftaa);\n\n        String bfaa = ReUtil.get(\"_bfaa = \\\"(.{32})\\\"\", body, 1);\n        if (StrUtil.isEmpty(bfaa)) {\n            bfaa = response.getCookieValue(\"raa\");\n            if (StrUtil.isEmpty(bfaa)) {\n                bfaa = response.getCookieValue(\"bfaa\");\n            }\n        }\n        res.put(\"bfaa\", bfaa);\n\n        String csrfToken = ReUtil.get(\"data-csrf='(\\\\w+)'\", body, 1);\n        res.put(\"csrf_token\", csrfToken);\n\n        if (needTTA) {\n            String _39ce7 = response.getCookieValue(\"39ce7\");\n            int _tta = 0;\n            for (int c = 0; c < _39ce7.length(); c++) {\n                _tta = (_tta + (c + 1) * (c + 2) * _39ce7.charAt(c)) % 1009;\n                if (c % 3 == 0) {\n                    _tta++;\n                }\n                if (c % 2 == 0) {\n                    _tta *= 2;\n                }\n                if (c > 0) {\n                    _tta -= (_39ce7.charAt(c / 2) / 2) * (_tta % 5);\n                }\n                while (_tta < 0) {\n                    _tta += 1009;\n                }\n                while (_tta >= 1009) {\n                    _tta -= 1009;\n                }\n            }\n            res.put(\"_tta\", _tta);\n        }\n        return res;\n    }\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/HDUProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpUtil;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\n/**\n * @Author: chenfan\n * @Date: 2021/2/17 22:42\n * @Description:\n */\n@Component\npublic class HDUProblemCrawler extends AbstractProblemCrawler {\n\n    public static final String JUDGE_NAME = \"HDU\";\n\n    public static final String HOST = \"https://acm.hdu.edu.cn\";\n\n    public static final String PROBLEM_URL = \"/showproblem.php?pid=%s\";\n\n    /**\n     * @param problemId String的原因是因为某些题库题号不是纯数字\n     * @return 返回Problem对象\n     * @throws Exception\n     */\n    @Override\n    public RemoteProblemInfo getProblemInfo(String problemId) throws Exception {\n        // 验证题号是否符合规范\n        Assert.isTrue(problemId.matches(\"[1-9]\\\\d*\"), \"HDU题号格式错误！\");\n        RemoteProblemInfo problemInfo = new RemoteProblemInfo();\n        Problem info = problemInfo.getProblem();\n        String url = HOST + String.format(PROBLEM_URL, problemId);\n        String html = HttpUtil.get(url);\n        info.setProblemId(JUDGE_NAME + \"-\" + problemId);\n        info.setTitle(ReUtil.get(\"color:#1A5CC8'>([\\\\s\\\\S]*?)</h1>\", html, 1).trim());\n        info.setTimeLimit(Integer.parseInt(ReUtil.get(\"(\\\\d*) MS\", html, 1)));\n        info.setMemoryLimit(Integer.parseInt(ReUtil.get(\"/(\\\\d*) K\", html, 1)) / 1024);\n        info.setDescription(ReUtil.get(\">Problem Description</div> <div class=.*?>([\\\\s\\\\S]*?)</div>\", html, 1)\n                .replaceAll(\"src=[./]*\", \"src=\" + HOST + \"/\"));\n        info.setInput(ReUtil.get(\">Input</div> <div class=.*?>([\\\\s\\\\S]*?)</div>\", html, 1));\n        info.setOutput(ReUtil.get(\">Output</div> <div class=.*?>([\\\\s\\\\S]*?)</div>\", html, 1));\n        StringBuilder sb = new StringBuilder(\"<input>\");\n        sb.append(ReUtil.get(\">Sample Input</div><div .*?,monospace;\\\">([\\\\s\\\\S]*?)</div></pre>\", html, 1));\n        sb.append(\"</input><output>\");\n        sb.append(ReUtil.get(\n                \">Sample Output</div><div .*?monospace;\\\">([\\\\s\\\\S]*?)(<div style=.*?</div><i style=.*?</i>)*?</div></pre>\",\n                html, 1)).append(\"</output>\");\n        info.setExamples(sb.toString());\n        info.setHint(ReUtil.get(\"<i>Hint</i></div>([\\\\s\\\\S]*?)</div><i .*?<br><[^<>]*?panel_title[^<>]*?>\", html, 1));\n        info.setSource(\n                String.format(\"<a style='color:#1A5CC8' href='https://acm.hdu.edu.cn/showproblem.php?pid=%s'>%s</a>\",\n                        problemId, JUDGE_NAME + \"-\" + problemId));\n\n        return problemInfo;\n    }\n\n    @Override\n    public String getOjInfo() {\n        return JUDGE_NAME;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/JSKProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author chenfan\n * @date 2022/1/17 20:15\n **/\n@Component\npublic class JSKProblemCrawler extends AbstractProblemCrawler {\n\n    public static final String JUDGE_NAME = \"JSK\";\n\n    public static final String HOST = \"https://www.jisuanke.com\";\n\n    public static final String PROBLEM_URL = \"/problem/%s\";\n\n    private Pattern compile1 = Pattern.compile(\"([\\\\s\\\\S]*?)<h4>输入格式</h4><p>([\\\\s\\\\S]*?)</p><h4>输出格式</h4><p>([\\\\s\\\\S]*?)</p>\");\n    private Pattern compile2 = Pattern.compile(\"([\\\\s\\\\S]*?)输入格式([\\\\s\\\\S]*?)输出格式([\\\\s\\\\S]*)\");\n    private Pattern compile3 = Pattern.compile(\"([\\\\s\\\\S]*?)输入([\\\\s\\\\S]*?)输出([\\\\s\\\\S]*)\");\n\n    /**\n     * @param problemId String的原因是因为某些题库题号不是纯数字\n     * @return 返回Problem对象\n     * @throws Exception\n     */\n    @Override\n    public RemoteProblemInfo getProblemInfo(String problemId) throws Exception {\n        // 验证题号是否符合规范 t/a开头+4位数字\n        Assert.isTrue(problemId.matches(\"[TAta]\\\\d{4}\"), \"JSK题号格式错误！\");\n        RemoteProblemInfo problemInfo = new RemoteProblemInfo();\n        Problem info = problemInfo.getProblem();\n        String url = HOST + String.format(PROBLEM_URL, problemId);\n        String html = HttpUtil.get(url);\n        html = html.replaceAll(\"<br>\", \"\\n\");\n        String problem = ReUtil.get(\"var problem=(\\\\{[\\\\s\\\\S]*?\\\\});\", html, 1);\n        JSONObject jsonObject = JSONUtil.parseObj(problem);\n        info.setProblemId(JUDGE_NAME + \"-\" + problemId);\n        info.setInfo(jsonObject.getStr(\"id\"));\n        info.setTitle(jsonObject.getStr(\"title\"));\n        info.setTimeLimit(jsonObject.getInt(\"time_limit\"));\n        info.setMemoryLimit(jsonObject.getInt(\"mem_limit\") / 1024);\n\n        String description = jsonObject.getStr(\"description\");\n        Matcher matcher;\n        if (problemId.toUpperCase().charAt(0) == 'T') {\n            matcher = compile1.matcher(description);\n            if (matcher.find()) {\n                info.setDescription(matcher.group(1));\n                info.setInput(matcher.group(2));\n                info.setOutput(matcher.group(3));\n            }\n        } else {\n            matcher = compile2.matcher(description);\n            if (matcher.find()) {\n                info.setDescription(matcher.group(1));\n                info.setInput(matcher.group(2));\n                info.setOutput(matcher.group(3));\n            } else {\n                matcher = compile3.matcher(description);\n                if (matcher.find()) {\n                    info.setDescription(matcher.group(1));\n                    info.setInput(matcher.group(2));\n                    info.setOutput(matcher.group(3));\n                }\n            }\n        }\n\n        String sb = \"<input>\" + jsonObject.getStr(\"sample_input\") +\n                \"</input><output>\" + jsonObject.getStr(\"sample_output\") + \"</output>\";\n        info.setExamples(sb);\n\n        info.setHint(jsonObject.getStr(\"hint\"));\n        info.setSource(String.format(\"<a style='color:#1A5CC8' href='https://www.jisuanke.com/problem/%s'>%s</a>\",\n                problemId, JUDGE_NAME + \"-\" + problemId));\n        return problemInfo;\n    }\n\n    @Override\n    public String getOjInfo() {\n        return JUDGE_NAME;\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/MXTProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpUtil;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\n/**\n * @author chenfan\n * @date 2022/1/17 23:06\n **/\n@Component\npublic class MXTProblemCrawler extends AbstractProblemCrawler {\n\n    public static final String JUDGE_NAME = \"MXT\";\n\n    public static final String HOST = \"https://mxt.cn\";\n\n    public static final String PROBLEM_URL = \"/course/%s.html\";\n\n    @Override\n    public RemoteProblemInfo getProblemInfo(String problemId) throws Exception {\n        // 验证题号是否符合规范 4位数字\n        Assert.isTrue(problemId.matches(\"\\\\d{4,5}\"), \"MXT题号格式错误！\");\n        RemoteProblemInfo problemInfo = new RemoteProblemInfo();\n        Problem info = problemInfo.getProblem();\n        String url = HOST + String.format(PROBLEM_URL, problemId);\n        String html = HttpUtil.get(url);\n        html = html.replaceAll(\"<br>\", \"\\n\");\n        info.setProblemId(JUDGE_NAME + \"-\" + problemId);\n        info.setTitle(ReUtil.get(\"<div class=\\\"page-header\\\">\\n\"\n                + \"\\t\\t<h2>([A-Z]\\\\d{4} )(\\\\[[\\\\s\\\\S]*?\\\\] )*([\\\\s\\\\S]*?)<\\\\/h2>\\n\" + \"\\t<\\\\/div>\", html, 3));\n        info.setDescription(ReUtil.getGroup1(\"<div class=\\\"panel-heading\\\">\\n\" + \"\\t\\t\\t<b>描述</b>\\n\" + \"\\t\\t</div>\\n\"\n                + \"\\t\\t<div class=\\\"panel-body jdc-latex-show\\\">([\\\\s\\\\S]*?)</div>\", html).trim());\n        info.setInput(ReUtil.getGroup1(\"<div class=\\\"panel-heading\\\">\\n\" + \"\\t\\t\\t<b>输入</b>\\n\" + \"\\t\\t</div>\\n\"\n                + \"\\t\\t<div class=\\\"panel-body jdc-latex-show\\\">([\\\\s\\\\S]*?)</div>\", html).trim());\n        info.setOutput(ReUtil.getGroup1(\"<div class=\\\"panel-heading\\\">\\n\" + \"\\t\\t\\t<b>输出</b>\\n\" + \"\\t\\t<\\\\/div>\\n\"\n                + \"\\t\\t<div class=\\\"panel-body jdc-latex-show\\\">([\\\\s\\\\S]*?)</div>\", html).trim());\n        // info.setTimeLimit(jsonObject.getInt(\"time_limit\"));\n        // info.setMemoryLimit(jsonObject.getInt(\"mem_limit\") / 1024);\n        String sb = \"<input>\" + ReUtil.getGroup1(\"<div class=\\\"panel-heading\\\">\\n\"\n                + \"\\t\\t\\t<b>样例输入 </b>\\n\" + \"\\t\\t</div>\\n\" + \"\\t\\t<div class=\\\"panel-body\\\">\\n\"\n                + \"\\t\\t<figure class=\\\"highlight\\\"><pre class=\\\"pre\\\" style=\\\"background-color:#fff\\\">([\\\\s\\\\S]*?)</pre></figure>\\n\"\n                + \"\\t\\t</div>\", html) +\n                \"</input><output>\" +\n                ReUtil.getGroup1(\"<div class=\\\"panel-heading\\\">\\n\" + \"\\t\\t\\t<b>样例输出 </b>\\n\" + \"\\t\\t</div>\\n\"\n                        + \"\\t\\t<div class=\\\"panel-body\\\">\\n\"\n                        + \"\\t\\t<figure><pre class=\\\"pre\\\" style=\\\"background-color:#fff\\\">([\\\\s\\\\S]*?)</pre></figure>\\n\"\n                        + \"\\t\\t</div>\", html) +\n                \"</output>\";\n        info.setExamples(sb);\n        final String hint = ReUtil.getGroup1(\"<div class=\\\"panel-heading\\\">\\n\" + \"\\t\\t\\t<b>提示</b>\\n\" + \"\\t\\t</div>\\n\"\n                + \"\\t\\t<div class=\\\"panel-body jdc-latex-show\\\">([\\\\s\\\\S]*?)</div>\", html);\n        info.setHint(hint != null ? hint.trim() : hint);\n        info.setSource(String.format(\"<a style='color:#1A5CC8' href='https://mxt.cn/course/%s.html'>%s</a>\",\n                problemId, JUDGE_NAME + \"-\" + problemId));\n        return problemInfo;\n    }\n\n    @Override\n    public String getOjInfo() {\n        return JUDGE_NAME;\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/POJProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpUtil;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\n/**\n * @Author: chenfan\n * @Date: 2021/6/24 23:27\n * @Description:\n */\n@Component\npublic class POJProblemCrawler extends AbstractProblemCrawler {\n\n    public static final String JUDGE_NAME = \"POJ\";\n\n    public static final String HOST = \"http://poj.org\";\n\n    public static final String PROBLEM_URL = \"/problem?id=%s\";\n\n    @Override\n    public RemoteProblemInfo getProblemInfo(String problemId) throws Exception {\n        // 验证题号是否符合规范 1-9开头的数字\n        Assert.isTrue(problemId.matches(\"[1-9]\\\\d*\"), \"POJ题号格式错误！\");\n        RemoteProblemInfo problemInfo = new RemoteProblemInfo();\n        Problem info = problemInfo.getProblem();\n        String url = HOST + String.format(PROBLEM_URL, problemId);\n        String html = HttpUtil.get(url);\n\n        html = html.replaceAll(\"<br>\", \"\\n\");\n        info.setProblemId(JUDGE_NAME + \"-\" + problemId);\n        info.setTitle(ReUtil.get(\"<title>\\\\d{3,} -- ([\\\\s\\\\S]*?)</title>\", html, 1).trim());\n        info.setTimeLimit(Integer.parseInt(ReUtil.get(\"<b>Time Limit:</b> (\\\\d{3,})MS</td>\", html, 1)));\n        info.setMemoryLimit(Integer.parseInt(ReUtil.get(\"<b>Memory Limit:</b> (\\\\d{2,})K</td>\", html, 1)) / 1024);\n        info.setDescription(ReUtil\n                .get(\"<p class=\\\"pst\\\">Description</p><div class=.*?>([\\\\s\\\\S]*?)</div><p class=\\\"pst\\\">\", html, 1)\n                .replaceAll(\"src=\\\"[../]*\", \"src=\\\"\" + HOST + \"/\"));\n\n        info.setInput(\n                ReUtil.get(\"<p class=\\\"pst\\\">Input</p><div class=.*?>([\\\\s\\\\S]*?)</div><p class=\\\"pst\\\">\", html, 1));\n        info.setOutput(\n                ReUtil.get(\"<p class=\\\"pst\\\">Output</p><div class=.*?>([\\\\s\\\\S]*?)</div><p class=\\\"pst\\\">\", html, 1));\n\n        String sb = \"<input>\" +\n                ReUtil.get(\n                        \"<p class=\\\"pst\\\">Sample Input</p><pre class=.*?>([\\\\s\\\\S]*?)</pre><p class=\\\"pst\\\">\", html, 1) +\n                \"</input><output>\" +\n                ReUtil.get(\n                        \"<p class=\\\"pst\\\">Sample Output</p><pre class=.*?>([\\\\s\\\\S]*?)</pre><p class=\\\"pst\\\">\", html,\n                        1) +\n                \"</output>\";\n        info.setExamples(sb);\n\n        info.setHint(ReUtil.get(\"<p class=.*?>Hint</p><div class=.*?>([\\\\s\\\\S]*?)</div><p class=\\\"pst\\\">\", html, 1));\n        info.setSource(String.format(\"<a style='color:#1A5CC8' href='http://poj.org/problem?id=%s'>%s</a>\", problemId,\n                JUDGE_NAME + \"-\" + problemId));\n        return problemInfo;\n    }\n\n    @Override\n    public String getOjInfo() {\n        return JUDGE_NAME;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/TKOJProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpUtil;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\n/**\n * @author chenfan\n * @date 2022/1/17 23:07\n **/\n@Component\npublic class TKOJProblemCrawler extends AbstractProblemCrawler {\n\n    public static final String JUDGE_NAME = \"TKOJ\";\n\n    public static final String HOST = \"http://tk.hustoj.com\";\n\n    public static final String PROBLEM_URL = \"/problem.php?id=%s\";\n\n    @Override\n    public RemoteProblemInfo getProblemInfo(String problemId) throws Exception {\n        // 验证题号是否符合规范 4位数字\n        Assert.isTrue(problemId.matches(\"\\\\d{4}\"), \"TKOJ题号格式错误！\");\n        RemoteProblemInfo problemInfo = new RemoteProblemInfo();\n        Problem info = problemInfo.getProblem();\n        String url = HOST + String.format(PROBLEM_URL, problemId);\n        String html = HttpUtil.get(url);\n        html = html.replaceAll(\"<br>\", \"\\n\");\n        info.setProblemId(JUDGE_NAME + \"-\" + problemId);\n        info.setTitle(ReUtil.getGroup1(\"<h2>\" + problemId + \": ([\\\\s\\\\S]*?)</h2>\", html).trim());\n        info.setDescription(\n                ReUtil.getGroup1(\"<h2>Description</h2><div class=\\\"content\\\">([\\\\s\\\\S]*?)</div><h2>Input</h2\", html));\n        info.setInput(ReUtil.getGroup1(\"<h2>Input</h2><div class=\\\"content\\\">([\\\\s\\\\S]*?)</div>\", html));\n        info.setOutput(ReUtil.getGroup1(\"<h2>Output</h2><div class=\\\"content\\\">([\\\\s\\\\S]*?)</div>\", html));\n        info.setTimeLimit(Integer.parseInt(ReUtil.getGroup1(\"Time Limit: </span>([1-9]\\\\d*) Sec\", html)) * 1000);\n        info.setMemoryLimit(Integer.parseInt(ReUtil.getGroup1(\"Memory Limit: </span>([1-9]\\\\d*) MB\", html)));\n        String sb = \"<input>\" +\n                ReUtil.getGroup1(\"<h2>Sample Input</h2>\\n\"\n                        + \"<pre class=\\\"content\\\"><span class=\\\"sampledata\\\">([\\\\s\\\\S]*?)</span></pre>\", html) +\n                \"</input><output>\" +\n                ReUtil.getGroup1(\"<h2>Sample Output</h2>\\n\"\n                        + \"<pre class=\\\"content\\\"><span class=\\\"sampledata\\\">([\\\\s\\\\S]*?)</span></pre>\", html) +\n                \"</output>\";\n        info.setExamples(sb);\n        final String hint = ReUtil.getGroup1(\"<h2>HINT</h2><div class=\\\"content\\\">([\\\\s\\\\S]*?)</div>\", html);\n        info.setHint(hint != null ? hint.trim() : hint);\n        info.setSource(String.format(\"<a style='color:#1A5CC8' href='http://tk.hustoj.com/problem.php?id=%s'>%s</a>\",\n                problemId, JUDGE_NAME + \"-\" + problemId));\n\n        return problemInfo;\n    }\n\n    @Override\n    public String getOjInfo() {\n        return JUDGE_NAME;\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/judge/remote/crawler/YACSProblemCrawler.java",
    "content": "package com.simplefanc.voj.backend.judge.remote.crawler;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\n/**\n * @author chenfan\n * @date 2022/2/15 17:15\n **/\n@Component\npublic class YACSProblemCrawler extends AbstractProblemCrawler {\n\n    public static final String JUDGE_NAME = \"YACS\";\n\n    public static final String HOST = \"http://www.iai.sh.cn\";\n\n    public static final String PROBLEM_URL = \"/problem/%s\";\n\n    /**\n     * @param problemId String的原因是因为某些题库题号不是纯数字\n     * @return 返回Problem对象\n     * @throws Exception\n     */\n    @Override\n    public RemoteProblemInfo getProblemInfo(String problemId) throws Exception {\n        // 验证题号是否符合规范\n        Assert.isTrue(problemId.matches(\"\\\\d+\"), \"YACS题号格式错误！\");\n        RemoteProblemInfo problemInfo = new RemoteProblemInfo();\n        Problem info = problemInfo.getProblem();\n        String url = HOST + String.format(PROBLEM_URL, problemId);\n        String html = HttpUtil.get(url);\n        html = html.replaceAll(\"<br>\", \"\\n\");\n        String problem = ReUtil.getGroup1(\"\\\"problem\\\":({[\\\\s\\\\S]*?})}}\", html);\n        JSONObject jsonObject = JSONUtil.parseObj(problem);\n        info.setProblemId(JUDGE_NAME + \"-\" + problemId);\n        info.setTitle(jsonObject.getStr(\"title\"));\n        info.setTimeLimit(jsonObject.getInt(\"limitTime\"));\n        info.setMemoryLimit(jsonObject.getInt(\"limitMemory\"));\n\n        info.setDescription(jsonObject.getStr(\"description\"));\n        info.setInput(jsonObject.getStr(\"inputFormat\"));\n        info.setOutput(jsonObject.getStr(\"outputFormat\"));\n\n        JSONArray exampleList = jsonObject.getJSONArray(\"exampleList\");\n\n        String sb = \"<input>\" +\n                // .append(jsonObject.getStr(\"sample_input\"))\n                \"</input><output>\" +\n                // .append(jsonObject.getStr(\"sample_output\"))\n                \"</output>\";\n        info.setExamples(sb);\n\n        info.setHint(jsonObject.getStr(\"dataRange\"));\n        info.setSource(String.format(\"<a style='color:#1A5CC8' href='https://nanti.jisuanke.com/t/%s'>%s</a>\",\n                problemId, JUDGE_NAME + \"-\" + problemId));\n        return problemInfo;\n    }\n\n    @Override\n    public String getOjInfo() {\n        return JUDGE_NAME;\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/AdminSysNoticeMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.vo.AdminSysNoticeVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.AdminSysNotice;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\n@Mapper\npublic interface AdminSysNoticeMapper extends BaseMapper<AdminSysNotice> {\n\n    IPage<AdminSysNoticeVO> getAdminSysNotice(Page<AdminSysNoticeVO> page, @Param(\"type\") String type);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/AnnouncementMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface AnnouncementMapper extends BaseMapper<Announcement> {\n\n    IPage<AnnouncementVO> getAnnouncementList(Page<AnnouncementVO> page, @Param(\"notAdmin\") Boolean notAdmin);\n\n    IPage<AnnouncementVO> getContestAnnouncement(Page<AnnouncementVO> page, @Param(\"cid\") Long cid,\n                                                 @Param(\"notAdmin\") Boolean notAdmin);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/AuthMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.user.Auth;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface AuthMapper extends BaseMapper<Auth> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/CategoryMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.Category;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface CategoryMapper extends BaseMapper<Category> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/CodeTemplateMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.CodeTemplate;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface CodeTemplateMapper extends BaseMapper<CodeTemplate> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/CommentLikeMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.discussion.CommentLike;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface CommentLikeMapper extends BaseMapper<CommentLike> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/CommentMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.vo.CommentVO;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Comment;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface CommentMapper extends BaseMapper<Comment> {\n\n    IPage<CommentVO> getCommentList(Page<CommentVO> page, @Param(\"cid\") Long cid, @Param(\"did\") Integer did,\n                                    @Param(\"onlyMineAndAdmin\") Boolean onlyMineAndAdmin,\n                                    @Param(\"myAndAdminUidList\") List<String> myAndAdminUidList);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ContestAnnouncementMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestAnnouncement;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface ContestAnnouncementMapper extends BaseMapper<ContestAnnouncement> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ContestMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.ContestRegisterCountVO;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface ContestMapper extends BaseMapper<Contest> {\n\n    List<Contest> getContestList(IPage page, @Param(\"type\") Integer type, @Param(\"status\") Integer status,\n                                   @Param(\"keyword\") String keyword);\n\n    List<ContestRegisterCountVO> getContestRegisterCount(@Param(\"cidList\") List<Long> cidList);\n\n    List<Contest> getWithinNext14DaysContests();\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ContestPrintMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestPrint;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * @Author: chenfan\n * @Date: 2021/9/19 21:04\n * @Description:\n */\n@Mapper\npublic interface ContestPrintMapper extends BaseMapper<ContestPrint> {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ContestProblemMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.backend.pojo.vo.ContestProblemVO;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface ContestProblemMapper extends BaseMapper<ContestProblem> {\n\n    List<ContestProblemVO> getContestProblemList(@Param(\"cid\") Long cid, @Param(\"startTime\") Date startTime,\n                                                 @Param(\"endTime\") Date endTime, @Param(\"sealTime\") Date sealTime, @Param(\"isAdmin\") Boolean isAdmin,\n                                                 @Param(\"adminList\") List<String> adminList);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ContestRecordMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.backend.pojo.vo.ContestRecordVO;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface ContestRecordMapper extends BaseMapper<ContestRecord> {\n\n    List<ContestRecord> getACInfo(@Param(\"status\") Integer status, @Param(\"cid\") Long cid);\n\n    List<ContestRecordVO> getOIContestRecordByRecentSubmission(@Param(\"cid\") Long cid,\n                                                               @Param(\"isOpenSealRank\") Boolean isOpenSealRank,\n                                                               @Param(\"sealTime\") Date sealTime, @Param(\"startTime\") Date startTime, @Param(\"endTime\") Date endTime);\n\n    List<ContestRecordVO> getOIContestRecordByHighestSubmission(@Param(\"cid\") Long cid,\n                                                                @Param(\"isOpenSealRank\") Boolean isOpenSealRank,\n                                                                @Param(\"sealTime\") Date sealTime, @Param(\"startTime\") Date startTime, @Param(\"endTime\") Date endTime);\n\n    List<ContestRecordVO> getACMContestRecord(@Param(\"cid\") Long cid, @Param(\"startTime\") Date startTime);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ContestRegisterMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRegister;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface ContestRegisterMapper extends BaseMapper<ContestRegister> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/DiscussionLikeMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionLike;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface DiscussionLikeMapper extends BaseMapper<DiscussionLike> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/DiscussionMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.backend.pojo.vo.DiscussionVO;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\n@Mapper\npublic interface DiscussionMapper extends BaseMapper<Discussion> {\n\n    DiscussionVO getDiscussion(@Param(\"did\") Integer did, @Param(\"uid\") String uid);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/DiscussionReportMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionReport;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface DiscussionReportMapper extends BaseMapper<DiscussionReport> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/FileMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.common.File;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Select;\nimport org.apache.ibatis.annotations.Update;\n\nimport java.util.List;\n\n@Mapper\npublic interface FileMapper extends BaseMapper<File> {\n\n    @Update(\"UPDATE `file` SET `delete` = 1 WHERE `uid` = #{uid} AND `type` = #{type}\")\n    int updateFileToDeleteByUidAndType(@Param(\"uid\") String uid, @Param(\"type\") String type);\n\n    @Select(\"select * from file where (type = 'avatar' AND `delete` = true)\")\n    List<File> queryDeleteAvatarList();\n\n    @Select(\"select * from file where (type = 'carousel')\")\n    List<File> queryCarouselFileList();\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/JudgeCaseMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface JudgeCaseMapper extends BaseMapper<JudgeCase> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/JudgeMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.vo.JudgeVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemCountVO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface JudgeMapper extends BaseMapper<Judge> {\n\n    IPage<JudgeVO> getCommonJudgeList(Page<JudgeVO> page, @Param(\"searchPid\") String searchPid,\n                                      @Param(\"status\") Integer status, @Param(\"username\") String username, @Param(\"uid\") String uid,\n                                      @Param(\"completeProblemId\") Boolean completeProblemId);\n\n    // TODO 参数过多\n    IPage<JudgeVO> getContestJudgeList(Page<JudgeVO> page, @Param(\"displayId\") String displayId, @Param(\"cid\") Long cid,\n                                       @Param(\"status\") Integer status, @Param(\"username\") String username, @Param(\"uid\") String uid,\n                                       @Param(\"beforeContestSubmit\") Boolean beforeContestSubmit, @Param(\"rule\") String rule,\n                                       @Param(\"startTime\") Date startTime, @Param(\"sealRankTime\") Date sealRankTime,\n                                       @Param(\"sealTimeUid\") String sealTimeUid, @Param(\"completeProblemId\") Boolean completeProblemId);\n\n    int getTodayJudgeNum();\n\n    ProblemCountVO getContestProblemCount(@Param(\"pid\") Long pid, @Param(\"cpid\") Long cpid, @Param(\"cid\") Long cid,\n                                          @Param(\"startTime\") Date startTime, @Param(\"sealRankTime\") Date sealRankTime,\n                                          @Param(\"adminList\") List<String> adminList);\n\n    ProblemCountVO getProblemCount(@Param(\"pid\") Long pid);\n\n    List<ProblemCountVO> getProblemListCount(@Param(\"pidList\") List<Long> pidList);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/JudgeServerMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeServer;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface JudgeServerMapper extends BaseMapper<JudgeServer> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/LanguageMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.Language;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface LanguageMapper extends BaseMapper<Language> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/MappingTrainingCategoryMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.training.MappingTrainingCategory;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface MappingTrainingCategoryMapper extends BaseMapper<MappingTrainingCategory> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/MsgRemindMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.vo.UserMsgVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserUnreadMsgCountVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.MsgRemind;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\n@Mapper\npublic interface MsgRemindMapper extends BaseMapper<MsgRemind> {\n\n    UserUnreadMsgCountVO getUserUnreadMsgCount(@Param(\"uid\") String uid);\n\n    IPage<UserMsgVO> getUserMsg(Page<UserMsgVO> page, @Param(\"uid\") String uid, @Param(\"action\") String action);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ProblemCaseMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/14 19:57\n * @Description:\n */\n@Mapper\npublic interface ProblemCaseMapper extends BaseMapper<ProblemCase> {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ProblemLanguageMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemLanguage;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface ProblemLanguageMapper extends BaseMapper<ProblemLanguage> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ProblemMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface ProblemMapper extends BaseMapper<Problem> {\n\n    List<ProblemVO> getProblemList(IPage page, @Param(\"keyword\") String keyword,\n                                   @Param(\"difficulty\") Integer difficulty, @Param(\"tid\") List<Long> tid,\n                                   @Param(\"tagListSize\") Integer tagListSize, @Param(\"oj\") String oj, @Param(\"allProblemVisible\") boolean allProblemVisible);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ProblemTagMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemTag;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface ProblemTagMapper extends BaseMapper<ProblemTag> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/RemoteJudgeAccountMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Select;\nimport org.apache.ibatis.annotations.Update;\n\nimport java.util.List;\n\n@Mapper\npublic interface RemoteJudgeAccountMapper extends BaseMapper<RemoteJudgeAccount> {\n\n    @Select(\"select * from `remote_judge_account` where `oj` = #{oj} and `status` = 1 for update\")\n    List<RemoteJudgeAccount> getAvailableAccount(@Param(\"oj\") String oj);\n\n    @Update(\"update `remote_judge_account` set `status` = 0 where `id` = #{id} and `status` = 1\")\n    int updateAccountStatusById(@Param(\"id\") Integer id);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/ReplyMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/5 22:07\n * @Description:\n */\n\n@Mapper\npublic interface ReplyMapper extends BaseMapper<Reply> {\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/RoleAuthMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.backend.pojo.vo.RoleAuthsVO;\nimport com.simplefanc.voj.common.pojo.entity.user.RoleAuth;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface RoleAuthMapper extends BaseMapper<RoleAuth> {\n\n    RoleAuthsVO getRoleAuths(@Param(\"rid\") long rid);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/RoleMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.user.Role;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface RoleMapper extends BaseMapper<Role> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/SessionMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.user.Session;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface SessionMapper extends BaseMapper<Session> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/TagClassificationMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.TagClassification;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.springframework.stereotype.Repository;\n\n/**\n * @Author chenfan\n * @Date 2022/8/3\n */\n@Mapper\npublic interface TagClassificationMapper extends BaseMapper<TagClassification> {\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/TagMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface TagMapper extends BaseMapper<Tag> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/TrainingCategoryMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\n@Mapper\npublic interface TrainingCategoryMapper extends BaseMapper<TrainingCategory> {\n\n    public TrainingCategory getTrainingCategoryByTrainingId(@Param(\"tid\") Long tid);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/TrainingMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingVO;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/19 22:03\n * @Description:\n */\n@Mapper\npublic interface TrainingMapper extends BaseMapper<Training> {\n\n    List<TrainingVO> getTrainingList(@Param(\"categoryId\") Long categoryId, @Param(\"auth\") String auth,\n                                     @Param(\"keyword\") String keyword);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/TrainingProblemMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingProblem;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n@Mapper\npublic interface TrainingProblemMapper extends BaseMapper<TrainingProblem> {\n\n    public List<Long> getTrainingProblemCount(@Param(\"tid\") Long tid);\n\n    public List<ProblemVO> getTrainingProblemList(@Param(\"tid\") Long tid);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/TrainingRecordMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingRecordVO;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRecord;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/21 14:27\n * @Description:\n */\n\n@Mapper\npublic interface TrainingRecordMapper extends BaseMapper<TrainingRecord> {\n\n    public List<TrainingRecordVO> getTrainingRecord(@Param(\"tid\") Long tid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/TrainingRegisterMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRegister;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface TrainingRegisterMapper extends BaseMapper<TrainingRegister> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/UserAcproblemMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface UserAcproblemMapper extends BaseMapper<UserAcproblem> {\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/UserInfoMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterDTO;\nimport com.simplefanc.voj.common.pojo.entity.user.UserInfo;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface UserInfoMapper extends BaseMapper<UserInfo> {\n\n    int addUser(RegisterDTO registerDTO);\n\n    List<String> getSuperAdminUidList(@Param(\"roleId\") Long roleId);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/UserRecordMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.vo.ACMRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.OIRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserHomeVO;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface UserRecordMapper {\n\n    IPage<ACMRankVO> getACMRankList(Page<ACMRankVO> page, @Param(\"uidList\") List<String> uidList);\n\n    List<ACMRankVO> getRecent7ACRank();\n\n    IPage<OIRankVO> getOIRankList(Page<OIRankVO> page, @Param(\"uidList\") List<String> uidList);\n\n    UserHomeVO getUserHomeInfo(@Param(\"uid\") String uid, @Param(\"username\") String username);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/UserRoleMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.common.pojo.entity.user.Role;\nimport com.simplefanc.voj.common.pojo.entity.user.UserRole;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface UserRoleMapper extends BaseMapper<UserRole> {\n\n    UserRolesVO getUserRoles(@Param(\"uid\") String uid, @Param(\"username\") String username);\n\n    List<Role> getRolesByUid(@Param(\"uid\") String uid);\n\n    IPage<UserRolesVO> getUserList(Page<UserRolesVO> page, @Param(\"limit\") int limit,\n                                   @Param(\"currentPage\") int currentPage, @Param(\"keyword\") String keyword,\n                                   @Param(\"roleId\") Long roleId, @Param(\"status\") Integer status);\n\n    IPage<UserRolesVO> getAdminUserList(Page<UserRolesVO> page,\n                                        @Param(\"limit\") int limit,\n                                        @Param(\"currentPage\") int currentPage,\n                                        @Param(\"keyword\") String keyword,\n                                        @Param(\"roleIdList\") List roleIdList);\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/UserSysNoticeMapper.java",
    "content": "package com.simplefanc.voj.backend.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.vo.SysMsgVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.UserSysNotice;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\n@Mapper\npublic interface UserSysNoticeMapper extends BaseMapper<UserSysNotice> {\n\n    IPage<SysMsgVO> getSysOrMineNotice(Page<SysMsgVO> page, @Param(\"uid\") String uid, @Param(\"type\") String type);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/AdminSysNoticeMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.AdminSysNoticeMapper\">\n\n    <resultMap id=\"map_AdminSysNoticeList\" type=\"com.simplefanc.voj.backend.pojo.vo.AdminSysNoticeVO\">\n        <id column=\"id\" property=\"id\"></id>\n        <result column=\"title\" property=\"title\"></result>\n        <result column=\"content\" property=\"content\"></result>\n        <result column=\"type\" property=\"type\"></result>\n        <result column=\"state\" property=\"state\"></result>\n        <result column=\"username\" property=\"adminUsername\"></result>\n        <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n        <result column=\"gmt_modified\" property=\"gmtModified\"></result>\n    </resultMap>\n\n    <select id=\"getAdminSysNotice\" resultMap=\"map_AdminSysNoticeList\">\n        select\n        a.id as id,\n        a.title as title,\n        a.content as content,\n        a.type as type,\n        a.state as state,\n        a.gmt_create as gmt_create,\n        a.gmt_modified as gmt_modified,\n        u.username as username\n        from admin_sys_notice a, user_info u\n        <where>\n            a.admin_id = u.uuid\n            <if test=\"type!=null\">\n                and a.type = #{type}\n            </if>\n        </where>\n        order by a.state asc, a.gmt_create desc\n    </select>\n\n</mapper>"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/AnnouncementMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.AnnouncementMapper\">\n\n    <select id=\"getAnnouncementList\" resultMap=\"map_AnnouncementVO\" useCache=\"true\">\n        SELECT a.*,u.username FROM user_info u,announcement a where a.uid = u.uuid\n        and (SELECT COUNT(*) FROM contest_announcement ca WHERE ca.aid=a.id) = 0\n        <if test=\"notAdmin\">\n            and a.status = 0\n        </if>\n        order by a.gmt_create desc\n    </select>\n\n    <select id=\"getContestAnnouncement\" resultMap=\"map_AnnouncementVO\">\n        SELECT a.*,u.username FROM user_info u,announcement a,contest_announcement ca\n        where a.uid = u.uuid and ca.cid =#{cid} and ca.aid = a.id\n        <if test=\"notAdmin\">\n            and a.status = 0\n        </if>\n        order by a.gmt_create desc\n    </select>\n\n    <resultMap id=\"map_AnnouncementVO\" type=\"com.simplefanc.voj.backend.pojo.vo.AnnouncementVO\">\n        <id column=\"id\" property=\"id\"></id>\n        <result column=\"title\" property=\"title\"></result>\n        <result column=\"content\" property=\"content\"></result>\n        <result column=\"uid\" property=\"uid\"></result>\n        <result column=\"username\" property=\"username\"></result>\n        <result column=\"status\" property=\"status\"></result>\n        <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n        <result column=\"gmt_modified\" property=\"gmtModified\"></result>\n    </resultMap>\n\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/CommentMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.CommentMapper\">\n\n    <resultMap id=\"map_CommentList\" type=\"com.simplefanc.voj.backend.pojo.vo.CommentVO\">\n        <id column=\"id\" property=\"id\"></id>\n        <result column=\"content\" property=\"content\"></result>\n        <result column=\"from_uid\" property=\"fromUid\"></result>\n        <result column=\"from_name\" property=\"fromName\"></result>\n        <result column=\"from_avatar\" property=\"fromAvatar\"></result>\n        <result column=\"from_role\" property=\"fromRole\"></result>\n        <result column=\"like_num\" property=\"likeNum\"></result>\n        <result column=\"total_reply_num\" property=\"totalReplyNum\"></result>\n        <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n        <collection property=\"replyList\" ofType=\"com.simplefanc.voj.common.pojo.entity.discussion.Reply\"\n                    select=\"getCommentListReply\" column=\"id\">\n        </collection>\n    </resultMap>\n\n\n    <!-- 主查询  -->\n    <select id=\"getCommentList\" resultMap=\"map_CommentList\" resultType=\"list\">\n        SELECT c.*,(SELECT COUNT(1) FROM reply WHERE comment_id=c.id and status=0) as total_reply_num FROM comment c\n        <where>\n            c.status=0\n            <if test=\"cid!=null\">\n                AND c.cid=#{cid}\n            </if>\n            <if test=\"did!=null\">\n                AND c.did=#{did}\n            </if>\n\n            <if test=\"onlyMineAndAdmin\">\n                AND c.from_uid in\n                <foreach item=\"uid\" collection=\"myAndAdminUidList\" separator=\",\" open=\"(\" close=\")\" index=\"\">\n                    #{uid}\n                </foreach>\n            </if>\n\n        </where>\n        order by c.like_num desc,c.gmt_create desc\n    </select>\n\n    <!-- 子查询 -->\n    <select id=\"getCommentListReply\" resultType=\"com.simplefanc.voj.common.pojo.entity.discussion.Reply\">\n        select r.*\n        from reply r\n        where r.comment_id = #{id}\n          and r.status = 0\n        order by r.gmt_create desc LIMIT 3\n    </select>\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/ContestExplanationMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.ContestExplanationMapper\">\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/ContestMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.ContestMapper\">\n    <select id=\"getContestList\" resultType=\"com.simplefanc.voj.common.pojo.entity.contest.Contest\" useCache=\"true\">\n        select c.id,c.uid,c.author,c.title,c.type,c.description,c.source,c.auth,c.status,c.start_time,\n        c.end_time,c.duration,c.open_rank,c.oi_rank_score_type,c.contest_admin_visible,c.visible\n        from contest c\n        <where>\n            <if test=\"keyword!=null and keyword!=''\">\n                and c.title like concat('%',#{keyword},'%')\n            </if>\n            <if test=\"status!=null\">\n                and c.status = #{status}\n            </if>\n            <if test=\"type!=null\">\n                and c.type = #{type}\n            </if>\n        </where>\n        order by c.status ASC, c.start_time DESC\n    </select>\n\n    <select id=\"getContestRegisterCount\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ContestRegisterCountVO\">\n        SELECT cr.cid as cid,COUNT(*) as count FROM contest_register cr,contest c\n        <where>\n            cr.cid = c.id\n            AND c.id in\n            <foreach collection=\"cidList\" item=\"cid\" open=\"(\" separator=\",\" close=\")\">\n                #{cid}\n            </foreach>\n        </where>\n        GROUP BY cr.cid\n    </select>\n\n    <select id=\"getWithinNext14DaysContests\" resultType=\"com.simplefanc.voj.common.pojo.entity.contest.Contest\">\n        SELECT c.id,\n               c.uid,\n               c.author,\n               c.title,\n               c.type,\n               c.source,\n               c.auth,\n               c.status,\n               c.start_time,\n               c.end_time,\n               c.duration,\n               c.oi_rank_score_type,\n               c.contest_admin_visible,\n               c.visible\n        FROM contest c\n        WHERE DATE_ADD(CURDATE(), INTERVAL 14 DAY) >= DATE (start_time) AND c.status != 1\n        order by c.start_time DESC\n    </select>\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/ContestProblemMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.ContestProblemMapper\">\n    <select id=\"getContestProblemList\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ContestProblemVO\">\n        select cp.id,cp.display_id,cp.cid,cp.pid,cp.display_title,cp.color,\n        (select count(*) from judge\n        <where>\n            cpid=cp.id and cid=cp.cid and pid=cp.pid\n            <if test=\"adminList!=null and adminList.size>0\">\n                and uid NOT IN\n                <foreach collection=\"adminList\" index=\"index\" item=\"item\"\n                         open=\"(\" separator=\",\" close=\")\">\n                    #{item}\n                </foreach>\n            </if>\n            <choose>\n                <when test=\"isAdmin\">\n                    AND submit_time >= #{startTime} AND #{endTime}>submit_time\n                </when>\n                <when test=\"sealTime!=null\">\n                    AND submit_time >= #{startTime} AND #{sealTime}>submit_time\n                </when>\n                <otherwise>\n                    AND submit_time >= #{startTime} AND #{endTime}>submit_time\n                </otherwise>\n            </choose>\n        </where>\n        ) as total,\n        (select count(*) from judge\n        <where>\n            cpid=cp.id and cid=cp.cid and pid =cp.pid and status=0\n            <if test=\"adminList!=null and adminList.size>0\">\n                and uid NOT IN\n                <foreach collection=\"adminList\" index=\"index\" item=\"item\"\n                         open=\"(\" separator=\",\" close=\")\">\n                    #{item}\n                </foreach>\n            </if>\n            <choose>\n                <when test=\"isAdmin\">\n                    AND submit_time >= #{startTime} AND #{endTime}>=submit_time\n                </when>\n                <when test=\"sealTime!=null\">\n                    AND submit_time >= #{startTime} AND #{sealTime}>=submit_time\n                </when>\n                <otherwise>\n                    AND submit_time >= #{startTime} AND #{endTime}>=submit_time\n                </otherwise>\n            </choose>\n        </where>\n        ) as ac\n        from contest_problem cp,problem p\n        where cp.cid = #{cid} and cp.pid=p.id and p.auth!=2\n    </select>\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/ContestRecordMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.ContestRecordMapper\">\n\n    <select id=\"getACInfo\" resultType=\"com.simplefanc.voj.common.pojo.entity.contest.ContestRecord\">\n        SELECT c.id,c.uid,c.username,c.display_id,c.cid,u.realname,c.pid,c.time,c.status,c.checked,c.submit_id,\n        c.submit_time FROM contest_record c,user_info u,\n        (SELECT status,uid,pid,cpid,\n        MIN(submit_id) AS min_submit_id,\n        MIN(submit_time) AS min_submit_time\n        FROM contest_record GROUP BY status,uid,pid,cpid\n        ) AS t\n        <where>\n            t.status = c.status AND t.uid=c.uid AND t.pid=c.pid AND c.uid = u.uuid\n            AND t.cpid=c.cpid\n            AND t.min_submit_id=c.submit_id\n            AND t.min_submit_time=c.submit_time\n            <if test=\"status!=null\">\n                AND c.status=#{status}\n            </if>\n            <if test=\"cid!=null\">\n                AND c.cid = #{cid}\n            </if>\n        </where>\n        ORDER BY c.checked ASC,c.submit_time ASC\n    </select>\n\n    <select id=\"getOIContestRecordByRecentSubmission\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ContestRecordVO\">\n        SELECT cr.id,cr.cid,cr.uid,cr.pid,cr.cpid,cr.display_id,cr.submit_id,cr.username,cr.status,cr.submit_time,\n        cr.time,cr.score,cr.use_time,cr.checked,cr.gmt_create,cr.gmt_modified\n        FROM\n        (SELECT uid,pid,cpid,MAX(time) AS time FROM contest_record\n        <where>\n            cid=#{cid} AND status IS NOT NULL\n            <choose>\n                <when test=\"isOpenSealRank\">\n                    AND submit_time BETWEEN #{startTime} AND #{sealTime}\n                </when>\n                <otherwise>\n                    AND submit_time BETWEEN #{startTime} AND #{endTime}\n                </otherwise>\n            </choose>\n        </where>\n        GROUP BY uid,pid,cpid) t,\n        contest_record cr\n        WHERE t.uid = cr.uid AND t.pid =cr.pid AND t.cpid = cr.cpid AND t.time = cr.time\n    </select>\n\n    <select id=\"getOIContestRecordByHighestSubmission\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ContestRecordVO\">\n        SELECT cr.id,cr.cid,cr.uid,cr.pid,cr.cpid,cr.display_id,cr.submit_id,cr.username,cr.status,cr.submit_time,\n        cr.time,cr.score,cr.use_time,cr.checked,cr.gmt_create,cr.gmt_modified\n        FROM\n        (SELECT uid,pid,cpid,MAX(score) AS score FROM contest_record\n        <where>\n            cid=#{cid} AND status IS NOT NULL\n            <choose>\n                <when test=\"isOpenSealRank\">\n                    AND submit_time BETWEEN #{startTime} AND #{sealTime}\n                </when>\n                <otherwise>\n                    AND submit_time BETWEEN #{startTime} AND #{endTime}\n                </otherwise>\n            </choose>\n        </where>\n        GROUP BY uid,pid,cpid) t,\n        contest_record cr\n        WHERE t.uid = cr.uid AND t.pid =cr.pid AND t.cpid = cr.cpid AND t.score = cr.score\n    </select>\n\n\n    <select id=\"getACMContestRecord\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ContestRecordVO\">\n        SELECT cr.id,\n               cr.cid,\n               cr.uid,\n               cr.pid,\n               cr.cpid,\n               cr.display_id,\n               cr.submit_id,\n               cr.username,\n               cr.status,\n               cr.submit_time,\n               cr.time,\n               cr.score,\n               cr.use_time,\n               cr.checked,\n               cr.gmt_create,\n               cr.gmt_modified,\n               u.gender,\n               u.realname as realname,\n               u.avatar,\n               u.school,\n               u.nickname\n        FROM contest_record cr,\n             user_info u\n        WHERE cr.uid = u.uuid\n          AND cr.cid = #{cid}\n          AND cr.status IS NOT NULL\n          AND cr.submit_time >= #{startTime}\n        ORDER BY cr.time ASC\n    </select>\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/ContestRegisterMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.ContestRegisterMapper\">\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/ContestScoreMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.ContestScoreMapper\">\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/DiscussionMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.DiscussionMapper\">\n\n    <resultMap id=\"map_DiscussionVO\" type=\"com.simplefanc.voj.backend.pojo.vo.DiscussionVO\">\n        <id column=\"id\" property=\"id\"></id>\n        <result column=\"content\" property=\"content\"></result>\n        <result column=\"description\" property=\"description\"></result>\n        <result column=\"title\" property=\"title\"></result>\n        <result column=\"category_id\" property=\"categoryId\"></result>\n        <result column=\"category_name\" property=\"categoryName\"></result>\n        <result column=\"pid\" property=\"pid\"></result>\n        <result column=\"uid\" property=\"uid\"></result>\n        <result column=\"author\" property=\"author\"></result>\n        <result column=\"avatar\" property=\"avatar\"></result>\n        <result column=\"role\" property=\"role\"></result>\n        <result column=\"view_num\" property=\"viewNum\"></result>\n        <result column=\"like_num\" property=\"likeNum\"></result>\n        <result column=\"has_like\" property=\"hasLike\"></result>\n        <result column=\"top_priority\" property=\"topPriority\"></result>\n        <result column=\"status\" property=\"status\"></result>\n        <result column=\"gmt_modified\" property=\"gmtModified\"></result>\n        <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n    </resultMap>\n\n\n    <!-- 主查询  -->\n    <select id=\"getDiscussion\" resultMap=\"map_DiscussionVO\"\n            resultType=\"com.simplefanc.voj.backend.pojo.vo.DiscussionVO\">\n        SELECT d.*,c.id as category_id,c.name as category_name,\n        (SELECT 1 FROM discussion_like dl WHERE dl.uid = #{uid} AND dl.did = #{did} LIMIT 1) as has_like\n        FROM discussion d,category c\n        <where>\n            c.id = d.category_id\n            <if test=\"did!=null\">\n                and d.id=#{did}\n            </if>\n        </where>\n    </select>\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/JudgeMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.JudgeMapper\">\n    <select id=\"getCommonJudgeList\" resultType=\"com.simplefanc.voj.backend.pojo.vo.JudgeVO\" useCache=\"false\">\n        select j.uid,j.submit_id,j.submit_time,j.uid,j.username,j.uid,j.pid,j.status,j.share,\n        j.time,j.memory,j.score,j.oi_rank_score,j.length,j.language,j.cid,j.cpid,j.judger,p.problem_id as\n        display_pid,p.title\n        from judge j,problem p\n        <where>\n            p.id = j.pid AND j.cid = 0 AND j.cpid = 0 AND p.auth = 1\n            <if test=\"searchPid!=null\">\n                <if test=\"completeProblemId != true\">\n                    AND p.problem_id like concat('%',#{searchPid},'%')\n                </if>\n                <if test=\"completeProblemId\">\n                    AND p.problem_id = #{searchPid}\n                </if>\n            </if>\n            <if test=\"username!='' and username!=null\">\n                AND j.username like concat('%',#{username},'%')\n            </if>\n            <if test=\"status!=null\">\n                AND j.status = #{status}\n            </if>\n            <if test=\"uid!=null and uid!=''\">\n                AND j.uid = #{uid}\n            </if>\n        </where>\n        order by j.submit_time DESC,j.submit_id DESC\n    </select>\n\n\n    <select id=\"getContestJudgeList\" resultType=\"com.simplefanc.voj.backend.pojo.vo.JudgeVO\" useCache=\"false\">\n        select j.uid,j.submit_id,j.submit_time,j.username,u.realname,j.uid,cp.display_id,cp.display_title as title,\n        j.status,j.share,j.time,j.memory,j.score,j.length,j.language,j.cid,j.cpid,j.judger\n        from judge j, contest_problem cp, user_info u\n        <where>\n            j.pid = cp.pid AND j.cid = cp.cid AND u.uuid = j.uid\n            <if test=\"cid!=null\">\n                AND j.cid = #{cid}\n            </if>\n            <if test=\"displayId!=null and displayId!=''\">\n                <if test=\"completeProblemId != true\">\n                    AND cp.display_id like concat('%',#{displayId},'%')\n                </if>\n                <if test=\"completeProblemId\">\n                    AND cp.display_id = #{displayId}\n                </if>\n            </if>\n            <if test=\"username!='' and username!=null\">\n                AND (\n                j.username like concat('%',#{username},'%') OR\n                u.realname like concat('%',#{username},'%')\n                )\n            </if>\n            <if test=\"status!=null\">\n                AND j.status = #{status}\n            </if>\n            <if test=\"uid!=null and uid!=''\">\n                AND j.uid = #{uid}\n            </if>\n            <if test=\"beforeContestSubmit!=null and beforeContestSubmit==true\">\n                AND #{startTime} > j.submit_time\n            </if>\n            <if test=\"beforeContestSubmit!=null and beforeContestSubmit==false\">\n                AND j.submit_time >= #{startTime}\n            </if>\n            <if test=\"sealRankTime!=null\">\n                AND (#{sealRankTime} > j.submit_time OR j.uid=#{sealTimeUid})\n            </if>\n<!--            <choose>-->\n<!--                <when test=\"sealRankTime!=null and rule=='ACM'\">-->\n<!--                    AND (#{sealRankTime} > j.submit_time OR j.uid=#{sealTimeUid})-->\n<!--                </when>-->\n<!--                <when test=\"sealRankTime!=null and rule=='OI'\">-->\n<!--                    AND #{sealRankTime} > j.submit_time-->\n<!--                </when>-->\n<!--            </choose>-->\n        </where>\n        order by j.submit_time DESC,j.submit_id DESC\n    </select>\n\n    <select id=\"getTodayJudgeNum\" resultType=\"int\">\n        SELECT count(*)\n        FROM judge\n        WHERE DATE (gmt_create) = CURDATE();\n    </select>\n\n    <select id=\"getContestProblemCount\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ProblemCountVO\">\n        SELECT COUNT(IF(status=-3,status,NULL)) AS pe,\n        COUNT(IF(status=-2,status,NULL)) AS ce,\n        COUNT(IF(status=-1,status,NULL)) AS wa,\n        COUNT(IF(status=0,status,NULL)) AS ac,\n        COUNT(IF(status=1,status,NULL)) AS tle,\n        COUNT(IF(status=2,status,NULL)) AS mle,\n        COUNT(IF(status=3,status,NULL)) AS re,\n        COUNT(IF(status=4,status,NULL)) AS se,\n        COUNT(IF(status=8,status,NULL)) AS pa,\n        COUNT(*) AS total\n        FROM judge\n        <where>\n            pid=#{pid} and cpid = #{cpid} and cid = #{cid}\n            <if test=\"startTime!=null\">\n                and submit_time >= #{startTime}\n            </if>\n            <if test=\"sealRankTime!=null\">\n                and #{sealRankTime} > submit_time\n            </if>\n            <if test=\"adminList!=null and adminList.size>0\">\n                and uid NOT IN\n                <foreach collection=\"adminList\" index=\"index\" item=\"item\"\n                         open=\"(\" separator=\",\" close=\")\">\n                    #{item}\n                </foreach>\n            </if>\n        </where>\n    </select>\n\n    <select id=\"getProblemCount\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ProblemCountVO\">\n        SELECT pid,\n               COUNT(IF(status = -3, status, NULL)) AS pe,\n               COUNT(IF(status = -2, status, NULL)) AS ce,\n               COUNT(IF(status = -1, status, NULL)) AS wa,\n               COUNT(IF(status = 0, status, NULL))  AS ac,\n               COUNT(IF(status = 1, status, NULL))  AS tle,\n               COUNT(IF(status = 2, status, NULL))  AS mle,\n               COUNT(IF(status = 3, status, NULL))  AS re,\n               COUNT(IF(status = 4, status, NULL))  AS se,\n               COUNT(IF(status = 8, status, NULL))  AS pa,\n               COUNT(*)                             AS total\n        FROM judge\n        where pid = #{pid}\n          AND cid = 0\n    </select>\n\n    <select id=\"getProblemListCount\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ProblemCountVO\">\n        SELECT pid,\n        -- COUNT(IF(STATUS=-3,STATUS,NULL)) AS pe,\n        -- COUNT(IF(STATUS=-2,STATUS,NULL)) AS ce,\n        -- COUNT(IF(STATUS=-1,STATUS,NULL)) AS wa,\n        COUNT(IF(STATUS=0,STATUS,NULL)) AS ac,\n        -- COUNT(IF(STATUS=1,STATUS,NULL)) AS tle,\n        -- COUNT(IF(STATUS=2,STATUS,NULL)) AS mle,\n        -- COUNT(IF(STATUS=3,STATUS,NULL)) AS re,\n        -- COUNT(IF(STATUS=4,STATUS,NULL)) AS se,\n        -- COUNT(IF(STATUS=8,STATUS,NULL)) AS pa,\n        COUNT(*) AS total\n        FROM judge\n        <where>\n            cid=0\n            <if test=\"pidList!=null and pidList.size > 0\">\n                AND pid in\n                <foreach collection=\"pidList\" item=\"pid\" open=\"(\" separator=\",\" close=\")\">\n                    #{pid}\n                </foreach>\n            </if>\n        </where>\n        GROUP BY pid\n    </select>\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/MsgRemindMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.MsgRemindMapper\">\n\n    <select id=\"getUserUnreadMsgCount\" resultType=\"com.simplefanc.voj.backend.pojo.vo.UserUnreadMsgCountVO\"\n            useCache=\"true\">\n        SELECT (SELECT COUNT(1)\n                FROM msg_remind WHERE recipient_id = #{uid}\n                  AND state = 0\n                  AND `action` = 'Discuss') AS 'comment',\n                (SELECT COUNT(1) FROM msg_remind WHERE recipient_id = #{uid}\n                 AND state = 0 AND `action` = 'Reply') AS 'reply',\n                (SELECT COUNT(1) FROM msg_remind WHERE recipient_id = #{uid}\n                 AND state = 0 AND `action` LIKE 'Like%') AS 'like',\n                (SELECT COUNT(1) FROM user_sys_notice WHERE recipient_id = #{uid}\n                 AND state = 0 AND `type` = 'Sys') AS 'sys',\n                (SELECT COUNT(1) FROM user_sys_notice WHERE recipient_id = #{uid}\n                 AND state = 0 AND `type` = 'Mine') AS 'mine'\n    </select>\n\n\n    <resultMap id=\"map_UserMsgList\" type=\"com.simplefanc.voj.backend.pojo.vo.UserMsgVO\">\n        <id column=\"id\" property=\"id\"></id>\n        <result column=\"sender_id\" property=\"senderId\"></result>\n        <result column=\"action\" property=\"action\"></result>\n        <result column=\"source_type\" property=\"sourceType\"></result>\n        <result column=\"source_id\" property=\"sourceId\"></result>\n        <result column=\"source_content\" property=\"sourceContent\"></result>\n        <result column=\"quote_id\" property=\"quoteId\"></result>\n        <result column=\"quote_type\" property=\"quoteType\"></result>\n        <result column=\"url\" property=\"url\"></result>\n        <result column=\"state\" property=\"state\"></result>\n        <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n        <result column=\"username\" property=\"senderUsername\"></result>\n        <result column=\"avatar\" property=\"senderAvatar\"></result>\n    </resultMap>\n\n    <select id=\"getUserMsg\" resultMap=\"map_UserMsgList\">\n        select\n        m.id as id,\n        m.sender_id as sender_id,\n        m.action as 'action',\n        m.source_id as source_id,\n        m.source_type as source_type,\n        m.source_content as source_content,\n        m.quote_id as quote_id,\n        m.quote_type as quote_type,\n        m.url as url,\n        m.state as state,\n        m.gmt_create as gmt_create,\n        u.username as username,\n        u.avatar as avatar\n        from msg_remind m,user_info u\n        <where>\n            m.sender_id = u.uuid\n            and m.recipient_id = #{uid}\n            <choose>\n                <when test=\"action == 'Like'\">\n                    and (m.action = 'Like_Post' OR m.action = 'Like_Discuss')\n                </when>\n                <otherwise>\n                    and m.action = #{action}\n                </otherwise>\n            </choose>\n        </where>\n        order by m.state asc, m.gmt_create desc\n    </select>\n\n</mapper>"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/ProblemMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.ProblemMapper\">\n\n    <resultMap id=\"map_ProblemList\" type=\"com.simplefanc.voj.backend.pojo.vo.ProblemVO\">\n        <id column=\"pid\" property=\"pid\"></id>\n        <result column=\"problem_id\" property=\"problemId\"></result>\n        <result column=\"title\" property=\"title\"></result>\n        <result column=\"difficulty\" property=\"difficulty\"></result>\n        <result column=\"type\" property=\"type\"></result>\n        <collection property=\"tags\" ofType=\"com.simplefanc.voj.common.pojo.entity.problem.Tag\" select=\"getProblemTag\"\n                    column=\"pid\">\n        </collection>\n    </resultMap>\n\n    <!-- 主查询 -->\n    <select id=\"getProblemList\" resultMap=\"map_ProblemList\">\n        SELECT DISTINCT p.id AS pid, p.problem_id, p.title, p.difficulty, p.type\n        FROM problem p\n        <if test=\"tid != null and tid.size() > 0\">\n            INNER JOIN\n            (\n            SELECT pid FROM\n            problem_tag\n            <where>\n                <foreach collection=\"tid\" item=\"id\" open=\"\" separator=\" or\" close=\"\">\n                    tid = #{id}\n                </foreach>\n            </where>\n            GROUP BY pid\n            HAVING COUNT(pid) = #{tagListSize}\n            ) pt\n            ON p.id = pt.pid\n        </if>\n        <where>\n            <if test=\"!allProblemVisible\">\n                p.auth = 1\n            </if>\n            <if test=\"keyword != null and keyword != ''\">\n                and (\n                p.title like concat('%',#{keyword},'%') or p.problem_id like concat('%',#{keyword},'%')\n                )\n            </if>\n            <if test=\"difficulty != null\">\n                and p.difficulty = #{difficulty}\n            </if>\n            <if test=\"oj != null and oj !='LOCAL'\">\n                and p.problem_id like concat(#{oj},'%') and p.is_remote=true\n            </if>\n            <if test=\"oj != null and oj =='LOCAL'\">\n                and p.is_remote=false\n            </if>\n        </where>\n        order by length(p.problem_id) asc,p.problem_id asc\n    </select>\n\n    <!-- 子查询 :为了防止分页总数据数出错-->\n    <select id=\"getProblemTag\" resultType=\"com.simplefanc.voj.common.pojo.entity.problem.Tag\">\n        select t.*\n        from tag t,\n             problem_tag pt\n        where t.id = pt.tid\n          and pt.pid = #{pid}\n    </select>\n\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/RoleAuthMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.RoleAuthMapper\">\n    <resultMap id=\"map_RoleAuths\" type=\"com.simplefanc.voj.backend.pojo.vo.RoleAuthsVO\">\n        <id column=\"id\" property=\"id\"></id>\n        <result column=\"role\" property=\"role\"></result>\n        <result column=\"description\" property=\"description\"></result>\n        <result column=\"status\" property=\"status\"></result>\n        <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n        <result column=\"gmt_modified\" property=\"gmtModified\"></result>\n        <collection property=\"auths\" ofType=\"com.simplefanc.voj.common.pojo.entity.user.Auth\">\n            <id column=\"auth_id\" property=\"id\"></id>\n            <result column=\"name\" property=\"name\"></result>\n            <result column=\"permission\" property=\"permission\"></result>\n            <result column=\"auth_status\" property=\"status\"></result>\n            <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n            <result column=\"gmt_modified\" property=\"gmtModified\"></result>\n        </collection>\n    </resultMap>\n    <select id=\"getRoleAuths\" resultMap=\"map_RoleAuths\">\n        SELECT r.*, a.id as auth_id, a.name, a.permission, a.status as auth_status\n        FROM role r\n                 LEFT OUTER JOIN role_auth ra ON r.id = ra.role_id\n                 LEFT JOIN auth a ON ra.auth_id = a.id\n        WHERE r.id = #{rid}\n    </select>\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/RoleMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.RoleMapper\">\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/SessionMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.SessionMapper\">\n\n\n</mapper>"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/TagMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.TagMapper\">\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/TrainingCategoryMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.TrainingCategoryMapper\">\n    <select id=\"getTrainingCategoryByTrainingId\"\n            resultType=\"com.simplefanc.voj.common.pojo.entity.training.TrainingCategory\">\n        select tc.*\n        from mapping_training_category mtc,\n             training_category tc\n        where tc.id = mtc.cid\n          and mtc.tid = #{tid}\n    </select>\n</mapper>"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/TrainingMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.TrainingMapper\">\n\n    <resultMap id=\"map_TrainingList\" type=\"com.simplefanc.voj.backend.pojo.vo.TrainingVO\">\n        <id column=\"id\" property=\"id\"></id>\n        <result column=\"title\" property=\"title\"></result>\n        <result column=\"description\" property=\"description\"></result>\n        <result column=\"author\" property=\"author\"></result>\n        <result column=\"auth\" property=\"auth\"></result>\n        <result column=\"rank\" property=\"rank\"></result>\n        <result column=\"problem_count\" property=\"problemCount\"></result>\n        <result column=\"category_name\" property=\"categoryName\"></result>\n        <result column=\"category_color\" property=\"categoryColor\"></result>\n        <result column=\"gmt_modified\" property=\"gmtModified\"></result>\n    </resultMap>\n\n    <select id=\"getTrainingList\" resultMap=\"map_TrainingList\">\n        SELECT t.*,COALESCE(tp.problem_count,0) as problem_count FROM (\n        select t.*,tc.name as category_name,tc.color as category_color\n        from training t\n        left join mapping_training_category mtc\n        on t.id = mtc.tid\n        left join training_category tc\n        on mtc.cid = tc.id\n        <where>\n            t.status = true\n            <if test=\"categoryId != null\">\n                and tc.id = #{categoryId}\n            </if>\n            <if test=\"auth != null\">\n                and t.auth = #{auth}\n            </if>\n            <if test=\"keyword != null and keyword != ''\">\n                and (\n                t.title like concat('%',#{keyword},'%') or t.author like concat('%',#{keyword},'%')\n                )\n            </if>\n        </where>\n        ) t LEFT JOIN\n        (\n        SELECT tp.tid,COUNT(*) AS problem_count FROM training_problem tp,problem p\n        WHERE tp.pid = p.id AND p.auth = 1 GROUP BY tp.tid\n        ) tp\n        ON t.id = tp.tid ORDER BY t.`rank` ASC;\n    </select>\n</mapper>"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/TrainingProblemMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.TrainingProblemMapper\">\n\n\n    <select id=\"getTrainingProblemCount\" resultType=\"java.lang.Long\">\n        select p.id\n        from training_problem tp,\n             problem p\n        where tp.tid = #{tid}\n          and tp.pid = p.id\n          and p.auth = 1\n    </select>\n\n\n    <resultMap id=\"map_TrainingProblemList\" type=\"com.simplefanc.voj.backend.pojo.vo.ProblemVO\">\n        <id column=\"pid\" property=\"pid\"></id>\n        <result column=\"problem_id\" property=\"problemId\"></result>\n        <result column=\"title\" property=\"title\"></result>\n        <result column=\"difficulty\" property=\"difficulty\"></result>\n        <result column=\"type\" property=\"type\"></result>\n        <result column=\"total\" property=\"total\"></result>\n        <result column=\"ac\" property=\"ac\"></result>\n        <collection property=\"tags\" ofType=\"com.simplefanc.voj.common.pojo.entity.problem.Tag\" select=\"getProblemTag\"\n                    column=\"pid\">\n        </collection>\n    </resultMap>\n\n    <select id=\"getTrainingProblemList\" resultMap=\"map_TrainingProblemList\">\n        SELECT p.id                                                                             AS pid,\n               p.problem_id,\n               p.title,\n               p.difficulty,\n               p.type,\n               (SELECT COUNT(*) FROM judge j WHERE j.cid = 0 AND j.pid = p.id AND j.status = 0) as ac,\n               (SELECT COUNT(*) FROM judge j WHERE j.cid = 0 AND j.pid = p.id)                  as total\n        FROM problem p,\n             training_problem tp\n        where p.id = tp.pid\n          and p.auth = 1\n          and tp.tid = #{tid}\n        order by tp.`rank` asc\n    </select>\n\n    <!-- 子查询 :为了防止分页总数据数出错-->\n    <select id=\"getProblemTag\" resultType=\"com.simplefanc.voj.common.pojo.entity.problem.Tag\">\n        select t.*\n        from tag t,\n             problem_tag pt\n        where t.id = pt.tid\n          and pt.pid = #{pid}\n    </select>\n</mapper>"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/TrainingRecordMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.TrainingRecordMapper\">\n    <select id=\"getTrainingRecord\" resultType=\"com.simplefanc.voj.backend.pojo.vo.TrainingRecordVO\">\n        SELECT tr.tid,\n               tr.uid,\n               tr.pid,\n               tr.tpid,\n               tr.submit_id,\n               j.status,\n               j.score,\n               j.time     as use_time,\n               u.gender,\n               u.realname as realname,\n               u.username,\n               u.avatar,\n               u.school,\n               u.nickname\n        FROM training_record tr,\n             user_info u,\n             judge j\n        WHERE tr.uid = u.uuid\n          AND tr.submit_id = j.submit_id\n          AND tr.tid = #{tid}\n          AND j.status IN (-3, -2, -1, 0, 1, 2, 3, 8)\n        ORDER BY j.gmt_create ASC\n    </select>\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/UserAcproblemMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.UserAcproblemMapper\">\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/UserInfoMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.UserInfoMapper\">\n    <insert id=\"addUser\" parameterType=\"com.simplefanc.voj.backend.pojo.dto.RegisterDTO\">\n        insert into user_info(uuid, username, password, email, realname, school, `number`)\n        values (#{uuid}, #{username}, #{password}, #{email}, #{realname}, #{school}, #{number})\n    </insert>\n    <select id=\"getSuperAdminUidList\" resultType=\"java.lang.String\" useCache=\"true\">\n        select u.uuid\n        from user_info u,\n             user_role ur\n        where u.uuid = ur.uid\n          and ur.role_id = #{roleId}\n    </select>\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/UserRecordMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.UserRecordMapper\">\n    <select id=\"getACMRankList\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ACMRankVO\" useCache=\"true\">\n        SELECT u.uuid as uid,u.nickname,u.username,left(u.signature,100) as signature,u.avatar,\n        (SELECT COUNT(DISTINCT pid) FROM user_acproblem WHERE uid = u.uuid) AS ac,\n        (SELECT COUNT(uid) FROM judge WHERE uid=u.uuid AND cid=0) AS total\n        FROM user_info u\n        <where>\n            u.status = 0\n            <if test=\"uidList!=null\">\n                AND u.uuid in\n                <foreach collection=\"uidList\" item=\"uid\" open=\"(\" separator=\",\" close=\")\">\n                    #{uid}\n                </foreach>\n            </if>\n        </where>\n        ORDER BY ac DESC,total ASC\n    </select>\n\n    <select id=\"getRecent7ACRank\" resultType=\"com.simplefanc.voj.backend.pojo.vo.ACMRankVO\">\n        SELECT u.uuid as uid,\n               u.nickname,\n               u.username,\n               u.avatar,\n               (\n                   SELECT COUNT(DISTINCT pid) FROM user_acproblem WHERE uid = u.uuid\n--                    and DATE(gmt_create) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)\n               ) AS ac,\n               (\n                  SELECT COUNT(uid)\n                  FROM judge\n                  WHERE uid=u.uuid AND cid=0\n--                   and DATE (gmt_modified) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)\n              ) AS total\n        FROM user_info u\n        WHERE u.status = 0\n        ORDER BY ac DESC LIMIT 10\n    </select>\n\n\n    <select id=\"getOIRankList\" resultType=\"com.simplefanc.voj.backend.pojo.vo.OIRankVO\" useCache=\"true\">\n        SELECT ui.uuid AS uid, ui.nickname, ui.username, left(ui.signature,100) as signature, ui.avatar,\n        (SELECT COUNT(DISTINCT pid) FROM user_acproblem WHERE uid = ui.uuid) AS ac,\n        (SELECT COUNT(uid) FROM judge WHERE uid = ui.uuid) AS total,\n        ss.score AS score\n        FROM user_info ui,\n        (\n        SELECT u.uuid AS uid, COALESCE(SUM(s.score),0) AS score\n        FROM user_info u\n        left join (SELECT MAX(oi_rank_score) AS score, uid, pid FROM judge WHERE cid=0 GROUP BY pid, uid) s\n        ON s.uid = u.uuid\n        WHERE u.status = 0\n        GROUP BY u.uuid\n        ) ss\n        <where>\n            ss.uid = ui.uuid\n            <if test=\"uidList!=null\">\n                AND ui.uuid in\n                <foreach collection=\"uidList\" item=\"uid\" open=\"(\" separator=\",\" close=\")\">\n                    #{uid}\n                </foreach>\n            </if>\n        </where>\n        ORDER BY score DESC, ac DESC\n    </select>\n\n    <resultMap id=\"map_UserHomeVO\" type=\"com.simplefanc.voj.backend.pojo.vo.UserHomeVO\">\n        <id column=\"uid\" property=\"uid\"></id>\n        <result column=\"username\" property=\"username\"></result>\n        <result column=\"school\" property=\"school\"></result>\n        <result column=\"signature\" property=\"signature\"></result>\n        <result column=\"nickname\" property=\"nickname\"></result>\n        <result column=\"github\" property=\"github\"></result>\n        <result column=\"blog\" property=\"blog\"></result>\n        <result column=\"avatar\" property=\"avatar\"></result>\n        <result column=\"rating\" property=\"rating\"></result>\n        <result column=\"total\" property=\"total\"></result>\n        <collection property=\"scoreList\" ofType=\"java.lang.Integer\" select=\"getProblemScore\" column=\"uid\">\n        </collection>\n    </resultMap>\n\n    <select id=\"getUserHomeInfo\" resultMap=\"map_UserHomeVO\">\n        SELECT uuid as uid,username,nickname,gender,signature,school,github,blog,avatar,\n        (SELECT COUNT(uid) FROM judge WHERE uid=uuid AND cid=0) AS total\n        FROM user_info\n        <where>\n            status = 0\n            <if test=\"uid!=null\">\n                AND uuid = #{uid}\n            </if>\n            <if test=\"username!=null\">\n                AND username = #{username}\n            </if>\n        </where>\n    </select>\n\n    <!-- 子查询-->\n    <select id=\"getProblemScore\" resultType=\"java.lang.Integer\">\n        SELECT MAX(oi_rank_score) AS sum_score\n        FROM judge\n        WHERE uid = #{uid}\n          AND cid = 0\n          AND score IS NOT NULL\n        GROUP BY pid\n    </select>\n\n</mapper>\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/UserRoleMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.UserRoleMapper\">\n    <resultMap id=\"map_UserRoles\" type=\"com.simplefanc.voj.backend.pojo.vo.UserRolesVO\">\n        <id column=\"uuid\" property=\"uid\"></id>\n        <result column=\"username\" property=\"username\"></result>\n        <result column=\"password\" property=\"password\"></result>\n        <result column=\"nickname\" property=\"nickname\"></result>\n        <result column=\"school\" property=\"school\"></result>\n        <result column=\"course\" property=\"course\"></result>\n        <result column=\"number\" property=\"number\"></result>\n        <result column=\"gender\" property=\"gender\"></result>\n        <result column=\"realname\" property=\"realname\"></result>\n        <result column=\"cf_username\" property=\"cfUsername\"></result>\n        <result column=\"github\" property=\"github\"></result>\n        <result column=\"blog\" property=\"blog\"></result>\n        <result column=\"email\" property=\"email\"></result>\n        <result column=\"signature\" property=\"signature\"></result>\n        <result column=\"status\" property=\"status\"></result>\n        <result column=\"avatar\" property=\"avatar\"></result>\n        <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n        <result column=\"gmt_modified\" property=\"gmtModified\"></result>\n        <collection property=\"roles\" ofType=\"com.simplefanc.voj.common.pojo.entity.user.Role\">\n            <id column=\"role_id\" property=\"id\"></id>\n            <result column=\"role\" property=\"role\"></result>\n            <result column=\"description\" property=\"description\"></result>\n            <result column=\"role_status\" property=\"status\"></result>\n            <result column=\"role_gmt_create\" property=\"gmtCreate\"></result>\n            <result column=\"role_gmt_modified\" property=\"gmtModified\"></result>\n        </collection>\n    </resultMap>\n\n    <select id=\"getUserRoles\" resultMap=\"map_UserRoles\">\n        SELECT r.id as role_id,r.role as role,r.description as description,r.status as role_status, r.gmt_create as\n        role_gmt_create,\n        r.gmt_modified as role_gmt_modified,\n        u.* FROM user_info u\n        LEFT OUTER JOIN user_role ur ON u.uuid=ur.uid LEFT JOIN role r ON ur.role_id = r.id\n        <where>\n            <if test=\"uid != null and uid != ''\">\n                u.uuid = #{uid}\n            </if>\n            <if test=\"username != null and username != ''\">\n                and u.username = #{username}\n            </if>\n        </where>\n\n    </select>\n\n    <select id=\"getRolesByUid\" resultType=\"com.simplefanc.voj.common.pojo.entity.user.Role\">\n        select r.*\n        from role r,\n             user_role ur\n        where r.id = ur.role_id\n          and ur.uid = #{uid}\n    </select>\n\n\n    <resultMap id=\"map_UserRolesList\" type=\"com.simplefanc.voj.backend.pojo.vo.UserRolesVO\">\n        <id column=\"uuid\" property=\"uid\"></id>\n        <result column=\"username\" property=\"username\"></result>\n        <result column=\"password\" property=\"password\"></result>\n        <result column=\"nickname\" property=\"nickname\"></result>\n        <result column=\"school\" property=\"school\"></result>\n        <result column=\"course\" property=\"course\"></result>\n        <result column=\"number\" property=\"number\"></result>\n        <result column=\"realname\" property=\"realname\"></result>\n        <result column=\"cf_username\" property=\"cfUsername\"></result>\n        <result column=\"github\" property=\"github\"></result>\n        <result column=\"blog\" property=\"blog\"></result>\n        <result column=\"email\" property=\"email\"></result>\n        <result column=\"signature\" property=\"signature\"></result>\n        <result column=\"status\" property=\"status\"></result>\n        <result column=\"avatar\" property=\"avatar\"></result>\n        <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n        <result column=\"gmt_modified\" property=\"gmtModified\"></result>\n        <collection property=\"roles\" ofType=\"com.simplefanc.voj.common.pojo.entity.user.Role\" select=\"getUserListRoles\"\n                    column=\"uuid=uuid\">\n        </collection>\n    </resultMap>\n\n\n    <!-- 主查询  -->\n    <select id=\"getUserList\" resultMap=\"map_UserRolesList\" resultType=\"list\">\n        SELECT u.* FROM user_info u, user_role ur\n        <where>\n            ur.uid = u.uuid\n            <if test=\"keyword!=null and keyword!=''\">\n                and (u.username like CONCAT(\"%\",#{keyword},\"%\")\n                or u.realname like CONCAT(\"%\",#{keyword},\"%\")\n                or u.school like CONCAT(\"%\",#{keyword},\"%\")\n                or u.number like CONCAT(\"%\",#{keyword},\"%\"))\n            </if>\n            <if test=\"status != null\">\n                and u.status = #{status}\n            </if>\n            <if test=\"roleId != null\">\n                and ur.role_id = #{roleId}\n            </if>\n        </where>\n        order by u.gmt_create desc, u.uuid desc\n    </select>\n\n    <!-- 子查询 -->\n    <select id=\"getUserListRoles\" resultType=\"com.simplefanc.voj.common.pojo.entity.user.Role\">\n        select r.*\n        from role r,\n             user_role ur\n        where ur.uid = #{uuid}\n          and ur.role_id = r.id\n    </select>\n\n    <!-- 主查询 -->\n    <select id=\"getAdminUserList\" resultMap=\"map_UserRolesList\" resultType=\"list\">\n        SELECT u.* FROM user_info u,\n        (SELECT DISTINCT ur.uid AS uid FROM user_role ur\n            WHERE ur.role_id in\n            <foreach collection=\"roleIdList\" item=\"roleId\" open=\"(\" separator=\",\" close=\")\">\n                #{roleId}\n            </foreach>\n        ) t\n        <where>\n            t.uid = u.uuid\n            <if test=\"keyword!=null and keyword!=''\">\n                and (u.username like CONCAT(\"%\",#{keyword},\"%\")\n                or u.email like CONCAT(\"%\",#{keyword},\"%\")\n                or u.realname like CONCAT(\"%\",#{keyword},\"%\"))\n            </if>\n        </where>\n        order by u.gmt_create desc, u.uuid desc\n    </select>\n\n</mapper>"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/mapper/xml/UserSysNoticeMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.simplefanc.voj.backend.mapper.UserSysNoticeMapper\">\n\n    <resultMap id=\"map_SysMsgList\" type=\"com.simplefanc.voj.backend.pojo.vo.SysMsgVO\">\n        <id column=\"id\" property=\"id\"></id>\n        <result column=\"type\" property=\"type\"></result>\n        <result column=\"state\" property=\"state\"></result>\n        <result column=\"gmt_create\" property=\"gmtCreate\"></result>\n        <result column=\"title\" property=\"title\"></result>\n        <result column=\"content\" property=\"content\"></result>\n        <result column=\"admin_id\" property=\"adminId\"></result>\n    </resultMap>\n\n    <select id=\"getSysOrMineNotice\" resultMap=\"map_SysMsgList\">\n        select\n        u.id as id,\n        u.type as type,\n        u.state as state,\n        u.gmt_create as gmt_create,\n        a.title as title,\n        a.content as content,\n        a.admin_id as admin_id\n        from user_sys_notice u,admin_sys_notice a\n        <where>\n            u.sys_notice_id = a.id\n            <if test=\"uid!=null\">\n                and u.recipient_id = #{uid}\n            </if>\n            <if test=\"type!=null\">\n                and u.type = #{type}\n            </if>\n        </where>\n        order by u.state asc,u.gmt_create desc\n    </select>\n</mapper>"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/AdminEditUserDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:09\n * @Description:\n */\n@Data\npublic class AdminEditUserDTO {\n\n    @NotBlank(message = \"username不能为空\")\n    private String username;\n\n    @NotBlank(message = \"uid不能为空\")\n    private String uid;\n\n    private String realname;\n\n    private String school;\n\n    private String number;\n\n    private String email;\n\n    private String password;\n\n    private Integer type;\n\n    private Integer status;\n\n    private Boolean setNewPwd;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/AnnouncementDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/21 22:55\n * @Description:\n */\n@Data\npublic class AnnouncementDTO {\n\n    @NotBlank(message = \"比赛id不能为空\")\n    private Long cid;\n\n    private Announcement announcement;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ApplyResetPasswordDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 17:26\n * @Description:\n */\n@Data\npublic class ApplyResetPasswordDTO {\n\n    @NotBlank(message = \"邮箱或验证码不能为空\")\n    private String captcha;\n\n    @NotBlank(message = \"邮箱或验证码不能为空\")\n    private String captchaKey;\n\n    @NotBlank(message = \"邮箱或验证码不能为空\")\n    private String email;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ChangeEmailDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 18:05\n * @Description:\n */\n@Data\npublic class ChangeEmailDTO {\n\n    private String password;\n\n    private String newEmail;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ChangePasswordDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 18:06\n * @Description:\n */\n@Data\npublic class ChangePasswordDTO {\n\n    private String oldPassword;\n\n    private String newPassword;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/CheckAcDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/17 19:13\n * @Description:\n */\n@Data\npublic class CheckAcDTO {\n\n    @NotBlank(message = \"比赛记录id不能为空\")\n    private Long id;\n\n    @NotBlank(message = \"比赛id不能为空\")\n    private Long cid;\n\n    @NotBlank(message = \"是否确认不能为空\")\n    private Boolean checked;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/CheckUsernameOrEmailDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 18:15\n * @Description:\n */\n@Data\npublic class CheckUsernameOrEmailDTO {\n\n    private String email;\n\n    private String username;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ContestPrintDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2021/9/20 13:00\n * @Description:\n */\n@Data\npublic class ContestPrintDTO {\n\n    @NotBlank(message = \"比赛id不能为空\")\n    private Long cid;\n\n    @NotBlank(message = \"打印内容不能为空\")\n    private String content;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ContestProblemDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 11:10\n * @Description:\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class ContestProblemDTO {\n\n    @NotBlank(message = \"题目id不能为空\")\n    private Long pid;\n\n    @NotBlank(message = \"比赛id不能为空\")\n    private Long cid;\n\n//    @NotBlank(message = \"题目在比赛中的展示id不能为空\")\n//    private String displayId;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ContestRankDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/10 17:07\n * @Description:\n */\n@Data\npublic class ContestRankDTO {\n\n    /***\n     * @param cid 比赛id\n     * @param removeStar 是否移除打星队伍\n     * @param forceRefresh 是否强制实时榜单\n     * @param concernedList 关注的用户(uuid)列表\n     */\n    private Long cid;\n\n    private Integer limit;\n\n    private Integer currentPage;\n\n    private Boolean forceRefresh;\n\n    private Boolean removeStar;\n\n    private List<String> concernedList;\n\n    private String keyword;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/DbAndRedisConfigDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.Accessors;\n\n/**\n * @Author chenfan\n * @Date 2022/4/2 19:49\n * @Description\n */\n@Data\n@Accessors(chain = true)\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class DbAndRedisConfigDTO {\n\n    /**\n     * 数据库名称\n     */\n    private String dbName;\n\n    /**\n     * MySQL 主机\n     */\n    private String dbHost;\n\n    /**\n     * MySQL 端口\n     */\n    private Integer dbPort;\n\n    /**\n     * MySQL 用户名\n     */\n    private String dbUsername;\n\n    /**\n     * MySQL 密码\n     */\n    private String dbPassword;\n\n    /**\n     * Redis 主机\n     */\n    private String redisHost;\n\n    /**\n     * Redis 端口\n     */\n    private Integer redisPort;\n\n    /**\n     * Redis 密码\n     */\n    private String redisPassword;\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/EmailConfigDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.Accessors;\n\n/**\n * @Author chenfan\n * @Date 2022/4/2 19:40\n * @Description\n */\n@Data\n@Accessors(chain = true)\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class EmailConfigDTO {\n\n    /**\n     * SMTP 主机\n     */\n    private String emailHost;\n\n    /**\n     * SMTP 密码/授权码\n     */\n    private String emailPassword;\n\n    /**\n     * SMTP 端口\n     */\n    private Integer emailPort;\n\n    /**\n     * SMTP 邮箱\n     */\n    private String emailUsername;\n\n    /**\n     * 邮件背景图片\n     */\n    private String emailBGImg;\n\n    /**\n     * SMTP 使用 SSL\n     */\n    private Boolean emailSsl;\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/LoginDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Size;\nimport java.io.Serializable;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/20 00:23\n * @Description: 登录数据实体类\n */\n@Data\npublic class LoginDTO implements Serializable {\n\n    @NotBlank(message = \"用户名不能为空\")\n    @Size(max = 20, message = \"用户名长度不能超过20位!\")\n    private String username;\n\n    @NotBlank(message = \"密码不能为空\")\n    @Size(min = 6, max = 20, message = \"密码长度应该为6~20位！\")\n    private String password;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/PidListDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/28 22:33\n * @Description: 主要是获取前端题目列表页查询用户对应题目提交详情\n */\n@Data\n@Accessors(chain = true)\npublic class PidListDTO {\n\n    @NotEmpty(message = \"查询的题目id列表不能为空\")\n    private List<Long> pidList;\n\n    @NotNull(message = \"是否为比赛题目提交判断不能为空\")\n    private Boolean isContestProblemList;\n\n    private Long cid;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ProblemDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport com.simplefanc.voj.common.pojo.entity.problem.*;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/14 22:30\n * @Description:\n */\n@Data\n@Accessors(chain = true)\npublic class ProblemDTO {\n\n    private Problem problem;\n\n    private List<ProblemCase> samples;\n\n    private Boolean isUploadTestCase;\n\n    private String uploadTestcaseDir;\n\n    private String judgeMode;\n\n    private Boolean changeModeCode;\n\n    private List<Language> languages;\n\n    private List<Tag> tags;\n\n    private List<CodeTemplate> codeTemplates;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/QDOJProblemDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport com.simplefanc.voj.common.pojo.entity.problem.CodeTemplate;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/30 15:09\n * @Description:\n */\n@ToString\n@Accessors(chain = true)\npublic class QDOJProblemDTO implements Serializable {\n\n    private Problem problem;\n\n    private List<String> languages;\n\n    private List<ProblemCase> samples;\n\n    private List<String> tags;\n\n    private List<CodeTemplate> codeTemplates;\n\n    private Boolean isSpj;\n\n    public Problem getProblem() {\n        return problem;\n    }\n\n    public void setProblem(Problem problem) {\n        this.problem = problem;\n    }\n\n    public List<String> getLanguages() {\n        return languages;\n    }\n\n    public void setLanguages(List<String> languages) {\n        this.languages = languages;\n    }\n\n    public List<ProblemCase> getSamples() {\n        return samples;\n    }\n\n    public void setSamples(List<ProblemCase> samples) {\n        this.samples = samples;\n    }\n\n    public List<String> getTags() {\n        return tags;\n    }\n\n    public void setTags(List<String> tags) {\n        this.tags = tags;\n    }\n\n    public List<CodeTemplate> getCodeTemplates() {\n        return codeTemplates;\n    }\n\n    public void setCodeTemplates(List<CodeTemplate> codeTemplates) {\n        this.codeTemplates = codeTemplates;\n    }\n\n    public Boolean getIsSpj() {\n        return isSpj;\n    }\n\n    public void setIsSpj(Boolean spj) {\n        isSpj = spj;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/RegisterContestDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/12 14:37\n * @Description:\n */\n@Data\npublic class RegisterContestDTO {\n\n    @NotBlank(message = \"cid不能为空\")\n    private Long cid;\n\n    @NotBlank(message = \"password不能为空\")\n    private String password;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/RegisterDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\nimport lombok.experimental.Accessors;\nimport org.springframework.lang.Nullable;\n\nimport javax.validation.constraints.Email;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Pattern;\nimport javax.validation.constraints.Size;\nimport java.io.Serializable;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/24 11:15\n * @Description: 注册数据实体类\n */\n@Data\n@Accessors(chain = true)\npublic class RegisterDTO implements Serializable {\n\n    @Nullable\n    private String uuid;\n\n    @NotBlank(message = \"用户名不能为空\")\n    @Size(max = 20, message = \"用户名长度不能超过20位!\")\n    private String username;\n\n    @NotBlank(message = \"真实姓名不能为空\")\n    private String realname;\n\n    @NotBlank(message = \"学校不能为空\")\n    private String school;\n\n    @NotBlank(message = \"学号不能为空\")\n    @Pattern(regexp = \"^[0-9A-Za-z]{6,18}$\", message = \"学号格式错误\")\n    private String number;\n\n    @NotBlank(message = \"密码不能为空\")\n    @Size(min = 6, max = 20, message = \"密码长度应该为6~20位！\")\n    private String password;\n\n    @NotBlank(message = \"邮箱不能为空\")\n    @Email(message = \"邮箱格式错误\")\n    private String email;\n\n    @NotBlank(message = \"验证码不能为空\")\n    private String code;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/RegisterTrainingDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 17:55\n * @Description:\n */\n@Data\npublic class RegisterTrainingDTO {\n\n    @NotBlank(message = \"tid不能为空\")\n    private Long tid;\n\n    @NotBlank(message = \"password不能为空\")\n    private String password;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ReplyDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\n/**\n * @Author: chenfan\n * @Date: 2021/6/24 17:00\n * @Description:\n */\n@Data\n@Accessors(chain = true)\npublic class ReplyDTO {\n\n    private Reply reply;\n\n    private Integer did;\n\n    private Integer quoteId;\n\n    private String quoteType;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ResetPasswordDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Size;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 17:32\n * @Description:\n */\n\n@Data\npublic class ResetPasswordDTO {\n    @NotBlank(message = \"用户名不能为空\")\n    private String username;\n\n    @Size(min = 6, max = 20, message = \"新密码长度应该为6~20位！\")\n    @NotBlank(message = \"新密码不能为空\")\n    private String password;\n\n    @NotBlank(message = \"验证码不能为空\")\n    private String code;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/SubmitIdListDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotEmpty;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/3 16:50\n * @Description:\n */\n@Data\npublic class SubmitIdListDTO {\n\n    @NotEmpty(message = \"查询的提交id列表不能为空\")\n    private List<Long> submitIds;\n\n    private Long cid;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/SwitchConfigDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.Accessors;\n\n\n/**\n * @Author chenfan\n * @Date 2022/9/20\n */\n@Data\n@Accessors(chain = true)\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class SwitchConfigDTO {\n\n    /**\n     * 是否开启公开评论区\n     */\n//    private Boolean openPublicDiscussion;\n\n    /**\n     * 是否开启比赛讨论区\n     */\n//    private Boolean openContestComment;\n\n    /**\n     * 是否开启公开评测\n     */\n    private Boolean openPublicJudge;\n\n    /**\n     * 是否开启比赛评测\n     */\n    private Boolean openContestJudge;\n\n    /**\n     * 非比赛的提交间隔秒数\n     */\n    private Integer defaultSubmitInterval;\n\n    private Long codeVisibleStartTime;\n\n    /**\n     * 是否允许注册\n     */\n    private Boolean register;\n\n    private Boolean problem;\n\n    private Boolean training;\n\n    private Boolean contest;\n\n    private Boolean status;\n\n    private Boolean rank;\n\n    private Boolean discussion;\n\n    private Boolean introduction;\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/TestEmailDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/4/7 11:02\n * @Description:\n */\n@Data\npublic class TestEmailDTO {\n\n    /**\n     * 发送的测试邮箱\n     */\n    private String email;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/ToJudgeDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/30 11:03\n * @Description: 用户代码提交类\n */\n@Data\n@Accessors(chain = true)\npublic class ToJudgeDTO {\n\n    @NotBlank(message = \"题目id不能为空\")\n    private String pid;\n\n    @NotBlank(message = \"代码语言选择不能为空\")\n    private String language;\n\n    @NotBlank(message = \"提交的代码不能为空\")\n    private String code;\n\n    @NotBlank(message = \"提交的比赛id所属不能为空，若并非比赛提交，请设置为0\")\n    private Long cid;\n\n    private Long tid;\n\n    private Boolean isRemote;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/TrainingDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/22 21:49\n * @Description: 后台管理训练的传输类\n */\n@Data\n@Accessors(chain = true)\npublic class TrainingDTO {\n\n    private Training training;\n\n    private TrainingCategory trainingCategory;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/TrainingProblemDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport javax.validation.constraints.NotBlank;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 11:10\n * @Description:\n */\n@Data\npublic class TrainingProblemDTO {\n\n    @NotBlank(message = \"题目id不能为空\")\n    private Long pid;\n\n    @NotBlank(message = \"训练id不能为空\")\n    private Long tid;\n\n    @NotBlank(message = \"题目在比赛中的展示id不能为空\")\n    private String displayId;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/UserReadContestAnnouncementDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.Data;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/17 15:31\n * @Description:\n */\n\n@Data\npublic class UserReadContestAnnouncementDTO {\n\n    private Long cid;\n\n    private List<Long> readAnnouncementList;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/dto/WebConfigDTO.java",
    "content": "package com.simplefanc.voj.backend.pojo.dto;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.Accessors;\n\n/**\n * @Author chenfan\n * @Date 2022/4/2 19:44\n * @Description\n */\n@Data\n@Accessors(chain = true)\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class WebConfigDTO {\n\n    /**\n     * 基础 URL\n     */\n    private String baseUrl;\n\n    /**\n     * 网站名称\n     */\n    private String name;\n\n    /**\n     * 网站简称\n     */\n    private String shortName;\n\n    /**\n     * 网站简介\n     */\n    private String description;\n\n    /**\n     * 备案名\n     */\n    private String recordName;\n\n    /**\n     * 备案地址\n     */\n    private String recordUrl;\n\n    /**\n     * 项目名\n     */\n    private String projectName;\n\n    /**\n     * 项目地址\n     */\n    private String projectUrl;\n\n    private Boolean register;\n\n    private Boolean problem;\n\n    private Boolean training;\n\n    private Boolean contest;\n\n    private Boolean status;\n\n    private Boolean rank;\n\n    private Boolean discussion;\n\n    private Boolean introduction;\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ACMContestRankVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/18 14:55\n * @Description:\n */\n@Data\n@Accessors(chain = true)\npublic class ACMContestRankVO {\n    @ApiModelProperty(value = \"序号\")\n    private Integer seq;\n\n    @ApiModelProperty(value = \"排名,排名为-1则为打星队伍\")\n    private Integer rank;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"用户真实姓名\")\n    private String realname;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"学校\")\n    private String school;\n\n    @ApiModelProperty(value = \"性别\")\n    private String gender;\n\n    @ApiModelProperty(value = \"头像\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"提交总罚时\")\n    private Long totalTime;\n\n    @ApiModelProperty(value = \"总提交数\")\n    private Integer total;\n\n    @ApiModelProperty(value = \"ac题目数\")\n    private Integer ac;\n\n    @ApiModelProperty(value = \"有提交的题的提交详情\")\n    private HashMap<String, HashMap<String, Object>> submissionInfo;\n\n    @ApiModelProperty(value = \"是否已注册\")\n    private Boolean registered;\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ACMRankVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/27 21:09\n * @Description:\n */\n@ApiModel(value = \"ACM排行榜数据类ACMRankVO\", description = \"\")\n@Data\npublic class ACMRankVO implements Serializable {\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"个性签名\")\n    private String signature;\n\n    @ApiModelProperty(value = \"头像地址\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"总提交数\")\n    private Integer total;\n\n    @ApiModelProperty(value = \"总通过数\")\n    private Integer ac;\n\n    @ApiModelProperty(value = \"cf得分\")\n    private Integer rating;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/AccessVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 17:51\n * @Description:\n */\n@Data\npublic class AccessVO {\n\n    @ApiModelProperty(value = \"是否有进入比赛或训练的权限\")\n    private Boolean access;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/AdminContestVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/7 19:45\n * @Description:\n */\n@ApiModel(value = \"管理比赛的回传实体\", description = \"\")\n@Data\npublic class AdminContestVO {\n\n    @ApiModelProperty(value = \"比赛id\")\n    private Long id;\n\n    @ApiModelProperty(value = \"比赛创建者id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"比赛创建者的用户名\")\n    private String author;\n\n    @ApiModelProperty(value = \"比赛标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"0为acm赛制，1为比分赛制\")\n    private Integer type;\n\n    @ApiModelProperty(value = \"比赛说明\")\n    private String description;\n\n    @ApiModelProperty(value = \"比赛来源，原创为0，克隆赛为比赛id\")\n    private Integer source;\n\n    @ApiModelProperty(value = \"0为公开赛，1为私有赛（访问有密码），2为保护赛（提交有密码）\")\n    private Integer auth;\n\n    @ApiModelProperty(value = \"是否打开密码限制\")\n    private Boolean openPwdLimit;\n\n    @ApiModelProperty(value = \"比赛密码\")\n    private String pwd;\n\n    @ApiModelProperty(value = \"开始时间\")\n    private Date startTime;\n\n    @ApiModelProperty(value = \"结束时间\")\n    private Date endTime;\n\n    @ApiModelProperty(value = \"比赛时长（s）\")\n    private Long duration;\n\n    @ApiModelProperty(value = \"是否开启封榜\")\n    private Boolean sealRank;\n\n    @ApiModelProperty(value = \"封榜起始时间，一直到比赛结束，不刷新榜单\")\n    private Date sealRankTime;\n\n    @ApiModelProperty(value = \"比赛结束是否自动解除封榜,自动转换成真实榜单\")\n    private Boolean autoRealRank;\n\n    @ApiModelProperty(value = \"比赛管理员是否参与排名\")\n    private Boolean contestAdminRank;\n\n    @ApiModelProperty(value = \"-1为未开始，0为进行中，1为已结束\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"是否可见\")\n    private Boolean visible;\n\n    @ApiModelProperty(value = \"是否仅对比赛管理员可见\")\n    private Boolean contestAdminVisible;\n\n    @ApiModelProperty(value = \"是否打开打印功能\")\n    private Boolean openPrint;\n\n    @ApiModelProperty(value = \"是否打开账号限制\")\n    private Boolean openAccountLimit;\n\n    @ApiModelProperty(\n            value = \"账号限制规则 <prefix>**</prefix><suffix>**</suffix><start>**</start><end>**</end><extra>**</extra>\")\n    private String accountLimitRule;\n\n    @ApiModelProperty(value = \"排行榜显示（username、nickname、realname）\")\n    private String rankShowName;\n\n    @ApiModelProperty(value = \"打星用户列表\")\n    private List<String> starAccount;\n\n    @ApiModelProperty(value = \"是否开放比赛榜单\")\n    private Boolean openRank;\n\n    @ApiModelProperty(value = \"oi排行榜得分方式，Recent、Highest（最近一次提交、最高得分提交）\")\n    private String oiRankScoreType;\n\n    private Date gmtCreate;\n\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/AdminSysNoticeVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/4 14:03\n * @Description:\n */\n@Data\n@ApiModel(value = \"系统通知消息\", description = \"\")\npublic class AdminSysNoticeVO {\n\n    private Long id;\n\n    @ApiModelProperty(value = \"通知标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"通知内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"发给哪些用户类型,例如全部用户All，指定单个用户Single，管理员Admin\")\n    private String type;\n\n    @ApiModelProperty(value = \"是否已被拉取过，如果已经拉取过，就无需再次拉取\")\n    private Boolean state;\n\n    @ApiModelProperty(value = \"发布通知的管理员用户名\")\n    private String adminUsername;\n\n    private Date gmtCreate;\n\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/AnnouncementVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/10 20:31\n * @Description:\n */\n@ApiModel(value = \"公告数据\", description = \"\")\n@Data\npublic class AnnouncementVO {\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"通知标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"通知内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"发布者（必须为比赛创建者或者超级管理员才能）\")\n    private String uid;\n\n    @ApiModelProperty(value = \"发布者的用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"0可见，1不可见\")\n    private Integer status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/CaptchaVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 16:37\n * @Description:\n */\n@Data\npublic class CaptchaVO {\n\n    @ApiModelProperty(value = \"验证码图片的base64\")\n    private String img;\n\n    @ApiModelProperty(value = \"验证码key\")\n    private String captchaKey;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ChangeAccountVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 17:58\n * @Description:\n */\n@Data\npublic class ChangeAccountVO {\n\n    private Integer code;\n\n    private String msg;\n\n    private UserInfoVO userInfo;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/CheckUsernameOrEmailVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 18:15\n * @Description:\n */\n@Data\npublic class CheckUsernameOrEmailVO {\n\n    private Boolean email;\n\n    private Boolean username;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/CommentListVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport lombok.Data;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 16:12\n * @Description:\n */\n@Data\npublic class CommentListVO {\n\n    private IPage<CommentVO> commentList;\n\n    private HashMap<Integer, Boolean> commentLikeMap;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/CommentVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/5 22:30\n * @Description:\n */\n@ApiModel(value = \"评论数据列表VO\", description = \"\")\n@Data\npublic class CommentVO {\n\n    private static final long serialVersionUID = 1L;\n\n    @ApiModelProperty(value = \"评论id\")\n    private Integer id;\n\n    @ApiModelProperty(value = \"评论内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"评论者id\")\n    private String fromUid;\n\n    @ApiModelProperty(value = \"评论者用户名\")\n    private String fromName;\n\n    @ApiModelProperty(value = \"评论组头像地址\")\n    private String fromAvatar;\n\n    @ApiModelProperty(value = \"评论者角色\")\n    private String fromRole;\n\n    @ApiModelProperty(value = \"点赞数量\")\n    private Integer likeNum;\n\n    @ApiModelProperty(value = \"该评论的总回复数\")\n    private Integer totalReplyNum;\n\n    private Date gmtCreate;\n\n    @ApiModelProperty(value = \"该评论回复列表\")\n    private List<Reply> replyList;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ContestOutsideInfo.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/8 12:32\n * @Description:\n */\n@ApiModel(value = \"赛外排行榜所需的比赛信息，同时包括题目题号、气球颜色\", description = \"\")\n@Data\npublic class ContestOutsideInfo {\n\n    @ApiModelProperty(value = \"比赛信息\")\n    private ContestVO contest;\n\n    @ApiModelProperty(value = \"比赛题目信息列表\")\n    private List<ContestProblem> problemList;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ContestProblemVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/16 16:07\n * @Description:\n */\n@ApiModel(value = \"比赛题目列表格式数据ContestProblemVO\", description = \"\")\n@Data\npublic class ContestProblemVO implements Serializable, Comparable<ContestProblemVO>  {\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"该题目在比赛中的顺序id\")\n    private String displayId;\n\n    @ApiModelProperty(value = \"比赛id\")\n    private Long cid;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"该题目在比赛中的标题，默认为原名字\")\n    private String displayTitle;\n\n    @ApiModelProperty(value = \"该题目在比赛中的气球颜色\")\n    private String color;\n\n    @ApiModelProperty(value = \"该题目的ac通过数\")\n    private Integer ac;\n\n    @ApiModelProperty(value = \"该题目的总提交数\")\n    private Integer total;\n\n    @Override\n    public int compareTo(ContestProblemVO cp) {\n        if (this.displayId.length() == cp.displayId.length()) {\n            return this.displayId.compareTo(cp.getDisplayId());\n        }\n        return Integer.compare(this.displayId.length(), cp.displayId.length());\n    }\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ContestRecordVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/28 12:05\n * @Description:\n */\n@ApiModel(value = \"用户在比赛的记录\", description = \"\")\n@Data\npublic class ContestRecordVO implements Serializable {\n\n    private Long id;\n\n    @ApiModelProperty(value = \"比赛id\")\n    private Long cid;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"比赛中的题目id\")\n    private Long cpid;\n\n    @ApiModelProperty(value = \"比赛中展示的id\")\n    private String displayId;\n\n    @ApiModelProperty(value = \"提交id，用于可重判\")\n    private Long submitId;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"学校\")\n    private String school;\n\n    @ApiModelProperty(value = \"性别\")\n    private String gender;\n\n    @ApiModelProperty(value = \"头像\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"真实姓名\")\n    private String realname;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"提交结果，0表示未AC通过不罚时，1表示AC通过，-1为未AC通过算罚时\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"具体提交时间\")\n    private Date submitTime;\n\n    @ApiModelProperty(value = \"提交时间，为提交时间减去比赛时间\")\n    private Long time;\n\n    @ApiModelProperty(value = \"OI比赛的得分\")\n    private Integer score;\n\n    @ApiModelProperty(value = \"提交耗时\")\n    private Integer useTime;\n\n    @ApiModelProperty(value = \"AC是否已校验\")\n    private Boolean checked;\n\n    private Date gmtCreate;\n\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ContestRegisterCountVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * @Author: chenfan\n * @Date: 2022/2/7 20:28\n * @Description:\n */\n@Data\n@ApiModel(value = \"比赛报名统计\", description = \"\")\npublic class ContestRegisterCountVO implements Serializable {\n\n    @ApiModelProperty(value = \"比赛id\")\n    private Long cid;\n\n    @ApiModelProperty(value = \"比赛报名人数\")\n    private Integer count;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ContestVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/27 21:53\n * @Description:\n */\n@ApiModel(value = \"比赛信息\", description = \"\")\n@Data\npublic class ContestVO implements Serializable {\n\n    @TableId(value = \"比赛id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"创建者用户名\")\n    private String author;\n\n    @ApiModelProperty(value = \"比赛标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"0为acm赛制，1为比分赛制\")\n    private Integer type;\n\n    @ApiModelProperty(value = \"比赛说明\")\n    private String description;\n\n    @ApiModelProperty(value = \"-1为未开始，0为进行中，1为已结束\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"比赛来源，原创为0，克隆赛为比赛id\")\n    private Integer source;\n\n    @ApiModelProperty(value = \"0为公开赛，1为私有赛（有密码），2为保护赛\")\n    private Integer auth;\n\n    @ApiModelProperty(\"当前服务器系统时间，为了前端统一时间\")\n    private Date now;\n\n    @ApiModelProperty(value = \"开始时间\")\n    private Date startTime;\n\n    @ApiModelProperty(value = \"结束时间\")\n    private Date endTime;\n\n    @ApiModelProperty(value = \"比赛时长（秒）\")\n    private Integer duration;\n\n    @ApiModelProperty(value = \"是否开启封榜\")\n    private Boolean sealRank;\n\n    @ApiModelProperty(value = \"是否打开打印功能\")\n    private Boolean openPrint;\n\n    @ApiModelProperty(value = \"封榜起始时间，一直到比赛结束，不刷新榜单\")\n    private Date sealRankTime;\n\n    @ApiModelProperty(value = \"排行榜显示（username、nickname、realname）\")\n    private String rankShowName;\n\n    @ApiModelProperty(value = \"是否开放比赛榜单\")\n    private Boolean openRank;\n\n    @ApiModelProperty(value = \"oi排行榜得分方式，Recent、Highest（最近一次提交、最高得分提交）\")\n    private String oiRankScoreType;\n\n    @ApiModelProperty(value = \"比赛的报名人数\")\n    private Integer count;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/DiscussionVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableLogic;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/7 10:06\n * @Description:\n */\n@Data\n@ApiModel(value = \"讨论数据VO\", description = \"\")\npublic class DiscussionVO {\n\n    private Integer id;\n\n    @ApiModelProperty(value = \"分类id\")\n    private Integer categoryId;\n\n    @ApiModelProperty(value = \"分类名字\")\n    private String categoryName;\n\n    @ApiModelProperty(value = \"讨论标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"讨论简介\")\n    private String description;\n\n    @ApiModelProperty(value = \"讨论内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"题目关联 默认为null则不关联题目\")\n    private String pid;\n\n    @ApiModelProperty(value = \"发表者id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"发表者用户名\")\n    private String author;\n\n    @ApiModelProperty(value = \"发表者头像地址\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"发表者角色\")\n    private String role;\n\n    @ApiModelProperty(value = \"浏览数量\")\n    private Integer viewNum;\n\n    @ApiModelProperty(value = \"点赞数量\")\n    private Integer likeNum;\n\n    @ApiModelProperty(value = \"如果有登录的话，是否点赞了\")\n    private Boolean hasLike;\n\n    @ApiModelProperty(value = \"优先级，是否置顶\")\n    private Boolean topPriority;\n\n    @ApiModelProperty(value = \"是否封禁 0正常，1封禁\")\n    @TableLogic\n    private Integer status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ExcelIpVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport com.alibaba.excel.annotation.write.style.ColumnWidth;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/10 16:33\n * @Description:\n */\n@Data\n@Accessors(chain = true)\n@ColumnWidth(25)\n@AllArgsConstructor\npublic class ExcelIpVO {\n\n    @ExcelProperty(value = \"用户名\", index = 0)\n    private String username;\n\n    @ExcelProperty(value = \"IP\", index = 1)\n    private String ip;\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ExcelUserVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport com.alibaba.excel.annotation.write.style.ColumnWidth;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/10 16:33\n * @Description:\n */\n@Data\n@Accessors(chain = true)\n@ColumnWidth(25)\npublic class ExcelUserVO {\n\n    @ExcelProperty(value = \"用户名\", index = 0)\n    private String username;\n\n    @ExcelProperty(value = \"密码\", index = 1)\n    private String password;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ImportProblemVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/27 15:21\n * @Description:\n */\n@ToString\n@Accessors(chain = true)\npublic class ImportProblemVO implements Serializable {\n\n    // TODO\n    private HashMap<String, Object> problem;\n\n    private List<String> languages;\n\n    // TODO\n    private List<HashMap<String, Object>> samples;\n\n    private List<String> tags;\n\n    private List<HashMap<String, String>> codeTemplates;\n\n    private HashMap<String, String> userExtraFile;\n\n    private HashMap<String, String> judgeExtraFile;\n\n    private String judgeMode;\n\n    public Map<String, Object> getProblem() {\n        return problem;\n    }\n\n    public void setProblem(HashMap<String, Object> problem) {\n        this.problem = problem;\n    }\n\n    public List<String> getLanguages() {\n        return languages;\n    }\n\n    public void setLanguages(List<String> languages) {\n        this.languages = languages;\n    }\n\n    public List<HashMap<String, Object>> getSamples() {\n        return samples;\n    }\n\n    public void setSamples(List<HashMap<String, Object>> samples) {\n        this.samples = samples;\n    }\n\n    public List<String> getTags() {\n        return tags;\n    }\n\n    public void setTags(List<String> tags) {\n        this.tags = tags;\n    }\n\n    public List<HashMap<String, String>> getCodeTemplates() {\n        return codeTemplates;\n    }\n\n    public void setCodeTemplates(List<HashMap<String, String>> codeTemplates) {\n        this.codeTemplates = codeTemplates;\n    }\n\n    public String getJudgeMode() {\n        return judgeMode;\n    }\n\n    public void setJudgeMode(String judgeMode) {\n        this.judgeMode = judgeMode;\n    }\n\n    public HashMap<String, String> getUserExtraFile() {\n        return userExtraFile;\n    }\n\n    public void setUserExtraFile(HashMap<String, String> userExtraFile) {\n        this.userExtraFile = userExtraFile;\n    }\n\n    public HashMap<String, String> getJudgeExtraFile() {\n        return judgeExtraFile;\n    }\n\n    public void setJudgeExtraFile(HashMap<String, String> judgeExtraFile) {\n        this.judgeExtraFile = judgeExtraFile;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/JudgeVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/29 13:08\n * @Description:\n */\n@Data\n@ApiModel(value = \"返回的判题信息\", description = \"\")\npublic class JudgeVO {\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"提交id\")\n    @TableId(value = \"submit_id\", type = IdType.AUTO)\n    private Long submitId;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"真实姓名\")\n    private String realname;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"题目展示id\")\n    private String displayPid;\n\n    @ApiModelProperty(value = \"题目标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"比赛display_id\")\n    private String displayId;\n\n    @ApiModelProperty(value = \"结果码具体参考文档\")\n    private Date submitTime;\n\n    @ApiModelProperty(value = \"结果码具体参考文档\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"0为代码全部人可见，1为仅自己可见。\")\n    private Boolean share;\n\n    @ApiModelProperty(value = \"运行时间(ms)\")\n    private Integer time;\n\n    @ApiModelProperty(value = \"运行内存（b）\")\n    private Integer memory;\n\n    @ApiModelProperty(value = \"题目得分，ACM题目默认为null\")\n    private Integer score;\n\n    @ApiModelProperty(value = \"该题在OI排行榜的分数\")\n    private Integer oiRankScore;\n\n    @ApiModelProperty(value = \"代码长度\")\n    private Integer length;\n\n    @ApiModelProperty(value = \"代码语言\")\n    private String language;\n\n    @ApiModelProperty(value = \"比赛id，非比赛题目默认为0\")\n    private Long cid;\n\n    @ApiModelProperty(value = \"比赛中题目排序id，非比赛题目默认为0\")\n    private Long cpid;\n\n    @ApiModelProperty(value = \"题目来源\")\n    private String source;\n\n    @ApiModelProperty(value = \"判题机ip\")\n    private String judger;\n\n    @ApiModelProperty(value = \"提交者所在ip\")\n    private String ip;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/OIContestRankVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/18 18:16\n * @Description:\n */\n\n@Data\n@Accessors(chain = true)\npublic class OIContestRankVO {\n\n    @ApiModelProperty(value = \"序号\")\n    private Integer seq;\n\n    @ApiModelProperty(value = \"排名,排名为-1则为打星队伍\")\n    private Integer rank;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"用户真实姓名\")\n    private String realname;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"性别\")\n    private String gender;\n\n    @ApiModelProperty(value = \"头像\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"学校\")\n    private String school;\n\n    @ApiModelProperty(value = \"提交总得分\")\n    private Integer totalScore;\n\n    @ApiModelProperty(value = \"提交总耗时，只有满分的提交才会统计\")\n    private Integer totalTime;\n\n    @ApiModelProperty(value = \"OI的题对应提交得分\")\n    private Map<String, Integer> submissionInfo;\n\n    @ApiModelProperty(value = \"OI的题得满分后对应提交最优耗时\")\n    private Map<String, Integer> timeInfo;\n\n    @ApiModelProperty(value = \"是否已注册\")\n    private Boolean registered;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/OIRankVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/7 14:56\n * @Description:\n */\n@ApiModel(value = \"OI排行榜数据类OIRankVO\", description = \"\")\n@Data\npublic class OIRankVO implements Serializable {\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"个性签名\")\n    private String signature;\n\n    @ApiModelProperty(value = \"头像地址\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"OI得分列表\")\n    private Integer score;\n\n    @ApiModelProperty(value = \"总提交数\")\n    private Integer total;\n\n    @ApiModelProperty(value = \"总通过数\")\n    private Integer ac;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ProblemCountVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@Accessors(chain = true)\npublic class ProblemCountVO implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    private Long pid;\n\n    private Integer total;\n\n    private Integer ac;\n\n    @ApiModelProperty(value = \"空间超限\")\n    private Integer mle;\n\n    @ApiModelProperty(value = \"时间超限\")\n    private Integer tle;\n\n    @ApiModelProperty(value = \"运行错误\")\n    private Integer re;\n\n    @ApiModelProperty(value = \"格式错误\")\n    private Integer pe;\n\n    @ApiModelProperty(value = \"编译错误\")\n    private Integer ce;\n\n    @ApiModelProperty(value = \"答案错误\")\n    private Integer wa;\n\n    @ApiModelProperty(value = \"系统错误\")\n    private Integer se;\n\n    @ApiModelProperty(value = \"部分通过，OI题目\")\n    private Integer pa;\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ProblemInfoVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/30 21:37\n * @Description:\n */\n@Data\n@AllArgsConstructor\npublic class ProblemInfoVO {\n\n    private Problem problem;\n\n    private List<Tag> tags;\n\n    private List<String> languages;\n\n    private ProblemCountVO problemCount;\n\n    private HashMap<String, String> codeTemplate;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ProblemTagVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport com.simplefanc.voj.common.pojo.entity.problem.TagClassification;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * @Author chenfan\n * @Date 2022/8/3\n */\n@Data\npublic class ProblemTagVO implements Serializable {\n    /**\n     * 标签分类\n     */\n    private TagClassification classification;\n\n    /**\n     * 标签列表\n     */\n    private List<Tag> tagList;\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/ProblemVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/27 14:14\n * @Description:\n */\n@ApiModel(value = \"题目列表查询对象ProblemVO\", description = \"\")\n@Data\npublic class ProblemVO implements Serializable {\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"题目展示id\")\n    private String problemId;\n\n    @ApiModelProperty(value = \"题目标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"题目难度\")\n    private Integer difficulty;\n\n    @ApiModelProperty(value = \"题目类型\")\n    private Integer type;\n\n    @ApiModelProperty(value = \"题目标签\")\n    private List<Tag> tags;\n\n    // 以下为题目做题情况\n\n    @ApiModelProperty(value = \"该题总提交数\")\n    private Integer total = 0;\n\n    @ApiModelProperty(value = \"通过提交数\")\n    private Integer ac = 0;\n\n    @ApiModelProperty(value = \"空间超限提交数\")\n    private Integer mle = 0;\n\n    @ApiModelProperty(value = \"时间超限提交数\")\n    private Integer tle = 0;\n\n    @ApiModelProperty(value = \"运行错误提交数\")\n    private Integer re = 0;\n\n    @ApiModelProperty(value = \"格式错误提交数\")\n    private Integer pe = 0;\n\n    @ApiModelProperty(value = \"编译错误提交数\")\n    private Integer ce = 0;\n\n    @ApiModelProperty(value = \"答案错误提交数\")\n    private Integer wa = 0;\n\n    @ApiModelProperty(value = \"系统错误提交数\")\n    private Integer se = 0;\n\n    @ApiModelProperty(value = \"该IO题目分数总和\")\n    private Integer pa = 0;\n\n    @ApiModelProperty(value = \"IO题目总分数\")\n    private Integer score;\n\n    public void setProblemCountVO(ProblemCountVO problemCountVO) {\n        this.total = problemCountVO.getTotal() == null ? 0 : problemCountVO.getTotal();\n        this.ac = problemCountVO.getAc() == null ? 0 : problemCountVO.getAc();\n        this.mle = problemCountVO.getMle() == null ? 0 : problemCountVO.getMle();\n        this.tle = problemCountVO.getTle() == null ? 0 : problemCountVO.getTle();\n        this.re = problemCountVO.getRe() == null ? 0 : problemCountVO.getRe();\n        this.pe = problemCountVO.getPe() == null ? 0 : problemCountVO.getPe();\n        this.ce = problemCountVO.getCe() == null ? 0 : problemCountVO.getCe();\n        this.wa = problemCountVO.getWa() == null ? 0 : problemCountVO.getWa();\n        this.se = problemCountVO.getSe() == null ? 0 : problemCountVO.getSe();\n        this.pa = problemCountVO.getPa() == null ? 0 : problemCountVO.getPa();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/RandomProblemVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 10:55\n * @Description:\n */\n@Data\npublic class RandomProblemVO {\n\n    @ApiModelProperty(value = \"题目id\")\n    private String problemId;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/RegisterCodeVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 17:08\n * @Description:\n */\n@Data\npublic class RegisterCodeVO {\n\n    @ApiModelProperty(value = \"邮箱\")\n    private String email;\n\n    @ApiModelProperty(value = \"注册邮件有效时间，单位秒\")\n    private Integer expire;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/RoleAuthsVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.simplefanc.voj.common.pojo.entity.user.Auth;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/5 14:05\n * @Description:\n */\n@ApiModel(value = \"角色以及其对应的权限列表\", description = \"\")\n@Data\npublic class RoleAuthsVO {\n\n    @ApiModelProperty(value = \"角色id\")\n    private Long id;\n\n    @ApiModelProperty(value = \"角色\")\n    private String role;\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n\n    @ApiModelProperty(value = \"默认0可用，1不可用\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"创建时间\")\n    private Date gmtCreate;\n\n    @ApiModelProperty(value = \"修改时间\")\n    private Date gmtModified;\n\n    @ApiModelProperty(value = \"权限列表\")\n    private List<Auth> auths;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/SubmissionInfoVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 11:38\n * @Description:\n */\n@Data\n@Accessors(chain = true)\npublic class SubmissionInfoVO {\n\n    @ApiModelProperty(value = \"提交详情\")\n    private Judge submission;\n\n    @ApiModelProperty(value = \"提交者是否可以分享该代码\")\n    private Boolean codeShare;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/SysMsgVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/3 16:36\n * @Description:\n */\n@ApiModel(value = \"用户的系统消息\", description = \"\")\n@Data\npublic class SysMsgVO {\n\n    private Long id;\n\n    @ApiModelProperty(value = \"通知标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"通知内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"发布通知的管理员id\")\n    private String adminId;\n\n    @ApiModelProperty(value = \"消息类型，系统通知Sys、我的信息Mine\")\n    private String type;\n\n    @ApiModelProperty(value = \"是否已读\")\n    private Boolean state;\n\n    private Date gmtCreate;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/TrainingRankVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/22 19:58\n * @Description:\n */\n@Data\n@Accessors(chain = true)\npublic class TrainingRankVO {\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"用户真实姓名\")\n    private String realname;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"学校\")\n    private String school;\n\n    @ApiModelProperty(value = \"性别\")\n    private String gender;\n\n    @ApiModelProperty(value = \"头像\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"ac题目数\")\n    private Integer ac;\n\n    @ApiModelProperty(value = \"总运行时间ms\")\n    private Integer totalRunTime;\n\n    @ApiModelProperty(value = \"有提交的题的提交详情\")\n    private HashMap<String, HashMap<String, Object>> submissionInfo;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/TrainingRecordVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/21 14:31\n * @Description:\n */\n@Data\n@ApiModel(value = \"用户在训练的记录\", description = \"\")\npublic class TrainingRecordVO {\n\n    private Long id;\n\n    @ApiModelProperty(value = \"训练id\")\n    private Long tid;\n\n    @ApiModelProperty(value = \"训练题目id\")\n    private Long tpid;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"提交id\")\n    private Long submitId;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"学校\")\n    private String school;\n\n    @ApiModelProperty(value = \"性别\")\n    private String gender;\n\n    @ApiModelProperty(value = \"头像\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"真实姓名\")\n    private String realname;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"提交结果状态码\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"OI得分\")\n    private Integer score;\n\n    @ApiModelProperty(value = \"提交耗时\")\n    private Integer useTime;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/TrainingVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/20 10:51\n * @Description:\n */\n@ApiModel(value = \"训练题单查询对象TrainingVO\", description = \"\")\n@Data\npublic class TrainingVO implements Serializable {\n\n    @ApiModelProperty(value = \"训练id\")\n    private Long id;\n\n    @ApiModelProperty(value = \"题目标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"训练描述\")\n    private String description;\n\n    @ApiModelProperty(value = \"训练创建者用户名\")\n    private String author;\n\n    @ApiModelProperty(value = \"训练题单权限类型：Public、Private\")\n    private String auth;\n\n    @ApiModelProperty(value = \"训练题单的分类名称\")\n    private String categoryName;\n\n    @ApiModelProperty(value = \"训练题单的分类背景颜色\")\n    private String categoryColor;\n\n    @ApiModelProperty(value = \"训练题单的编号，升序排序\")\n    private Integer rank;\n\n    @ApiModelProperty(value = \"该训练的总题数\")\n    private Integer problemCount;\n\n    @ApiModelProperty(value = \"当前用户已完成训练的题数\")\n    private Integer acCount;\n\n    @ApiModelProperty(value = \"训练更新时间\")\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/UserHomeVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/7 22:27\n * @Description:用户主页的数据格式\n */\n@ApiModel(value = \"用户主页的数据格式类UserHomeVO\", description = \"\")\n@Data\npublic class UserHomeVO {\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"学校\")\n    private String school;\n\n    @ApiModelProperty(value = \"个性签名\")\n    private String signature;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"gender\")\n    private String gender;\n\n    @ApiModelProperty(value = \"github地址\")\n    private String github;\n\n    @ApiModelProperty(value = \"博客地址\")\n    private String blog;\n\n    @ApiModelProperty(value = \"头像地址\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"总提交数\")\n    private Integer total;\n\n    @ApiModelProperty(value = \"cf得分\")\n    private Integer rating;\n\n    @ApiModelProperty(value = \"OI得分列表\")\n    private List<Integer> scoreList;\n\n    @ApiModelProperty(value = \"已解决题目列表\")\n    private List<String> solvedList;\n\n    @ApiModelProperty(value = \"最近上线时间\")\n    private Date recentLoginTime;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/UserInfoVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 16:48\n * @Description:\n */\n@Data\npublic class UserInfoVO {\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"头像\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"邮箱\")\n    private String email;\n\n    @ApiModelProperty(value = \"学号\")\n    private String number;\n\n    @ApiModelProperty(value = \"性别\")\n    private String gender;\n\n    @ApiModelProperty(value = \"学校\")\n    private String school;\n\n    @ApiModelProperty(value = \"专业\")\n    private String course;\n\n    @ApiModelProperty(value = \"个性签名\")\n    private String signature;\n\n    @ApiModelProperty(value = \"真实姓名\")\n    private String realname;\n\n    @ApiModelProperty(value = \"github地址\")\n    private String github;\n\n    @ApiModelProperty(value = \"博客地址\")\n    private String blog;\n\n    @ApiModelProperty(value = \"cf的username\")\n    private String cfUsername;\n\n    @ApiModelProperty(value = \"角色列表\")\n    private List<String> roleList;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/UserMsgVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/2 20:50\n * @Description:\n */\n@ApiModel(value = \"用户的讨论贴被评论的、被点赞、评论被回复的消息VO\", description = \"\")\n@Data\npublic class UserMsgVO {\n\n    private Long id;\n\n    @ApiModelProperty(value = \"动作类型，如点赞讨论帖Like_Post、点赞评论Like_Discuss、评论Discuss、回复Reply等\")\n    private String action;\n\n    @ApiModelProperty(value = \"消息来源id，讨论id或比赛id\")\n    private Integer sourceId;\n\n    @ApiModelProperty(value = \"事件源类型：'Discussion'、'Contest'等\")\n    private String sourceType;\n\n    @ApiModelProperty(value = \"事件源的标题，讨论帖子的标题或者比赛的标题\")\n    private String sourceTitle;\n\n    @ApiModelProperty(value = \"事件源的内容，比如回复的内容，回复的评论等等,不超过250字符，超过使用...\")\n    private String sourceContent;\n\n    @ApiModelProperty(value = \"事件引用上一级评论或回复id\")\n    private Integer quoteId;\n\n    @ApiModelProperty(value = \"事件引用上一级的类型：Comment、Reply\")\n    private String quoteType;\n\n    @ApiModelProperty(value = \"事件引用上一级的内容，例如回复你的源评论内容\")\n    private String quoteContent;\n\n    @ApiModelProperty(value = \"事件所发生的地点链接 url\")\n    private String url;\n\n    @ApiModelProperty(value = \"是否已读\")\n    private Boolean state;\n\n    @ApiModelProperty(value = \"动作发出者的uid\")\n    private String senderId;\n\n    @ApiModelProperty(value = \"动作发出者的用户名\")\n    private String senderUsername;\n\n    @ApiModelProperty(value = \"动作发出者的头像\")\n    private String senderAvatar;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/UserRolesVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport com.simplefanc.voj.common.pojo.entity.user.Role;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/5 13:30\n * @Description:\n */\n@ApiModel(value = \"用户信息以及其对应的角色\", description = \"\")\n@Data\npublic class UserRolesVO implements Serializable {\n\n    private static final long serialVersionUID = 10000L;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"密码\")\n    private String password;\n\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @ApiModelProperty(value = \"学校\")\n    private String school;\n\n    @ApiModelProperty(value = \"专业\")\n    private String course;\n\n    @ApiModelProperty(value = \"学号\")\n    private String number;\n\n    @ApiModelProperty(value = \"性别\")\n    private String gender;\n\n    @ApiModelProperty(value = \"真实姓名\")\n    private String realname;\n\n    @ApiModelProperty(value = \"cf的username\")\n    private String cfUsername;\n\n    @ApiModelProperty(value = \"github地址\")\n    private String github;\n\n    @ApiModelProperty(value = \"博客地址\")\n    private String blog;\n\n    @ApiModelProperty(value = \"邮箱\")\n    private String email;\n\n    @ApiModelProperty(value = \"头像地址\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"个性签名\")\n    private String signature;\n\n    @ApiModelProperty(value = \"0正常，1封禁，2申请中，3拒绝\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"创建时间\")\n    private Date gmtCreate;\n\n    @ApiModelProperty(value = \"修改时间\")\n    private Date gmtModified;\n\n    @ApiModelProperty(value = \"角色列表\")\n    private List<Role> roles;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/pojo/vo/UserUnreadMsgCountVO.java",
    "content": "package com.simplefanc.voj.backend.pojo.vo;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:59\n * @Description:\n */\n@ApiModel(value = \"用户未读消息统计\", description = \"\")\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class UserUnreadMsgCountVO {\n\n    @ApiModelProperty(value = \"未读评论\")\n    private Integer comment;\n\n    @ApiModelProperty(value = \"未读回复\")\n    private Integer reply;\n\n    @ApiModelProperty(value = \"未读点赞\")\n    private Integer like;\n\n    @ApiModelProperty(value = \"未读系统通知\")\n    private Integer sys;\n\n    @ApiModelProperty(value = \"未读我的消息\")\n    private Integer mine;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/account/PassportService.java",
    "content": "package com.simplefanc.voj.backend.service.account;\n\nimport com.simplefanc.voj.backend.pojo.dto.ApplyResetPasswordDTO;\nimport com.simplefanc.voj.backend.pojo.dto.LoginDTO;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ResetPasswordDTO;\nimport com.simplefanc.voj.backend.pojo.vo.RegisterCodeVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserInfoVO;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 16:46\n * @Description:\n */\n\npublic interface PassportService {\n\n    UserInfoVO login(LoginDTO loginDTO, HttpServletResponse response, HttpServletRequest request);\n\n    RegisterCodeVO getRegisterCode(String email);\n\n    void register(RegisterDTO registerDTO);\n\n    void applyResetPassword(ApplyResetPasswordDTO applyResetPasswordDTO);\n\n    void resetPassword(ResetPasswordDTO resetPasswordDTO);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/account/impl/PassportServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.account.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.lang.Validator;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.RandomUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.simplefanc.voj.backend.common.constants.EmailConstant;\nimport com.simplefanc.voj.backend.common.constants.RoleEnum;\nimport com.simplefanc.voj.backend.common.exception.StatusAccessDeniedException;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.utils.JwtUtil;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.user.SessionEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserRoleEntityService;\nimport com.simplefanc.voj.backend.config.property.EmailRuleBO;\nimport com.simplefanc.voj.backend.pojo.dto.ApplyResetPasswordDTO;\nimport com.simplefanc.voj.backend.pojo.dto.LoginDTO;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ResetPasswordDTO;\nimport com.simplefanc.voj.backend.config.ConfigVO;\nimport com.simplefanc.voj.backend.pojo.vo.RegisterCodeVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserInfoVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.account.PassportService;\nimport com.simplefanc.voj.backend.service.email.EmailService;\nimport com.simplefanc.voj.backend.service.msg.NoticeService;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.user.*;\nimport com.simplefanc.voj.common.utils.IpUtil;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 16:46\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class PassportServiceImpl implements PassportService {\n\n    private final RedisUtil redisUtil;\n\n    private final JwtUtil jwtUtil;\n\n    private final ConfigVO configVO;\n\n    private final EmailRuleBO emailRuleBo;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final UserRoleEntityService userRoleEntityService;\n\n    private final SessionEntityService sessionEntityService;\n\n    private final EmailService emailService;\n\n    private final NoticeService noticeService;\n\n    @Override\n    public UserInfoVO login(LoginDTO loginDTO, HttpServletResponse response, HttpServletRequest request) {\n        // 去掉账号密码首尾的空格\n        loginDTO.setPassword(loginDTO.getPassword().trim());\n        loginDTO.setUsername(loginDTO.getUsername().trim());\n\n        String userIpAddr = IpUtil.getUserIpAddr(request);\n        String key = RedisConstant.TRY_LOGIN_NUM + loginDTO.getUsername() + \"_\" + userIpAddr;\n        Integer tryLoginCount = redisUtil.get(key, Integer.class);\n\n        if (tryLoginCount != null && tryLoginCount >= 20) {\n            throw new StatusFailException(\"对不起！登录失败次数过多！您的账号有风险，半个小时内暂时无法登录！\");\n        }\n\n        UserRolesVO userRolesVO = userRoleEntityService.getUserRoles(null, loginDTO.getUsername());\n\n        if (userRolesVO == null) {\n            throw new StatusFailException(\"用户名或密码错误！请注意大小写！\");\n        }\n\n        if (!userRolesVO.getPassword().equals(SecureUtil.md5(loginDTO.getPassword()))) {\n            if (tryLoginCount == null) {\n                // 三十分钟不尝试，该限制会自动清空消失\n                redisUtil.set(key, 1, 60 * 30);\n            } else {\n                redisUtil.set(key, tryLoginCount + 1, 60 * 30);\n            }\n            throw new StatusFailException(\"用户名或密码错误！请注意大小写！\");\n        }\n\n        if (userRolesVO.getStatus() != 0) {\n            throw new StatusFailException(\"该账户暂未开放，请联系管理员进行处理！\");\n        }\n\n        String jwt = jwtUtil.generateToken(userRolesVO.getUid());\n        // 放到信息头部\n        response.setHeader(\"Authorization\", jwt);\n        response.setHeader(\"Access-Control-Expose-Headers\", \"Authorization\");\n\n        // 会话记录\n        sessionEntityService.save(new Session().setUid(userRolesVO.getUid()).setIp(IpUtil.getUserIpAddr(request))\n                .setUserAgent(request.getHeader(\"User-Agent\")));\n\n        // 登录成功，清除锁定限制\n        if (tryLoginCount != null) {\n            redisUtil.del(key);\n        }\n\n        // 异步检查是否异地登录\n//        sessionEntityService.checkRemoteLogin(userRolesVO.getUid());\n\n        UserInfoVO userInfoVO = new UserInfoVO();\n        BeanUtil.copyProperties(userRolesVO, userInfoVO, \"roles\");\n        userInfoVO.setRoleList(userRolesVO.getRoles().stream().map(Role::getRole).collect(Collectors.toList()));\n        return userInfoVO;\n    }\n\n    @Override\n    public RegisterCodeVO getRegisterCode(String email) {\n        // 需要判断一下网站是否开启注册\n        if (!configVO.getRegister()) {\n            throw new StatusAccessDeniedException(\"对不起！本站暂未开启注册功能！\");\n        }\n        if (!emailService.isOk()) {\n            throw new StatusAccessDeniedException(\"对不起！本站邮箱系统未配置，暂不支持注册！\");\n        }\n\n        email = email.trim();\n\n        boolean isEmail = Validator.isEmail(email);\n        if (!isEmail) {\n            throw new StatusFailException(\"对不起！您的邮箱格式不正确！\");\n        }\n\n        boolean isBlackEmail = emailRuleBo.getBlackList().stream().anyMatch(email::endsWith);\n        if (isBlackEmail) {\n            throw new StatusForbiddenException(\"对不起！您的邮箱无法注册本网站！\");\n        }\n\n        String lockKey = EmailConstant.REGISTER_EMAIL_LOCK + email;\n        if (redisUtil.hasKey(lockKey)) {\n            throw new StatusFailException(\"对不起，您的操作频率过快，请在\" + redisUtil.getExpire(lockKey) + \"秒后再次发送注册邮件！\");\n        }\n\n        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"email\", email);\n        UserInfo userInfo = userInfoEntityService.getOne(queryWrapper, false);\n        if (userInfo != null) {\n            throw new StatusFailException(\"对不起！该邮箱已被注册，请更换新的邮箱！\");\n        }\n\n        // 随机生成6位数字的组合\n        String numbers = RandomUtil.randomNumbers(6);\n        // 默认验证码有效5分钟\n        redisUtil.set(EmailConstant.REGISTER_KEY_PREFIX + email, numbers, 5 * 60);\n        emailService.sendCode(email, numbers);\n        redisUtil.set(lockKey, 0, 60);\n\n        RegisterCodeVO registerCodeVO = new RegisterCodeVO();\n        registerCodeVO.setEmail(email);\n        registerCodeVO.setExpire(5 * 60);\n\n        return registerCodeVO;\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void register(RegisterDTO registerDTO) {\n        // 需要判断一下网站是否开启注册\n        if (!configVO.getRegister()) {\n            throw new StatusAccessDeniedException(\"对不起！本站暂未开启注册功能！\");\n        }\n\n        String codeKey = EmailConstant.REGISTER_KEY_PREFIX + registerDTO.getEmail();\n        if (!redisUtil.hasKey(codeKey)) {\n            throw new StatusFailException(\"验证码不存在或已过期\");\n        }\n        // 验证码判断\n        if (!redisUtil.get(codeKey).equals(registerDTO.getCode())) {\n            throw new StatusFailException(\"验证码不正确\");\n        }\n\n        String uuid = IdUtil.simpleUUID();\n        // 为新用户设置uuid\n        registerDTO.setUuid(uuid);\n        // 将密码MD5加密写入数据库\n        registerDTO.setPassword(SecureUtil.md5(registerDTO.getPassword().trim()));\n        registerDTO.setUsername(registerDTO.getUsername().trim());\n        registerDTO.setEmail(registerDTO.getEmail().trim());\n\n        // 往user_info表插入数据\n        boolean addUser = userInfoEntityService.addUser(registerDTO);\n\n        // 往user_role表插入数据\n        boolean addUserRole = userRoleEntityService.save(new UserRole().setRoleId(RoleEnum.DEFAULT_USER.getId()).setUid(uuid));\n\n        if (addUser && addUserRole) {\n            redisUtil.del(registerDTO.getEmail());\n            noticeService.syncNoticeToNewRegisterUser(uuid);\n        } else {\n            throw new StatusFailException(\"注册失败，请稍后重新尝试！\");\n        }\n    }\n\n    @Override\n    public void applyResetPassword(ApplyResetPasswordDTO applyResetPasswordDTO) {\n        String captcha = applyResetPasswordDTO.getCaptcha();\n        String captchaKey = applyResetPasswordDTO.getCaptchaKey();\n        String email = applyResetPasswordDTO.getEmail();\n\n        if (!emailService.isOk()) {\n            throw new StatusFailException(\"对不起！本站邮箱系统未配置，暂不支持重置密码！\");\n        }\n\n        String lockKey = EmailConstant.RESET_EMAIL_LOCK + email;\n        if (redisUtil.hasKey(lockKey)) {\n            throw new StatusFailException(\"对不起，您的操作频率过快，请在\" + redisUtil.getExpire(lockKey) + \"秒后再次发送重置邮件！\");\n        }\n\n        // 获取redis中的验证码\n        String redisCode = redisUtil.get(captchaKey, String.class);\n        if (!redisCode.equals(captcha.trim().toLowerCase())) {\n            throw new StatusFailException(\"验证码不正确\");\n        }\n\n        QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();\n        userInfoQueryWrapper.eq(\"email\", email.trim());\n        UserInfo userInfo = userInfoEntityService.getOne(userInfoQueryWrapper, false);\n        if (userInfo == null) {\n            throw new StatusFailException(\"对不起，该邮箱无该用户，请重新检查！\");\n        }\n        // 随机生成20位数字与字母的组合\n        String code = IdUtil.simpleUUID().substring(0, 21);\n        // 默认链接有效10分钟\n        redisUtil.set(EmailConstant.RESET_PASSWORD_KEY_PREFIX + userInfo.getUsername(), code, 10 * 60);\n        // 发送邮件\n        emailService.sendResetPassword(userInfo.getUsername(), code, email.trim());\n        redisUtil.set(lockKey, 0, 90);\n    }\n\n    @Override\n    public void resetPassword(ResetPasswordDTO resetPasswordDTO) {\n        String username = resetPasswordDTO.getUsername();\n        String password = resetPasswordDTO.getPassword();\n        String code = resetPasswordDTO.getCode();\n\n        String codeKey = EmailConstant.RESET_PASSWORD_KEY_PREFIX + username;\n        if (!redisUtil.hasKey(codeKey)) {\n            throw new StatusFailException(\"重置密码链接不存在或已过期，请重新发送重置邮件\");\n        }\n        // 验证码判断\n        if (!redisUtil.get(codeKey).equals(code)) {\n            throw new StatusFailException(\"重置密码的验证码不正确，请重新输入\");\n        }\n\n        UpdateWrapper<UserInfo> userInfoUpdateWrapper = new UpdateWrapper<>();\n        userInfoUpdateWrapper.eq(\"username\", username).set(\"password\", SecureUtil.md5(password));\n        boolean isOk = userInfoEntityService.update(userInfoUpdateWrapper);\n        if (!isOk) {\n            throw new StatusFailException(\"重置密码失败\");\n        }\n        redisUtil.del(codeKey);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/announcement/AdminAnnouncementService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.announcement;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:40\n * @Description:\n */\npublic interface AdminAnnouncementService {\n\n    IPage<AnnouncementVO> getAnnouncementList(Integer limit, Integer currentPage);\n\n    void deleteAnnouncement(long aid);\n\n    void addAnnouncement(Announcement announcement);\n\n    void updateAnnouncement(Announcement announcement);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/announcement/impl/AdminAnnouncementServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.announcement.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.dao.common.AnnouncementEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.backend.service.admin.announcement.AdminAnnouncementService;\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:40\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class AdminAnnouncementServiceImpl implements AdminAnnouncementService {\n\n    private final AnnouncementEntityService announcementEntityService;\n\n    @Override\n    public IPage<AnnouncementVO> getAnnouncementList(Integer limit, Integer currentPage) {\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n        return announcementEntityService.getAnnouncementList(limit, currentPage, false);\n\n    }\n\n    @Override\n    public void deleteAnnouncement(long aid) {\n        boolean isOk = announcementEntityService.removeById(aid);\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败\");\n        }\n    }\n\n    @Override\n    public void addAnnouncement(Announcement announcement) {\n        boolean isOk = announcementEntityService.save(announcement);\n        if (!isOk) {\n            throw new StatusFailException(\"添加失败\");\n        }\n    }\n\n    @Override\n    public void updateAnnouncement(Announcement announcement) {\n        boolean isOk = announcementEntityService.saveOrUpdate(announcement);\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/contest/AdminContestAnnouncementService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.contest;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.AnnouncementDTO;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 11:19\n * @Description:\n */\n\npublic interface AdminContestAnnouncementService {\n\n    IPage<AnnouncementVO> getAnnouncementList(Integer limit, Integer currentPage, Long cid);\n\n    void deleteAnnouncement(Long aid);\n\n    void addAnnouncement(AnnouncementDTO announcementDTO);\n\n    void updateAnnouncement(AnnouncementDTO announcementDTO);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/contest/AdminContestProblemService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.contest;\n\nimport com.simplefanc.voj.backend.pojo.dto.ContestProblemDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\n\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 11:20\n * @Description:\n */\n\npublic interface AdminContestProblemService {\n\n    Map<String, Object> getProblemList(Integer limit, Integer currentPage, String keyword, Long cid,\n                                           Integer problemType, String oj);\n\n    Problem getProblem(Long pid);\n\n    void deleteProblem(Long pid, Long cid);\n\n    Map<Object, Object> addProblem(ProblemDTO problemDTO);\n\n    void updateProblem(ProblemDTO problemDTO);\n\n    ContestProblem getContestProblem(Long cid, Long pid);\n\n    ContestProblem setContestProblem(ContestProblem contestProblem);\n\n    void addProblemFromPublic(ContestProblemDTO contestProblemDTO);\n\n    void importContestRemoteOjProblem(String name, String problemId, Long cid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/contest/AdminContestService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.contest;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.AdminContestVO;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 11:20\n * @Description:\n */\n\npublic interface AdminContestService {\n\n    IPage<Contest> getContestList(Integer limit, Integer currentPage, String keyword);\n\n    AdminContestVO getContest(Long cid);\n\n    void deleteContest(Long cid);\n\n    void addContest(AdminContestVO adminContestVO);\n\n    void updateContest(AdminContestVO adminContestVO);\n\n    void changeContestVisible(Long cid, String uid, Boolean visible);\n\n    void cloneContest(Long cid);\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/contest/impl/AdminContestAnnouncementServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.contest.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.dao.common.AnnouncementEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestAnnouncementEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.AnnouncementDTO;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.backend.service.admin.contest.AdminContestAnnouncementService;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestAnnouncement;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 11:19\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class AdminContestAnnouncementServiceImpl implements AdminContestAnnouncementService {\n\n    private final AnnouncementEntityService announcementEntityService;\n\n    private final ContestAnnouncementEntityService contestAnnouncementEntityService;\n\n    @Override\n    public IPage<AnnouncementVO> getAnnouncementList(Integer limit, Integer currentPage, Long cid) {\n\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n        return announcementEntityService.getContestAnnouncement(cid, false, limit, currentPage);\n    }\n\n    @Override\n    public void deleteAnnouncement(Long aid) {\n        boolean isOk = announcementEntityService.removeById(aid);\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败！\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addAnnouncement(AnnouncementDTO announcementDTO) {\n        boolean saveAnnouncement = announcementEntityService.save(announcementDTO.getAnnouncement());\n        boolean saveContestAnnouncement = contestAnnouncementEntityService.saveOrUpdate(new ContestAnnouncement()\n                .setAid(announcementDTO.getAnnouncement().getId()).setCid(announcementDTO.getCid()));\n        if (!saveAnnouncement || !saveContestAnnouncement) {\n            throw new StatusFailException(\"添加失败\");\n        }\n    }\n\n    @Override\n    public void updateAnnouncement(AnnouncementDTO announcementDTO) {\n        boolean isOk = announcementEntityService.saveOrUpdate(announcementDTO.getAnnouncement());\n        // 删除成功\n        if (!isOk) {\n            throw new StatusFailException(\"更新失败！\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/contest/impl/AdminContestProblemServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.contest.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.dao.contest.ContestProblemEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.judge.remote.crawler.AbstractProblemCrawler;\nimport com.simplefanc.voj.backend.pojo.dto.ContestProblemDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.admin.contest.AdminContestProblemService;\nimport com.simplefanc.voj.backend.service.admin.problem.RemoteProblemService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.ProblemEnum;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.io.File;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 11:20\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\n@Slf4j(topic = \"voj\")\npublic class AdminContestProblemServiceImpl implements AdminContestProblemService {\n\n    private final ContestProblemEntityService contestProblemEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final RemoteProblemService remoteProblemService;\n\n    private final FilePathProperties filePathProps;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Map<String, Object> getProblemList(Integer limit, Integer currentPage, String keyword, Long cid,\n                                              Integer problemType, String oj) {\n        // 根据cid在ContestProblem表中查询到对应pid集合\n        HashMap<Long, ContestProblem> contestProblemMap = new HashMap<>();\n        List<Long> pidList = new LinkedList<>();\n        contestProblemEntityService.lambdaQuery()\n                .eq(ContestProblem::getCid, cid)\n                .list()\n                .forEach(contestProblem -> {\n                    contestProblemMap.put(contestProblem.getPid(), contestProblem);\n                    pidList.add(contestProblem.getPid());\n                });\n\n        QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();\n\n        if (problemType != null) {\n            // 从公共题库添加题目\n            problemQueryWrapper.notIn(!pidList.isEmpty(), \"id\", pidList);\n\n            problemQueryWrapper\n                    // 同时需要与比赛相同类型的题目或vj题目（20221123移除限制）\n//                    .and(wrapper -> wrapper.eq(\"type\", problemType).or().eq(\"is_remote\", true))\n                    // 题目权限为隐藏的不可加入！\n                    .ne(\"auth\", ProblemEnum.AUTH_PRIVATE.getCode());\n        } else {\n            // 比赛题目列表\n            problemQueryWrapper.in(!pidList.isEmpty(), \"id\", pidList);\n        }\n\n        // 根据oj筛选过滤\n        if (oj != null && !\"All\".equals(oj)) {\n            if (!RemoteOj.isRemoteOj(oj)) {\n                problemQueryWrapper.eq(\"is_remote\", false);\n            } else {\n                problemQueryWrapper.eq(\"is_remote\", true).likeRight(\"problem_id\", oj);\n            }\n        }\n\n        // 根据keyword筛选过滤\n        if (StrUtil.isNotEmpty(keyword)) {\n            problemQueryWrapper.and(wrapper -> wrapper.like(\"title\", keyword).or().like(\"problem_id\", keyword).or()\n                    .like(\"author\", keyword));\n        }\n\n        // 比赛题目列表为空\n        if (pidList.isEmpty() && problemType == null) {\n            problemQueryWrapper = new QueryWrapper<>();\n            problemQueryWrapper.eq(\"id\", null);\n        }\n\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n        if (!pidList.isEmpty() && problemType == null) {\n            limit = Integer.MAX_VALUE;\n        }\n\n        IPage<Problem> problemListPage = problemEntityService.page(new Page<>(currentPage, limit), problemQueryWrapper);\n\n        if (!pidList.isEmpty() && problemType == null) {\n            List<Problem> sortedProblemList = problemListPage.getRecords().stream()\n                    .sorted(Comparator.comparing(Problem::getId, (a, b) -> {\n                        ContestProblem x = contestProblemMap.get(a);\n                        ContestProblem y = contestProblemMap.get(b);\n                        return x.compareTo(y);\n                    })).collect(Collectors.toList());\n            problemListPage.setRecords(sortedProblemList);\n        }\n\n        return MapUtil.builder(new HashMap<String, Object>()).put(\"problemList\", problemListPage)\n                .put(\"contestProblemMap\", contestProblemMap)\n                .build();\n    }\n\n    @Override\n    public Problem getProblem(Long pid) {\n        Problem problem = problemEntityService.getById(pid);\n\n        if (problem != null) {\n            // 获取当前登录的用户\n            UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n            boolean isRoot = UserSessionUtil.isRoot();\n            boolean isProblemAdmin = UserSessionUtil.isProblemAdmin();\n            // 只有超级管理员和题目管理员、题目创建者才能操作\n            if (!isRoot && !isProblemAdmin && !userRolesVO.getUsername().equals(problem.getAuthor())) {\n                throw new StatusForbiddenException(\"对不起，你无权限查看题目！\");\n            }\n\n            return problem;\n        } else {\n            throw new StatusFailException(\"查询失败！\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteProblem(Long pid, Long cid) {\n        // 比赛id不为null，表示就是从比赛列表移除而已\n        if (cid != null) {\n            QueryWrapper<ContestProblem> contestProblemQueryWrapper = new QueryWrapper<>();\n            contestProblemQueryWrapper.eq(\"cid\", cid).eq(\"pid\", pid);\n            contestProblemEntityService.remove(contestProblemQueryWrapper);\n            // 把该题目在比赛的提交全部删掉\n            UpdateWrapper<Judge> judgeUpdateWrapper = new UpdateWrapper<>();\n            judgeUpdateWrapper.eq(\"cid\", cid).eq(\"pid\", pid);\n            judgeEntityService.remove(judgeUpdateWrapper);\n        } else {\n            // problem的id为其他表的外键的表中的对应数据都会被一起删除！\n            problemEntityService.removeById(pid);\n        }\n\n        if (cid == null) {\n            FileUtil.del(filePathProps.getTestcaseBaseFolder() + File.separator + \"problem_\" + pid);\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Map<Object, Object> addProblem(ProblemDTO problemDTO) {\n\n        QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"problem_id\", problemDTO.getProblem().getProblemId().toUpperCase());\n        Problem problem = problemEntityService.getOne(queryWrapper);\n        if (problem != null) {\n            throw new StatusFailException(\"该题目的Problem ID已存在，请更换！\");\n        }\n        // 设置为比赛题目\n        problemDTO.getProblem().setAuth(ProblemEnum.AUTH_CONTEST.getCode());\n        boolean isOk = problemEntityService.adminAddProblem(problemDTO);\n        // 添加成功\n        if (isOk) {\n            // 顺便返回新的题目id，好下一步添加外键操作\n            // TODO put 键\n            return MapUtil.builder().put(\"pid\", problemDTO.getProblem().getId()).map();\n        } else {\n            throw new StatusFailException(\"添加失败\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateProblem(ProblemDTO problemDTO) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        boolean isRoot = UserSessionUtil.isRoot();\n        boolean isProblemAdmin = UserSessionUtil.isProblemAdmin();\n        // 只有超级管理员和题目管理员、题目创建者才能操作\n        if (!isRoot && !isProblemAdmin && !userRolesVO.getUsername().equals(problemDTO.getProblem().getAuthor())) {\n            throw new StatusForbiddenException(\"对不起，你无权限修改题目！\");\n        }\n\n        QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"problem_id\", problemDTO.getProblem().getProblemId().toUpperCase());\n        Problem problem = problemEntityService.getOne(queryWrapper);\n\n        // 如果problem_id不是原来的且已存在该problem_id，则修改失败！\n        if (problem != null && problem.getId().longValue() != problemDTO.getProblem().getId()) {\n            throw new StatusFailException(\"当前的Problem ID 已被使用，请重新更换新的！\");\n        }\n\n        // 记录修改题目的用户\n        problemDTO.getProblem().setModifiedUser(userRolesVO.getUsername());\n\n        boolean isOk = problemEntityService.adminUpdateProblem(problemDTO);\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n    @Override\n    public ContestProblem getContestProblem(Long cid, Long pid) {\n        QueryWrapper<ContestProblem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"cid\", cid).eq(\"pid\", pid);\n        ContestProblem contestProblem = contestProblemEntityService.getOne(queryWrapper);\n        if (contestProblem == null) {\n            throw new StatusFailException(\"查询失败\");\n        }\n        return contestProblem;\n    }\n\n    @Override\n    public ContestProblem setContestProblem(ContestProblem contestProblem) {\n        boolean isOk = contestProblemEntityService.saveOrUpdate(contestProblem);\n        if (isOk) {\n            contestProblemEntityService.syncContestRecord(contestProblem.getPid(), contestProblem.getCid(),\n                    contestProblem.getDisplayId());\n            return contestProblem;\n        } else {\n            throw new StatusFailException(\"更新失败\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addProblemFromPublic(ContestProblemDTO contestProblemDTO) {\n        Long pid = contestProblemDTO.getPid();\n        Long cid = contestProblemDTO.getCid();\n\n        List<ContestProblem> list = contestProblemEntityService.lambdaQuery().eq(ContestProblem::getCid, cid)\n                .orderByDesc(ContestProblem::getId).list();\n        boolean existed = list.stream().anyMatch(contestProblem -> pid.equals(contestProblem.getPid()));\n        if (existed) {\n            throw new StatusFailException(\"添加失败，该题目已添加！\");\n        }\n\n        // 比赛中题目显示默认为原标题\n        Problem problem = problemEntityService.getById(pid);\n        String displayName = problem.getTitle();\n        // 修改成比赛题目\n        boolean updateProblem = problemEntityService.saveOrUpdate(problem.setAuth(ProblemEnum.AUTH_CONTEST.getCode()));\n\n        ContestProblem last = list.isEmpty() ? null : list.get(0);\n        // displayId 按顺序递增\n        String displayId = last == null ? \"A\" : Character.toString((char) (last.getDisplayId().charAt(0) + 1));\n        boolean isOk = contestProblemEntityService.saveOrUpdate(\n                new ContestProblem().setCid(cid).setPid(pid).setDisplayTitle(displayName).setDisplayId(displayId));\n        if (!isOk || !updateProblem) {\n            throw new StatusFailException(\"添加失败\");\n        }\n    }\n\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void importContestRemoteOjProblem(String name, String problemId, Long cid) {\n        QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"problem_id\", name.toUpperCase() + \"-\" + problemId);\n        Problem problem = problemEntityService.getOne(queryWrapper, false);\n\n        // 如果该题目不存在，需要先导入\n        if (problem == null) {\n            try {\n                AbstractProblemCrawler.RemoteProblemInfo otherOjProblemInfo = remoteProblemService\n                        .getOtherOJProblemInfo(name.toUpperCase(), problemId);\n                if (otherOjProblemInfo != null) {\n                    problem = remoteProblemService.adminAddOtherOJProblem(otherOjProblemInfo, name);\n                    if (problem == null) {\n                        throw new StatusFailException(\"导入新题目失败！请重新尝试！\");\n                    }\n                } else {\n                    throw new StatusFailException(\"导入新题目失败！原因：可能是与该OJ链接超时或题号格式错误！\");\n                }\n            } catch (Exception e) {\n                log.error(\"导入远程题目异常-------------->\", e);\n                throw new StatusFailException(e.getMessage());\n            }\n        }\n\n        addProblemFromPublic(new ContestProblemDTO(problem.getId(), cid));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/contest/impl/AdminContestServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.contest.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.date.DateUnit;\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.exception.StatusSystemErrorException;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestProblemEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestRegisterEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.AdminContestVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.admin.contest.AdminContestService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRegister;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 11:20\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class AdminContestServiceImpl implements AdminContestService {\n\n    private final ContestEntityService contestEntityService;\n\n    private final ContestProblemEntityService contestProblemEntityService;\n\n    private final ContestRegisterEntityService contestRegisterEntityService;\n\n    private final ContestValidator contestValidator;\n\n    @Override\n    public IPage<Contest> getContestList(Integer limit, Integer currentPage, String keyword) {\n\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n        IPage<Contest> iPage = new Page<>(currentPage, limit);\n        QueryWrapper<Contest> queryWrapper = new QueryWrapper<>();\n        // 过滤密码\n        queryWrapper.select(Contest.class, info -> !\"pwd\".equals(info.getColumn()));\n        if (StrUtil.isNotEmpty(keyword)) {\n            keyword = keyword.trim();\n            queryWrapper.like(\"title\", keyword).or().like(\"id\", keyword);\n        }\n        queryWrapper.orderByAsc(\"status\").orderByDesc(\"start_time\");\n        return contestEntityService.page(iPage, queryWrapper);\n    }\n\n    @Override\n    public AdminContestVO getContest(Long cid) {\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(cid);\n        // 查询不存在\n        if (contest == null) {\n            throw new StatusFailException(\"查询失败：该比赛不存在,请检查参数cid是否准确！\");\n        }\n\n        // 只有超级管理员和比赛拥有者才能操作\n        if (!contestValidator.isContestAdmin(contest)) {\n            throw new StatusForbiddenException(\"对不起，你无权限操作！\");\n        }\n        AdminContestVO adminContestVO = BeanUtil.copyProperties(contest, AdminContestVO.class, \"starAccount\");\n        if (StrUtil.isEmpty(contest.getStarAccount())) {\n            adminContestVO.setStarAccount(new ArrayList<>());\n        } else {\n            JSONObject jsonObject = JSONUtil.parseObj(contest.getStarAccount());\n            List<String> starAccount = jsonObject.get(\"star_account\", List.class);\n            adminContestVO.setStarAccount(starAccount);\n        }\n        return adminContestVO;\n    }\n\n    @Override\n    public void deleteContest(Long cid) {\n        boolean isOk = contestEntityService.removeById(cid);\n        // contest的id为其他表的外键的表中的对应数据都会被一起删除！\n        // 删除成功\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败\");\n        }\n    }\n\n    @Override\n    public void addContest(AdminContestVO adminContestVO) {\n        Contest contest = BeanUtil.copyProperties(adminContestVO, Contest.class, \"starAccount\");\n        JSONObject accountJson = new JSONObject();\n        accountJson.set(\"star_account\", adminContestVO.getStarAccount());\n        contest.setStarAccount(accountJson.toString());\n        boolean isOk = contestEntityService.save(contest);\n        if (!isOk) {\n            throw new StatusFailException(\"添加失败\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateContest(AdminContestVO adminContestVO) {\n        // 是否为超级管理员\n        boolean isRoot = UserSessionUtil.isRoot();\n        if (!isRoot && !contestValidator.isContestOwner(adminContestVO.getUid())) {\n            throw new StatusForbiddenException(\"对不起，你无权限操作！\");\n        }\n        Contest contest = BeanUtil.copyProperties(adminContestVO, Contest.class, \"starAccount\");\n        JSONObject accountJson = new JSONObject();\n        accountJson.set(\"star_account\", adminContestVO.getStarAccount());\n        contest.setStarAccount(accountJson.toString());\n        Contest oldContest = contestEntityService.getById(contest.getId());\n        boolean isOk = contestEntityService.saveOrUpdate(contest);\n        if (isOk) {\n            if (!contest.getAuth().equals(ContestEnum.AUTH_PUBLIC.getCode())) {\n                // 改了比赛密码则需要删掉已有的注册比赛用户\n                if (!Objects.equals(oldContest.getPwd(), contest.getPwd())) {\n                    UpdateWrapper<ContestRegister> updateWrapper = new UpdateWrapper<>();\n                    updateWrapper.eq(\"cid\", contest.getId());\n                    contestRegisterEntityService.remove(updateWrapper);\n                }\n            }\n        } else {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n    @Override\n    public void changeContestVisible(Long cid, String uid, Boolean visible) {\n        // 是否为超级管理员\n        boolean isRoot = UserSessionUtil.isRoot();\n        // 只有超级管理员和比赛拥有者才能操作\n        if (!isRoot && !contestValidator.isContestOwner(uid)) {\n            throw new StatusForbiddenException(\"对不起，你无权限操作！\");\n        }\n\n        boolean isOK = contestEntityService.saveOrUpdate(new Contest().setId(cid).setVisible(visible));\n\n        if (!isOK) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void cloneContest(Long cid) {\n        Contest contest = contestEntityService.getById(cid);\n        if (contest == null) {\n            throw new StatusSystemErrorException(\"该比赛不存在，无法克隆！\");\n        }\n        final UserRolesVO userInfo = UserSessionUtil.getUserInfo();\n        long duration = DateUtil.between(contest.getStartTime(), contest.getEndTime(), DateUnit.SECOND);\n        Date startTime = DateUtil.offsetHour(DateUtil.beginOfHour(new Date()), 1);\n        Date endTime = DateUtil.offsetSecond(startTime, (int) duration);\n        Contest copyContest = BeanUtil.copyProperties(contest, Contest.class, \"id\", \"gmtCreate\", \"gmtModified\");\n        copyContest.setUid(userInfo.getUid())\n                .setAuthor(userInfo.getUsername())\n                .setSource(cid.intValue())\n                .setTitle(contest.getTitle() + \" [Clone]\")\n                .setStartTime(startTime)\n                .setEndTime(endTime)\n                .setDuration(duration)\n                .setSealRankTime(DateUtil.offsetMillisecond(endTime, (int) -DateUtil.betweenMs(contest.getSealRankTime(), contest.getEndTime())));\n\n        contestEntityService.save(copyContest);\n        Long copyCid = copyContest.getId();\n\n        List<ContestProblem> problems = contestProblemEntityService.lambdaQuery()\n                .eq(ContestProblem::getCid, cid)\n                .list();\n        List<ContestProblem> copyContestProblems = problems.stream()\n                .map(problem -> BeanUtil.copyProperties(problem, ContestProblem.class, \"id\", \"gmtCreate\", \"gmtModified\"))\n                .map(problem -> problem.setCid(copyCid))\n                .collect(Collectors.toList());\n        contestProblemEntityService.saveBatch(copyContestProblems);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/discussion/AdminDiscussionService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.discussion;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionReport;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 16:02\n * @Description:\n */\n\npublic interface AdminDiscussionService {\n\n    void updateDiscussion(Discussion discussion);\n\n    void removeDiscussion(List<Integer> didList);\n\n    IPage<DiscussionReport> getDiscussionReport(Integer limit, Integer currentPage);\n\n    void updateDiscussionReport(DiscussionReport discussionReport);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/discussion/impl/AdminDiscussionServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.discussion.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionReportEntityService;\nimport com.simplefanc.voj.backend.service.admin.discussion.AdminDiscussionService;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionReport;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 16:02\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class AdminDiscussionServiceImpl implements AdminDiscussionService {\n\n    private final DiscussionEntityService discussionEntityService;\n\n    private final DiscussionReportEntityService discussionReportEntityService;\n\n    @Override\n    public void updateDiscussion(Discussion discussion) {\n        boolean isOk = discussionEntityService.updateById(discussion);\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n    @Override\n    public void removeDiscussion(List<Integer> didList) {\n        boolean isOk = discussionEntityService.removeByIds(didList);\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败\");\n        }\n    }\n\n    @Override\n    public IPage<DiscussionReport> getDiscussionReport(Integer limit, Integer currentPage) {\n        QueryWrapper<DiscussionReport> discussionReportQueryWrapper = new QueryWrapper<>();\n        discussionReportQueryWrapper.orderByAsc(\"status\");\n        IPage<DiscussionReport> iPage = new Page<>(currentPage, limit);\n        return discussionReportEntityService.page(iPage, discussionReportQueryWrapper);\n    }\n\n    @Override\n    public void updateDiscussionReport(DiscussionReport discussionReport) {\n        boolean isOk = discussionReportEntityService.updateById(discussionReport);\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/problem/AdminProblemService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.problem;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.common.pojo.dto.CompileDTO;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport com.simplefanc.voj.common.result.CommonResult;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 16:32\n * @Description:\n */\n\npublic interface AdminProblemService {\n\n    IPage<Problem> getProblemList(Integer limit, Integer currentPage, String keyword, Integer auth, String oj);\n\n    Problem getProblem(Long pid);\n\n    void deleteProblem(Long pid);\n\n    void addProblem(ProblemDTO problemDTO);\n\n    void updateProblem(ProblemDTO problemDTO);\n\n    List<ProblemCase> getProblemCases(Long pid, Boolean isUpload);\n\n    CommonResult compileSpj(CompileDTO compileDTO);\n\n    CommonResult compileInteractive(CompileDTO compileDTO);\n\n    void importRemoteOjProblem(String name, String problemId);\n\n    void changeProblemAuth(Problem problem);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/problem/RemoteProblemService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.problem;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.dao.problem.*;\nimport com.simplefanc.voj.backend.judge.remote.crawler.AbstractProblemCrawler;\nimport com.simplefanc.voj.backend.judge.remote.crawler.CrawlersHolder;\nimport com.simplefanc.voj.common.pojo.entity.problem.*;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 17:33\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class RemoteProblemService {\n\n    private final ProblemEntityService problemEntityService;\n\n    private final ProblemTagEntityService problemTagEntityService;\n\n    private final TagEntityService tagEntityService;\n\n    private final LanguageEntityService languageEntityService;\n\n    private final ProblemLanguageEntityService problemLanguageEntityService;\n\n    public AbstractProblemCrawler.RemoteProblemInfo getOtherOJProblemInfo(String ojName, String problemId)\n            throws Exception {\n        return CrawlersHolder.getCrawler(ojName).getProblemInfo(problemId);\n    }\n\n    @Transactional(rollbackFor = Exception.class)\n    public Problem adminAddOtherOJProblem(AbstractProblemCrawler.RemoteProblemInfo remoteProblemInfo, String OJName) {\n        Problem problem = remoteProblemInfo.getProblem();\n        // 1. 保存题目\n        boolean addProblemResult = problemEntityService.save(problem);\n\n        // 2. 添加对应的language\n        QueryWrapper<Language> languageQueryWrapper = new QueryWrapper<>();\n        languageQueryWrapper.eq(\"oj\", OJName);\n        List<Language> OJLanguageList = languageEntityService.list(languageQueryWrapper);\n        List<ProblemLanguage> problemLanguageList = new LinkedList<>();\n        for (Language language : OJLanguageList) {\n            problemLanguageList.add(new ProblemLanguage().setPid(problem.getId()).setLid(language.getId()));\n        }\n        boolean addProblemLanguageResult = problemLanguageEntityService.saveOrUpdateBatch(problemLanguageList);\n\n        // 3. 添加对应的tag\n        boolean addProblemTagResult;\n        List<Tag> addTagList = remoteProblemInfo.getTagList();\n        List<Tag> needAddTagList = new LinkedList<>();\n        HashMap<String, Tag> tagFlag = new HashMap<>();\n        if (addTagList != null && addTagList.size() > 0) {\n            QueryWrapper<Tag> tagQueryWrapper = new QueryWrapper<>();\n            tagQueryWrapper.eq(\"oj\", OJName);\n            List<Tag> tagList = tagEntityService.list(tagQueryWrapper);\n            // 已存在的tag不进行添加\n            for (Tag hasTag : tagList) {\n                tagFlag.put(hasTag.getName().toUpperCase(), hasTag);\n            }\n            for (Tag tmp : addTagList) {\n                Tag tag = tagFlag.get(tmp.getName().toUpperCase());\n                if (tag == null) {\n                    tmp.setOj(OJName);\n                    needAddTagList.add(tmp);\n                } else {\n                    needAddTagList.add(tag);\n                }\n            }\n            tagEntityService.saveOrUpdateBatch(needAddTagList);\n\n            List<ProblemTag> problemTagList = new LinkedList<>();\n            for (Tag tmp : needAddTagList) {\n                problemTagList.add(new ProblemTag().setTid(tmp.getId()).setPid(problem.getId()));\n            }\n            addProblemTagResult = problemTagEntityService.saveOrUpdateBatch(problemTagList);\n        } else {\n            QueryWrapper<Tag> tagQueryWrapper = new QueryWrapper<>();\n            tagQueryWrapper.eq(\"name\", OJName);\n            Tag OJNameTag = tagEntityService.getOne(tagQueryWrapper, false);\n            if (OJNameTag == null) {\n                OJNameTag = new Tag();\n                OJNameTag.setOj(OJName);\n                OJNameTag.setName(OJName);\n                tagEntityService.saveOrUpdate(OJNameTag);\n            }\n            addProblemTagResult = problemTagEntityService\n                    .saveOrUpdate(new ProblemTag().setTid(OJNameTag.getId()).setPid(problem.getId()));\n        }\n\n        if (addProblemResult && addProblemTagResult && addProblemLanguageResult) {\n            return problem;\n        }\n        return null;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/problem/impl/AdminProblemServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.problem.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.constants.CallJudgerType;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemCaseEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.judge.Dispatcher;\nimport com.simplefanc.voj.backend.judge.remote.crawler.AbstractProblemCrawler;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.admin.problem.AdminProblemService;\nimport com.simplefanc.voj.backend.service.admin.problem.RemoteProblemService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.ProblemEnum;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.pojo.dto.CompileDTO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.io.File;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 16:32\n * @Description:\n */\n\n@Service\n@RefreshScope\n@RequiredArgsConstructor\n@Slf4j(topic = \"voj\")\npublic class AdminProblemServiceImpl implements AdminProblemService {\n\n    private final ProblemEntityService problemEntityService;\n\n    private final ProblemCaseEntityService problemCaseEntityService;\n\n    private final Dispatcher dispatcher;\n\n    @Value(\"${voj.judge.token}\")\n    private String judgeToken;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final RemoteProblemService remoteProblemService;\n\n    private final FilePathProperties filePathProps;\n\n    @Override\n    public IPage<Problem> getProblemList(Integer limit, Integer currentPage, String keyword, Integer auth, String oj) {\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n        IPage<Problem> iPage = new Page<>(currentPage, limit);\n        IPage<Problem> problemList;\n\n        QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.orderByDesc(\"gmt_create\").orderByDesc(\"id\");\n\n        // 根据oj筛选过滤\n        if (oj != null && !\"All\".equals(oj)) {\n            if (!RemoteOj.isRemoteOj(oj)) {\n                queryWrapper.eq(\"is_remote\", false);\n            } else {\n                queryWrapper.eq(\"is_remote\", true).likeRight(\"problem_id\", oj);\n            }\n        }\n\n        if (auth != null && auth != 0) {\n            queryWrapper.eq(\"auth\", auth);\n        }\n\n        if (StrUtil.isNotEmpty(keyword)) {\n            final String key = keyword.trim();\n            queryWrapper\n                    .and(wrapper -> wrapper.like(\"title\", key).or().like(\"author\", key).or().like(\"problem_id\", key));\n        }\n        problemList = problemEntityService.page(iPage, queryWrapper);\n        return problemList;\n    }\n\n    @Override\n    public Problem getProblem(Long pid) {\n        Problem problem = problemEntityService.getById(pid);\n\n        // 查询成功\n        if (problem != null) {\n            // 获取当前登录的用户\n            UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n            boolean isRoot = UserSessionUtil.isRoot();\n            boolean isProblemAdmin = UserSessionUtil.isProblemAdmin();\n            // 只有超级管理员和题目管理员、题目创建者才能操作\n            if (!isRoot && !isProblemAdmin && !userRolesVO.getUsername().equals(problem.getAuthor())) {\n                throw new StatusForbiddenException(\"对不起，你无权限查看题目！\");\n            }\n\n            return problem;\n        } else {\n            throw new StatusFailException(\"查询失败！\");\n        }\n    }\n\n    @Override\n    public void deleteProblem(Long pid) {\n        boolean isOk = problemEntityService.removeById(pid);\n        // problem的id为其他表的外键的表中的对应数据都会被一起删除！\n        // 删除成功\n        if (isOk) {\n            FileUtil.del(filePathProps.getTestcaseBaseFolder() + File.separator + \"problem_\" + pid);\n        } else {\n            throw new StatusFailException(\"删除失败！\");\n        }\n    }\n\n    @Override\n    public void addProblem(ProblemDTO problemDTO) {\n        QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"problem_id\", problemDTO.getProblem().getProblemId().toUpperCase());\n        Problem problem = problemEntityService.getOne(queryWrapper);\n        if (problem != null) {\n            throw new StatusFailException(\"该题目的Problem ID 已存在，请更换！\");\n        }\n\n        boolean isOk = problemEntityService.adminAddProblem(problemDTO);\n        if (!isOk) {\n            throw new StatusFailException(\"添加失败\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateProblem(ProblemDTO problemDTO) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        boolean isRoot = UserSessionUtil.isRoot();\n        boolean isProblemAdmin = UserSessionUtil.isProblemAdmin();\n        // 只有超级管理员和题目管理员、题目创建者才能操作\n        if (!isRoot && !isProblemAdmin && !userRolesVO.getUsername().equals(problemDTO.getProblem().getAuthor())) {\n            throw new StatusForbiddenException(\"对不起，你无权限修改题目！\");\n        }\n\n        String problemId = problemDTO.getProblem().getProblemId().toUpperCase();\n        QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"problem_id\", problemId);\n        Problem problem = problemEntityService.getOne(queryWrapper);\n\n        // 如果problem_id不是原来的且已存在该problem_id，则修改失败！\n        if (problem != null && problem.getId().longValue() != problemDTO.getProblem().getId()) {\n            throw new StatusFailException(\"当前的Problem ID 已被使用，请更换！\");\n        }\n\n        // 记录修改题目的用户\n        problemDTO.getProblem().setModifiedUser(userRolesVO.getUsername());\n\n        boolean result = problemEntityService.adminUpdateProblem(problemDTO);\n        // 更新成功\n        if (result) {\n            // 说明改了problemId，同步一下judge表\n            if (problem == null) {\n                UpdateWrapper<Judge> judgeUpdateWrapper = new UpdateWrapper<>();\n                judgeUpdateWrapper.eq(\"pid\", problemDTO.getProblem().getId()).set(\"display_pid\", problemId);\n                judgeEntityService.update(judgeUpdateWrapper);\n            }\n\n        } else {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n    @Override\n    public List<ProblemCase> getProblemCases(Long pid, Boolean isUpload) {\n        QueryWrapper<ProblemCase> problemCaseQueryWrapper = new QueryWrapper<>();\n        problemCaseQueryWrapper.eq(\"pid\", pid).eq(\"status\", 0);\n        if (isUpload) {\n            problemCaseQueryWrapper.last(\"order by length(input) asc,input asc\");\n        }\n        return problemCaseEntityService.list(problemCaseQueryWrapper);\n    }\n\n    @Override\n    public CommonResult compileSpj(CompileDTO compileDTO) {\n        compileDTO.setToken(judgeToken);\n        return dispatcher.dispatcher(CallJudgerType.COMPILE, \"/compile-spj\", compileDTO);\n    }\n\n    @Override\n    public CommonResult compileInteractive(CompileDTO compileDTO) {\n        compileDTO.setToken(judgeToken);\n        return dispatcher.dispatcher(CallJudgerType.COMPILE, \"/compile-interactive\", compileDTO);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void importRemoteOjProblem(String name, String problemId) {\n        QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"problem_id\", name.toUpperCase() + \"-\" + problemId);\n        Problem problem = problemEntityService.getOne(queryWrapper);\n        if (problem != null) {\n            throw new StatusFailException(\"该题目已添加，请勿重复添加！\");\n        }\n\n        try {\n            AbstractProblemCrawler.RemoteProblemInfo otherOjProblemInfo = remoteProblemService\n                    .getOtherOJProblemInfo(name.toUpperCase(), problemId);\n            if (otherOjProblemInfo != null) {\n                Problem importProblem = remoteProblemService.adminAddOtherOJProblem(otherOjProblemInfo, name);\n                if (importProblem == null) {\n                    throw new StatusFailException(\"导入新题目失败！请重新尝试！\");\n                }\n            } else {\n                throw new StatusFailException(\"导入新题目失败！原因：可能是与该OJ链接超时或题号格式错误！\");\n            }\n        } catch (Exception e) {\n            log.error(\"导入远程题目异常-------------->\", e);\n            throw new StatusFailException(e.getMessage());\n        }\n    }\n\n    @Override\n    public void changeProblemAuth(Problem problem) {\n        // 普通管理员只能将题目变成隐藏题目和比赛题目\n        boolean root = UserSessionUtil.isRoot();\n\n        boolean problemAdmin = UserSessionUtil.isProblemAdmin();\n\n        if (!problemAdmin && !root && problem.getAuth().equals(ProblemEnum.AUTH_PUBLIC.getCode())) {\n            throw new StatusForbiddenException(\"修改失败！你无权限公开题目！\");\n        }\n\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        UpdateWrapper<Problem> problemUpdateWrapper = new UpdateWrapper<>();\n        problemUpdateWrapper.eq(\"id\", problem.getId()).set(\"auth\", problem.getAuth()).set(\"modified_user\",\n                userRolesVO.getUsername());\n\n        boolean isOk = problemEntityService.update(problemUpdateWrapper);\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/rejudge/RejudgeService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.rejudge;\n\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 16:21\n * @Description:\n */\n\npublic interface RejudgeService {\n\n    Judge rejudge(Long submitId);\n\n    void rejudgeContestProblem(Long cid, Long pid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/rejudge/impl/RejudgeServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.rejudge.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.dao.contest.ContestRecordEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeCaseEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserAcproblemEntityService;\nimport com.simplefanc.voj.backend.judge.local.JudgeTaskDispatcher;\nimport com.simplefanc.voj.backend.judge.remote.RemoteJudgeTaskDispatcher;\nimport com.simplefanc.voj.backend.service.admin.rejudge.RejudgeService;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 16:21\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class RejudgeServiceImpl implements RejudgeService {\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final UserAcproblemEntityService userAcproblemEntityService;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    private final JudgeCaseEntityService judgeCaseEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final JudgeTaskDispatcher judgeTaskDispatcher;\n\n    private final RemoteJudgeTaskDispatcher remoteJudgeTaskDispatcher;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Judge rejudge(Long submitId) {\n        Judge judge = judgeEntityService.getById(submitId);\n\n        boolean isContestSubmission = judge.getCid() != 0;\n        boolean resetContestRecordResult = true;\n\n        // 如果是非比赛题目\n        if (!isContestSubmission) {\n            // 重判前，需要将该题目对应记录表一并更新\n            // 如果该题已经是AC通过状态，更新该题目的用户ac做题表 user_acproblem\n            if (judge.getStatus().intValue() == JudgeStatus.STATUS_ACCEPTED.getStatus().intValue()) {\n                QueryWrapper<UserAcproblem> userAcproblemQueryWrapper = new QueryWrapper<>();\n                userAcproblemQueryWrapper.eq(\"submit_id\", judge.getSubmitId());\n                userAcproblemEntityService.remove(userAcproblemQueryWrapper);\n            }\n        } else {\n            // 将对应比赛记录设置成默认值\n            UpdateWrapper<ContestRecord> updateWrapper = new UpdateWrapper<>();\n            updateWrapper.eq(\"submit_id\", submitId).setSql(\"status=null,score=null\");\n            resetContestRecordResult = contestRecordEntityService.update(updateWrapper);\n        }\n\n        // 清除该提交对应的测试点结果\n        QueryWrapper<JudgeCase> judgeCaseQueryWrapper = new QueryWrapper<>();\n        judgeCaseQueryWrapper.eq(\"submit_id\", submitId);\n        judgeCaseEntityService.remove(judgeCaseQueryWrapper);\n\n        // 设置默认值\n        // 开始进入判题队列\n        judge.setStatus(JudgeStatus.STATUS_PENDING.getStatus());\n        judge.setVersion(judge.getVersion() + 1);\n        judge.setJudger(\"\").setTime(null).setMemory(null).setErrorMessage(null).setOiRankScore(null).setScore(null);\n\n        boolean result = judgeEntityService.updateById(judge);\n\n        if (result && resetContestRecordResult) {\n            // 调用判题服务\n            Problem problem = problemEntityService.getById(judge.getPid());\n            // 如果是远程oj判题\n            if (problem.getIsRemote()) {\n                remoteJudgeTaskDispatcher.sendTask(judge, problem.getProblemId(), isContestSubmission);\n            } else {\n                judgeTaskDispatcher.sendTask(judge, isContestSubmission);\n            }\n            return judge;\n        } else {\n            throw new StatusFailException(\"重判失败！请重新尝试！\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void rejudgeContestProblem(Long cid, Long pid) {\n        QueryWrapper<Judge> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"cid\", cid).eq(\"pid\", pid);\n        List<Judge> rejudgeList = judgeEntityService.list(queryWrapper);\n\n        if (rejudgeList.size() == 0) {\n            throw new StatusFailException(\"当前该题目无提交，不可重判！\");\n        }\n\n        List<Long> submitIdList = new LinkedList<>();\n        HashMap<Long, Integer> idMapStatus = new HashMap<>();\n        // 全部设置默认值\n        for (Judge judge : rejudgeList) {\n            idMapStatus.put(judge.getSubmitId(), judge.getStatus());\n            // 开始进入判题队列\n            judge.setStatus(JudgeStatus.STATUS_PENDING.getStatus());\n            judge.setVersion(judge.getVersion() + 1);\n            judge.setJudger(\"\").setTime(null).setMemory(null).setErrorMessage(null).setOiRankScore(null).setScore(null);\n            submitIdList.add(judge.getSubmitId());\n        }\n        boolean resetJudgeResult = judgeEntityService.updateBatchById(rejudgeList);\n        // 清除每个提交对应的测试点结果\n        QueryWrapper<JudgeCase> judgeCaseQueryWrapper = new QueryWrapper<>();\n        judgeCaseQueryWrapper.in(\"submit_id\", submitIdList);\n        judgeCaseEntityService.remove(judgeCaseQueryWrapper);\n        // 将对应比赛记录设置成默认值\n        UpdateWrapper<ContestRecord> updateWrapper = new UpdateWrapper<>();\n        updateWrapper.in(\"submit_id\", submitIdList).setSql(\"status=null,score=null\");\n        boolean resetContestRecordResult = contestRecordEntityService.update(updateWrapper);\n\n        if (resetContestRecordResult && resetJudgeResult) {\n            // 调用重判服务\n            Problem problem = problemEntityService.getById(pid);\n            // 如果是远程oj判题\n            if (problem.getIsRemote()) {\n                for (Judge judge : rejudgeList) {\n                    // 进入重判队列，等待调用判题服务\n                    remoteJudgeTaskDispatcher.sendTask(judge, problem.getProblemId(), judge.getCid() != 0);\n                }\n            } else {\n                for (Judge judge : rejudgeList) {\n                    // 进入重判队列，等待调用判题服务\n                    judgeTaskDispatcher.sendTask(judge, judge.getCid() != 0);\n                }\n            }\n        } else {\n            throw new StatusFailException(\"重判失败！请重新尝试！\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/system/ConfigService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.system;\n\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.backend.pojo.dto.*;\nimport com.simplefanc.voj.common.result.CommonResult;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:50\n * @Description: 动态修改网站配置，获取后台服务状态及判题服务器的状态\n */\npublic interface ConfigService {\n\n    /**\n     * @MethodName getServiceInfo\n     * @Params * @param null\n     * @Description 获取当前服务的相关信息以及当前系统的cpu情况，内存使用情况\n     * @Return CommonResult\n     * @Since 2021/12/3\n     */\n    JSONObject getServiceInfo();\n\n    List<JSONObject> getJudgeServiceInfo();\n\n    WebConfigDTO getWebConfig();\n\n    void setWebConfig(WebConfigDTO webConfigDTO);\n\n    void deleteHomeCarousel(Long id);\n\n    EmailConfigDTO getEmailConfig();\n\n    void setEmailConfig(EmailConfigDTO config);\n\n    void testEmail(TestEmailDTO testEmailDTO);\n\n    DbAndRedisConfigDTO getDbAndRedisConfig();\n\n    void setDbAndRedisConfig(DbAndRedisConfigDTO config);\n\n    boolean sendNewConfigToNacos();\n\n    SwitchConfigDTO getSwitchConfig();\n\n    void setSwitchConfig(SwitchConfigDTO config);\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/system/DashboardService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.system;\n\nimport com.simplefanc.voj.common.pojo.entity.user.Session;\n\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:44\n * @Description:\n */\npublic interface DashboardService {\n\n    Session getRecentSession();\n\n    Map<Object, Object> getDashboardInfo();\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/system/impl/ConfigServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.system.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.lang.Validator;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport cn.hutool.system.oshi.OshiUtil;\nimport com.alibaba.nacos.api.NacosFactory;\nimport com.alibaba.nacos.api.config.ConfigType;\nimport com.alibaba.nacos.api.exception.NacosException;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.utils.ConfigUtil;\nimport com.simplefanc.voj.backend.common.utils.RestTemplateUtil;\nimport com.simplefanc.voj.backend.config.ConfigVO;\nimport com.simplefanc.voj.backend.dao.common.FileEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.*;\nimport com.simplefanc.voj.backend.service.admin.system.ConfigService;\nimport com.simplefanc.voj.backend.service.email.EmailService;\nimport com.simplefanc.voj.common.pojo.entity.common.File;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.client.discovery.DiscoveryClient;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Properties;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:50\n * @Description: 动态修改网站配置，获取后台服务状态及判题服务器的状态\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class ConfigServiceImpl implements ConfigService {\n\n    private final ConfigVO configVO;\n\n    private final EmailService emailService;\n\n    private final FileEntityService fileEntityService;\n\n    private final ConfigUtil configUtil;\n\n    private final DiscoveryClient discoveryClient;\n\n    private final RestTemplateUtil restTemplateUtil;\n\n    @Value(\"${service-url.name}\")\n    private String judgeServiceName;\n\n    @Value(\"${spring.application.name}\")\n    private String currentServiceName;\n\n    @Value(\"${spring.cloud.nacos.discovery.server-addr}\")\n    private String nacosServerAddr;\n\n    @Value(\"voj\" + \"-\" + \"${spring.profiles.active}\" + \".yml\")\n    private String dataId;\n\n    @Value(\"${spring.cloud.nacos.config.group}\")\n    private String group;\n\n    @Value(\"${spring.cloud.nacos.config.username}\")\n    private String nacosUsername;\n\n    @Value(\"${spring.cloud.nacos.config.password}\")\n    private String nacosPassword;\n\n    /**\n     * @MethodName getServiceInfo\n     * @Params @param null\n     * @Description 获取当前服务的相关信息以及当前系统的cpu情况，内存使用情况\n     * @Return CommonResult\n     * @Since 2021/12/3\n     */\n    @Override\n    public JSONObject getServiceInfo() {\n        JSONObject result = new JSONObject();\n\n        List<ServiceInstance> serviceInstances = discoveryClient.getInstances(currentServiceName);\n        String response = restTemplateUtil.get(nacosServerAddr, \"/nacos/v1/ns/operator/metrics\", String.class);\n\n        JSONObject jsonObject = JSONUtil.parseObj(response);\n        // 获取当前数据后台所在机器环境\n        // 当前机器的cpu核数\n        int cores = OshiUtil.getCpuInfo().getCpuNum();\n        double cpuLoad = 100 - OshiUtil.getCpuInfo().getFree();\n        // 当前服务所在机器cpu使用率\n        String percentCpuLoad = String.format(\"%.2f\", cpuLoad) + \"%\";\n        // 当前服务所在机器总内存\n        double totalVirtualMemory = OshiUtil.getMemory().getTotal();\n        // 当前服务所在机器空闲内存\n        double freePhysicalMemorySize = OshiUtil.getMemory().getAvailable();\n        double value = freePhysicalMemorySize / totalVirtualMemory;\n        // 当前服务所在机器内存使用率\n        String percentMemoryLoad = String.format(\"%.2f\", (1 - value) * 100) + \"%\";\n        result.set(\"nacos\", jsonObject);\n        result.set(\"backupCores\", cores);\n        result.set(\"backupService\", serviceInstances);\n        result.set(\"backupPercentCpuLoad\", percentCpuLoad);\n        result.set(\"backupPercentMemoryLoad\", percentMemoryLoad);\n        return result;\n    }\n\n    @Override\n    public List<JSONObject> getJudgeServiceInfo() {\n        List<JSONObject> serviceInfoList = new LinkedList<>();\n        List<ServiceInstance> serviceInstances = discoveryClient.getInstances(judgeServiceName);\n        for (ServiceInstance serviceInstance : serviceInstances) {\n            String result = restTemplateUtil.get(serviceInstance.getUri(), \"/get-sys-config\", String.class);\n            JSONObject jsonObject = JSONUtil.parseObj(result);\n            jsonObject.set(\"service\", serviceInstance);\n            serviceInfoList.add(jsonObject);\n        }\n        return serviceInfoList;\n    }\n\n    @Override\n    public WebConfigDTO getWebConfig() {\n        return BeanUtil.copyProperties(configVO, WebConfigDTO.class);\n//        return WebConfigDTO.builder().baseUrl(UnicodeUtil.toString(configVO.getBaseUrl()))\n//                .name(UnicodeUtil.toString(configVO.getName()))\n//                .shortName(UnicodeUtil.toString(configVO.getShortName()))\n//                .description(UnicodeUtil.toString(configVO.getDescription()))\n//                .register(configVO.getRegister())\n//                .problem(configVO.getProblem())\n//                .training(configVO.getTraining())\n//                .contest(configVO.getContest())\n//                .status(configVO.getStatus())\n//                .rank(configVO.getRank())\n//                .discussion(configVO.getDiscussion())\n//                .introduction(configVO.getIntroduction())\n//                .recordName(UnicodeUtil.toString(configVO.getRecordName()))\n//                .recordUrl(UnicodeUtil.toString(configVO.getRecordUrl()))\n//                .projectName(UnicodeUtil.toString(configVO.getProjectName()))\n//                .projectUrl(UnicodeUtil.toString(configVO.getProjectUrl())).build();\n    }\n\n    @Override\n    public void setWebConfig(WebConfigDTO webConfigDTO) {\n        if (StrUtil.isNotEmpty(webConfigDTO.getBaseUrl())) {\n            configVO.setBaseUrl(webConfigDTO.getBaseUrl());\n        }\n        if (StrUtil.isNotEmpty(webConfigDTO.getName())) {\n            configVO.setName(webConfigDTO.getName());\n        }\n        if (StrUtil.isNotEmpty(webConfigDTO.getShortName())) {\n            configVO.setShortName(webConfigDTO.getShortName());\n        }\n        if (StrUtil.isNotEmpty(webConfigDTO.getDescription())) {\n            configVO.setDescription(webConfigDTO.getDescription());\n        }\n//        if (webConfigDTO.getRegister() != null) {\n//            configVO.setRegister(webConfigDTO.getRegister());\n//        }\n//        if (webConfigDTO.getCodeVisibleStartTime() != null) {\n//            configVO.setCodeVisibleStartTime(webConfigDTO.getCodeVisibleStartTime());\n//        }\n        if (StrUtil.isNotEmpty(webConfigDTO.getRecordName())) {\n            configVO.setRecordName(webConfigDTO.getRecordName());\n        }\n        if (StrUtil.isNotEmpty(webConfigDTO.getRecordUrl())) {\n            configVO.setRecordUrl(webConfigDTO.getRecordUrl());\n        }\n        if (StrUtil.isNotEmpty(webConfigDTO.getProjectName())) {\n            configVO.setProjectName(webConfigDTO.getProjectName());\n        }\n        if (StrUtil.isNotEmpty(webConfigDTO.getProjectUrl())) {\n            configVO.setProjectUrl(webConfigDTO.getProjectUrl());\n        }\n\n        boolean isOk = sendNewConfigToNacos();\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n    @Override\n    public void deleteHomeCarousel(Long id) {\n        File imgFile = fileEntityService.getById(id);\n        if (imgFile == null) {\n            throw new StatusFailException(\"文件id错误，图片不存在\");\n        }\n        boolean isOk = fileEntityService.removeById(id);\n        if (isOk) {\n            FileUtil.del(imgFile.getFilePath());\n        } else {\n            throw new StatusFailException(\"删除失败！\");\n        }\n    }\n\n    @Override\n    public EmailConfigDTO getEmailConfig() {\n        return BeanUtil.copyProperties(configVO, EmailConfigDTO.class);\n//        return EmailConfigDTO.builder().emailUsername(configVO.getEmailUsername())\n//                .emailPassword(configVO.getEmailPassword()).emailHost(configVO.getEmailHost())\n//                .emailPort(configVO.getEmailPort()).emailSsl(configVO.getEmailSsl()).build();\n    }\n\n    @Override\n    public void setEmailConfig(EmailConfigDTO config) {\n        if (StrUtil.isNotEmpty(config.getEmailHost())) {\n            configVO.setEmailHost(config.getEmailHost());\n        }\n        if (StrUtil.isNotEmpty(config.getEmailPassword())) {\n            configVO.setEmailPassword(config.getEmailPassword());\n        }\n\n        if (config.getEmailPort() != null) {\n            configVO.setEmailPort(config.getEmailPort());\n        }\n\n        if (StrUtil.isNotEmpty(config.getEmailUsername())) {\n            configVO.setEmailUsername(config.getEmailUsername());\n        }\n\n        if (config.getEmailSsl() != null) {\n            configVO.setEmailSsl(config.getEmailSsl());\n        }\n\n        boolean isOk = sendNewConfigToNacos();\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n    @Override\n    public void testEmail(TestEmailDTO testEmailDTO) {\n        String email = testEmailDTO.getEmail();\n        if (StrUtil.isEmpty(email)) {\n            throw new StatusFailException(\"测试的邮箱不能为空！\");\n        }\n        boolean isEmail = Validator.isEmail(email);\n        if (isEmail) {\n            emailService.testEmail(email);\n        } else {\n            throw new StatusFailException(\"测试的邮箱格式不正确！\");\n        }\n    }\n\n    @Override\n    public DbAndRedisConfigDTO getDbAndRedisConfig() {\n        return BeanUtil.copyProperties(configVO, DbAndRedisConfigDTO.class);\n//        return DbAndRedisConfigDTO.builder().dbName(configVO.getMysqlDbName()).dbHost(configVO.getMysqlHost())\n//                .dbPort(configVO.getMysqlPort()).dbUsername(configVO.getMysqlUsername())\n//                .dbPassword(configVO.getMysqlPassword()).redisHost(configVO.getRedisHost())\n//                .redisPort(configVO.getRedisPort()).redisPassword(configVO.getRedisPassword()).build();\n    }\n\n    @Override\n    public void setDbAndRedisConfig(DbAndRedisConfigDTO config) {\n        if (StrUtil.isNotEmpty(config.getDbName())) {\n            configVO.setMysqlDbName(config.getDbName());\n        }\n\n        if (StrUtil.isNotEmpty(config.getDbHost())) {\n            configVO.setMysqlHost(config.getDbHost());\n        }\n        if (config.getDbPort() != null) {\n            configVO.setMysqlPort(config.getDbPort());\n        }\n        if (StrUtil.isNotEmpty(config.getDbUsername())) {\n            configVO.setMysqlUsername(config.getDbUsername());\n        }\n        if (StrUtil.isNotEmpty(config.getDbPassword())) {\n            configVO.setMysqlPassword(config.getDbPassword());\n        }\n\n        if (StrUtil.isNotEmpty(config.getRedisHost())) {\n            configVO.setRedisHost(config.getRedisHost());\n        }\n\n        if (config.getRedisPort() != null) {\n            configVO.setRedisPort(config.getRedisPort());\n        }\n        if (StrUtil.isNotEmpty(config.getRedisPassword())) {\n            configVO.setRedisPassword(config.getRedisPassword());\n        }\n\n        boolean isOk = sendNewConfigToNacos();\n\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n    @Override\n    public boolean sendNewConfigToNacos() {\n        Properties properties = new Properties();\n        properties.put(\"serverAddr\", nacosServerAddr);\n\n        // if need username and password to login\n        properties.put(\"username\", nacosUsername);\n        properties.put(\"password\", nacosPassword);\n\n        com.alibaba.nacos.api.config.ConfigService configService;\n        boolean isOk = false;\n        try {\n            configService = NacosFactory.createConfigService(properties);\n            isOk = configService.publishConfig(dataId, group,\n                    configUtil.getConfigContent(), ConfigType.YAML.getType());\n        } catch (NacosException e) {\n            log.error(\"通过 Nacos 修改网站配置异常--------------->\", e);\n        }\n        return isOk;\n    }\n\n    @Override\n    public SwitchConfigDTO getSwitchConfig() {\n        return BeanUtil.copyProperties(configVO, SwitchConfigDTO.class);\n//        return SwitchConfigDTO.builder()\n//                .openPublicDiscussion(configVO.getOpenPublicDiscussion())\n//                .openContestComment(configVO.getOpenContestComment())\n//                .openPublicJudge(configVO.getOpenPublicJudge())\n//                .openContestJudge(configVO.getOpenContestJudge())\n//                .defaultSubmitInterval(configVO.getDefaultSubmitInterval())\n//                .build();\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void setSwitchConfig(SwitchConfigDTO config) {\n//        if (config.getOpenPublicDiscussion() != null) {\n//            configVO.setOpenPublicDiscussion(config.getOpenPublicDiscussion());\n//        }\n//        if (config.getOpenContestComment() != null) {\n//            configVO.setOpenContestComment(config.getOpenContestComment());\n//        }\n        if (config.getOpenPublicJudge() != null) {\n            configVO.setOpenPublicJudge(config.getOpenPublicJudge());\n        }\n        if (config.getOpenContestJudge() != null) {\n            configVO.setOpenContestJudge(config.getOpenContestJudge());\n        }\n        if (config.getDefaultSubmitInterval() != null) {\n            if (config.getDefaultSubmitInterval() >= 0) {\n                configVO.setDefaultSubmitInterval(config.getDefaultSubmitInterval());\n            } else {\n                configVO.setDefaultSubmitInterval(0);\n            }\n        }\n        if (config.getCodeVisibleStartTime() != null) {\n            configVO.setCodeVisibleStartTime(config.getCodeVisibleStartTime());\n        }\n\n        if (config.getRegister() != null) {\n            configVO.setRegister(config.getRegister());\n        }\n        if (config.getProblem() != null) {\n            configVO.setProblem(config.getProblem());\n        }\n        if (config.getTraining() != null) {\n            configVO.setTraining(config.getTraining());\n        }\n        if (config.getContest() != null) {\n            configVO.setContest(config.getContest());\n        }\n        if (config.getStatus() != null) {\n            configVO.setStatus(config.getStatus());\n        }\n        if (config.getRank() != null) {\n            configVO.setRank(config.getRank());\n        }\n        if (config.getDiscussion() != null) {\n            configVO.setDiscussion(config.getDiscussion());\n        }\n        if (config.getIntroduction() != null) {\n            configVO.setIntroduction(config.getIntroduction());\n        }\n        boolean isOk = sendNewConfigToNacos();\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/system/impl/DashboardServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.system.impl;\n\nimport cn.hutool.core.map.MapUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.user.SessionEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.admin.system.DashboardService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.pojo.entity.user.Session;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:44\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class DashboardServiceImpl implements DashboardService {\n\n    private final ContestEntityService contestEntityService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final SessionEntityService sessionEntityService;\n\n    @Override\n    public Session getRecentSession() {\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        QueryWrapper<Session> wrapper = new QueryWrapper<Session>().eq(\"uid\", userRolesVO.getUid())\n                .orderByDesc(\"gmt_create\");\n        List<Session> sessionList = sessionEntityService.list(wrapper);\n        if (sessionList.size() > 1) {\n            return sessionList.get(1);\n        } else {\n            return sessionList.get(0);\n        }\n    }\n\n    @Override\n    public Map<Object, Object> getDashboardInfo() {\n        int userNum = userInfoEntityService.count();\n        int recentContestNum = contestEntityService.getWithinNext14DaysContests().size();\n        int todayJudgeNum = judgeEntityService.getTodayJudgeNum();\n        // TODO put 键\n        return MapUtil.builder().put(\"userNum\", userNum).put(\"recentContestNum\", recentContestNum)\n                .put(\"todayJudgeNum\", todayJudgeNum).map();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/tag/AdminTagService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.tag;\n\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport com.simplefanc.voj.common.pojo.entity.problem.TagClassification;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 17:47\n * @Description:\n */\n\npublic interface AdminTagService {\n\n    Tag addTag(Tag tag);\n\n    void updateTag(Tag tag);\n\n    void deleteTag(Long tid);\n\n    List<TagClassification> getTagClassification(String oj);\n\n    TagClassification addTagClassification(TagClassification tagClassification);\n\n    void updateTagClassification(TagClassification tagClassification);\n\n    void deleteTagClassification(Long tcid);\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/tag/impl/AdminTagServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.tag.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.dao.problem.TagClassificationEntityService;\nimport com.simplefanc.voj.backend.dao.problem.TagEntityService;\nimport com.simplefanc.voj.backend.service.admin.tag.AdminTagService;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport com.simplefanc.voj.common.pojo.entity.problem.TagClassification;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 17:47\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class AdminTagServiceImpl implements AdminTagService {\n\n    private final TagEntityService tagEntityService;\n    private final TagClassificationEntityService tagClassificationEntityService;\n\n    @Override\n    public Tag addTag(Tag tag) {\n        QueryWrapper<Tag> tagQueryWrapper = new QueryWrapper<>();\n        tagQueryWrapper.eq(\"name\", tag.getName()).eq(\"oj\", tag.getOj());\n        Tag existTag = tagEntityService.getOne(tagQueryWrapper, false);\n\n        if (existTag != null) {\n            throw new StatusFailException(\"该标签名称已存在！请勿重复添加！\");\n        }\n\n        boolean isOk = tagEntityService.save(tag);\n        if (!isOk) {\n            throw new StatusFailException(\"添加失败\");\n        }\n        return tag;\n    }\n\n    @Override\n    public void updateTag(Tag tag) {\n        boolean isOk = tagEntityService.updateById(tag);\n        if (!isOk) {\n            throw new StatusFailException(\"更新失败\");\n        }\n    }\n\n    @Override\n    public void deleteTag(Long tid) {\n        boolean isOk = tagEntityService.removeById(tid);\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败\");\n        }\n    }\n\n    @Override\n    public List<TagClassification> getTagClassification(String oj) {\n        oj = oj.toUpperCase();\n        if (\"ALL\".equals(oj)) {\n            return tagClassificationEntityService.list();\n        } else {\n            QueryWrapper<TagClassification> tagClassificationQueryWrapper = new QueryWrapper<>();\n            tagClassificationQueryWrapper.eq(\"oj\", oj).orderByAsc(\"`rank`\");\n            return tagClassificationEntityService.list(tagClassificationQueryWrapper);\n        }\n    }\n\n    @Override\n    public TagClassification addTagClassification(TagClassification tagClassification) throws StatusFailException {\n        QueryWrapper<TagClassification> tagClassificationQueryWrapper = new QueryWrapper<>();\n        tagClassificationQueryWrapper.eq(\"name\", tagClassification.getName())\n                .eq(\"oj\", tagClassification.getOj());\n        TagClassification existTagClassification = tagClassificationEntityService.getOne(tagClassificationQueryWrapper, false);\n\n        if (existTagClassification != null) {\n            throw new StatusFailException(\"该标签分类名称已存在！请勿重复！\");\n        }\n        boolean isOk = tagClassificationEntityService.save(tagClassification);\n        if (!isOk) {\n            throw new StatusFailException(\"添加失败\");\n        }\n        return tagClassification;\n    }\n\n    @Override\n    public void updateTagClassification(TagClassification tagClassification) throws StatusFailException {\n        boolean isOk = tagClassificationEntityService.updateById(tagClassification);\n        if (!isOk) {\n            throw new StatusFailException(\"更新失败\");\n        }\n    }\n\n    @Override\n    public void deleteTagClassification(Long tcid) throws StatusFailException {\n        boolean isOk = tagClassificationEntityService.removeById(tcid);\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/training/AdminTrainingCategoryService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.training;\n\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 19:29\n * @Description:\n */\npublic interface AdminTrainingCategoryService {\n\n    TrainingCategory addTrainingCategory(TrainingCategory trainingCategory);\n\n    void updateTrainingCategory(TrainingCategory trainingCategory);\n\n    void deleteTrainingCategory(Long cid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/training/AdminTrainingProblemService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.training;\n\nimport com.simplefanc.voj.backend.pojo.dto.TrainingProblemDTO;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingProblem;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 20:20\n * @Description:\n */\n\npublic interface AdminTrainingProblemService {\n\n    HashMap<String, Object> getProblemList(Integer limit, Integer currentPage, String keyword, Boolean queryExisted,\n                                           Long tid);\n\n    void updateProblem(TrainingProblem trainingProblem);\n\n    void deleteProblem(Long pid, Long tid);\n\n    void addProblemFromPublic(TrainingProblemDTO trainingProblemDTO);\n\n    void importTrainingRemoteOjProblem(String name, String problemId, Long tid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/training/AdminTrainingRecordService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.training;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.constants.TrainingEnum;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingProblemEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingRecordEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingRegisterEntityService;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingProblem;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRecord;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 20:09\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class AdminTrainingRecordService {\n\n    private final TrainingEntityService trainingEntityService;\n\n    private final TrainingProblemEntityService trainingProblemEntityService;\n\n    private final TrainingRecordEntityService trainingRecordEntityService;\n\n    private final TrainingRegisterEntityService trainingRegisterEntityService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    @Async\n    public void syncUserSubmissionToRecordByTid(Long tid, String uid) {\n        QueryWrapper<TrainingProblem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"tid\", tid);\n        List<TrainingProblem> trainingProblemList = trainingProblemEntityService.list(queryWrapper);\n        List<Long> pidList = new ArrayList<>();\n        HashMap<Long, Long> pidMapTPid = new HashMap<>();\n        for (TrainingProblem trainingProblem : trainingProblemList) {\n            pidList.add(trainingProblem.getPid());\n            pidMapTPid.put(trainingProblem.getPid(), trainingProblem.getId());\n        }\n        if (!CollectionUtils.isEmpty(pidList)) {\n            QueryWrapper<Judge> judgeQueryWrapper = new QueryWrapper<>();\n            judgeQueryWrapper.in(\"pid\", pidList).eq(\"cid\", 0)\n                    // 只同步ac的提交\n                    .eq(\"status\", JudgeStatus.STATUS_ACCEPTED.getStatus()).eq(\"uid\", uid);\n            List<Judge> judgeList = judgeEntityService.list(judgeQueryWrapper);\n            saveBatchNewRecordByJudgeList(judgeList, tid, null, pidMapTPid);\n        }\n    }\n\n    @Async\n    public void syncAlreadyRegisterUserRecord(Long tid, Long pid, Long tpId) {\n        Training training = trainingEntityService.getById(tid);\n        if (!TrainingEnum.AUTH_PRIVATE.getValue().equals(training.getAuth())) {\n            return;\n        }\n        List<String> uidList = trainingRegisterEntityService.getAlreadyRegisterUidList(tid);\n        syncNewProblemUserSubmissionToRecord(pid, tpId, tid, uidList);\n    }\n\n    @Async\n    public void checkSyncRecord(Training training) {\n        if (!TrainingEnum.AUTH_PRIVATE.getValue().equals(training.getAuth())) {\n            return;\n        }\n        QueryWrapper<TrainingRecord> trainingRecordQueryWrapper = new QueryWrapper<>();\n        trainingRecordQueryWrapper.eq(\"tid\", training.getId());\n        int count = trainingRecordEntityService.count(trainingRecordQueryWrapper);\n        if (count <= 0) {\n            syncAllUserProblemRecord(training.getId());\n        }\n\n    }\n\n    private void syncNewProblemUserSubmissionToRecord(Long pid, Long tpId, Long tid, List<String> uidList) {\n        if (!CollectionUtils.isEmpty(uidList)) {\n            QueryWrapper<Judge> judgeQueryWrapper = new QueryWrapper<>();\n            judgeQueryWrapper.eq(\"pid\", pid).eq(\"cid\", 0)\n                    // 只同步ac的提交\n                    .eq(\"status\", JudgeStatus.STATUS_ACCEPTED.getStatus()).in(\"uid\", uidList);\n            List<Judge> judgeList = judgeEntityService.list(judgeQueryWrapper);\n            saveBatchNewRecordByJudgeList(judgeList, tid, tpId, null);\n        }\n    }\n\n    private void syncAllUserProblemRecord(Long tid) {\n        QueryWrapper<TrainingProblem> trainingProblemQueryWrapper = new QueryWrapper<>();\n        trainingProblemQueryWrapper.eq(\"tid\", tid);\n        List<TrainingProblem> trainingProblemList = trainingProblemEntityService.list(trainingProblemQueryWrapper);\n        if (trainingProblemList.size() == 0) {\n            return;\n        }\n        List<Long> pidList = new ArrayList<>();\n        HashMap<Long, Long> pidMapTPid = new HashMap<>();\n        for (TrainingProblem trainingProblem : trainingProblemList) {\n            pidList.add(trainingProblem.getPid());\n            pidMapTPid.put(trainingProblem.getPid(), trainingProblem.getId());\n        }\n\n        List<String> uidList = trainingRegisterEntityService.getAlreadyRegisterUidList(tid);\n        if (uidList.size() == 0) {\n            return;\n        }\n        QueryWrapper<Judge> judgeQueryWrapper = new QueryWrapper<>();\n        judgeQueryWrapper.in(\"pid\", pidList).eq(\"cid\", 0)\n                // 只同步ac的提交\n                .eq(\"status\", JudgeStatus.STATUS_ACCEPTED.getStatus()).in(\"uid\", uidList);\n        List<Judge> judgeList = judgeEntityService.list(judgeQueryWrapper);\n        saveBatchNewRecordByJudgeList(judgeList, tid, null, pidMapTPid);\n    }\n\n    private void saveBatchNewRecordByJudgeList(List<Judge> judgeList, Long tid, Long tpId,\n                                               HashMap<Long, Long> pidMapTPid) {\n        if (!CollectionUtils.isEmpty(judgeList)) {\n            List<TrainingRecord> trainingRecordList = new ArrayList<>();\n            for (Judge judge : judgeList) {\n                TrainingRecord trainingRecord = new TrainingRecord().setPid(judge.getPid())\n                        .setSubmitId(judge.getSubmitId()).setTid(tid).setUid(judge.getUid());\n                if (pidMapTPid != null) {\n                    trainingRecord.setTpid(pidMapTPid.get(judge.getPid()));\n                }\n                if (tpId != null) {\n                    trainingRecord.setTpid(tpId);\n                }\n                trainingRecordList.add(trainingRecord);\n            }\n            trainingRecordEntityService.saveBatch(trainingRecordList);\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/training/AdminTrainingService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.training;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.TrainingDTO;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 19:46\n * @Description:\n */\npublic interface AdminTrainingService {\n\n    IPage<Training> getTrainingList(Integer limit, Integer currentPage, String keyword);\n\n    TrainingDTO getTraining(Long tid);\n\n    void deleteTraining(Long tid);\n\n    void addTraining(TrainingDTO trainingDTO);\n\n    void updateTraining(TrainingDTO trainingDTO);\n\n    void changeTrainingStatus(Long tid, String author, Boolean status);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/training/impl/AdminTrainingCategoryServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.training.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.dao.training.TrainingCategoryEntityService;\nimport com.simplefanc.voj.backend.service.admin.training.AdminTrainingCategoryService;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 19:29\n * @Description:\n */\n\n@Service\n@RequiredArgsConstructor\npublic class AdminTrainingCategoryServiceImpl implements AdminTrainingCategoryService {\n\n    private final TrainingCategoryEntityService trainingCategoryEntityService;\n\n    @Override\n    public TrainingCategory addTrainingCategory(TrainingCategory trainingCategory) {\n        QueryWrapper<TrainingCategory> trainingCategoryQueryWrapper = new QueryWrapper<>();\n        trainingCategoryQueryWrapper.eq(\"name\", trainingCategory.getName());\n        TrainingCategory existedTrainingCategory = trainingCategoryEntityService.getOne(trainingCategoryQueryWrapper,\n                false);\n\n        if (existedTrainingCategory != null) {\n            throw new StatusFailException(\"该分类名称已存在！请勿重复添加！\");\n        }\n\n        boolean isOk = trainingCategoryEntityService.save(trainingCategory);\n        if (!isOk) {\n            throw new StatusFailException(\"添加失败\");\n        }\n        return trainingCategory;\n    }\n\n    @Override\n    public void updateTrainingCategory(TrainingCategory trainingCategory) {\n        boolean isOk = trainingCategoryEntityService.updateById(trainingCategory);\n        if (!isOk) {\n            throw new StatusFailException(\"更新失败！\");\n        }\n    }\n\n    @Override\n    public void deleteTrainingCategory(Long cid) {\n        boolean isOk = trainingCategoryEntityService.removeById(cid);\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败！\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/training/impl/AdminTrainingProblemServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.training.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingProblemEntityService;\nimport com.simplefanc.voj.backend.judge.remote.crawler.AbstractProblemCrawler;\nimport com.simplefanc.voj.backend.pojo.dto.TrainingProblemDTO;\nimport com.simplefanc.voj.backend.service.admin.problem.RemoteProblemService;\nimport com.simplefanc.voj.backend.service.admin.training.AdminTrainingProblemService;\nimport com.simplefanc.voj.backend.service.admin.training.AdminTrainingRecordService;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingProblem;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.io.File;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 20:20\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\n@Slf4j(topic = \"voj\")\npublic class AdminTrainingProblemServiceImpl implements AdminTrainingProblemService {\n\n    private final TrainingProblemEntityService trainingProblemEntityService;\n\n    private final TrainingEntityService trainingEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final AdminTrainingRecordService adminTrainingRecordService;\n\n    private final RemoteProblemService remoteProblemService;\n\n    private final FilePathProperties filePathProps;\n\n    @Override\n    public HashMap<String, Object> getProblemList(Integer limit, Integer currentPage, String keyword,\n                                                  Boolean queryExisted, Long tid) {\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n\n        IPage<Problem> iPage = new Page<>(currentPage, limit);\n        // 根据tid在TrainingProblem表中查询到对应pid集合\n        QueryWrapper<TrainingProblem> trainingProblemQueryWrapper = new QueryWrapper<>();\n        trainingProblemQueryWrapper.eq(\"tid\", tid).orderByAsc(\"display_id\");\n        List<Long> pidList = new LinkedList<>();\n        List<TrainingProblem> trainingProblemList = trainingProblemEntityService.list(trainingProblemQueryWrapper);\n        HashMap<Long, TrainingProblem> trainingProblemMap = new HashMap<>();\n        trainingProblemList.forEach(trainingProblem -> {\n            if (!trainingProblemMap.containsKey(trainingProblem.getPid())) {\n                trainingProblemMap.put(trainingProblem.getPid(), trainingProblem);\n            }\n            pidList.add(trainingProblem.getPid());\n        });\n        // TODO put 键\n        HashMap<String, Object> trainingProblem = new HashMap<>();\n        // 该训练原本就无题目数据\n        if (pidList.size() == 0 && queryExisted) {\n            trainingProblem.put(\"problemList\", pidList);\n            trainingProblem.put(\"contestProblemMap\", trainingProblemMap);\n            return trainingProblem;\n        }\n\n        QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();\n\n        // 逻辑判断，如果是查询已有的就应该是in，如果是查询不要重复的，使用not in\n        if (queryExisted) {\n            problemQueryWrapper.in(pidList.size() > 0, \"id\", pidList);\n        } else {\n            // 权限需要是公开的（隐藏的，比赛中不可加入！）\n            problemQueryWrapper.eq(\"auth\", 1);\n            problemQueryWrapper.notIn(pidList.size() > 0, \"id\", pidList);\n        }\n\n        if (StrUtil.isNotEmpty(keyword)) {\n            problemQueryWrapper.and(wrapper -> wrapper.like(\"title\", keyword).or().like(\"problem_id\", keyword).or()\n                    .like(\"author\", keyword));\n        }\n\n        IPage<Problem> problemListPager = problemEntityService.page(iPage, problemQueryWrapper);\n\n        if (queryExisted) {\n            List<Problem> problemListPagerRecords = problemListPager.getRecords();\n            List<Problem> sortProblemList = problemListPagerRecords.stream()\n                    .sorted(Comparator.comparingInt(problem -> trainingProblemMap.get(problem.getId()).getRank()))\n                    .collect(Collectors.toList());\n            problemListPager.setRecords(sortProblemList);\n        }\n        trainingProblem.put(\"problemList\", problemListPager);\n        trainingProblem.put(\"trainingProblemMap\", trainingProblemMap);\n        return trainingProblem;\n    }\n\n    @Override\n    public void updateProblem(TrainingProblem trainingProblem) {\n        boolean isOk = trainingProblemEntityService.saveOrUpdate(trainingProblem);\n\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败！\");\n        }\n    }\n\n    @Override\n    public void deleteProblem(Long pid, Long tid) {\n        boolean isOk;\n        // 训练id不为null，表示就是从比赛列表移除而已\n        if (tid != null) {\n            QueryWrapper<TrainingProblem> trainingProblemQueryWrapper = new QueryWrapper<>();\n            trainingProblemQueryWrapper.eq(\"tid\", tid).eq(\"pid\", pid);\n            isOk = trainingProblemEntityService.remove(trainingProblemQueryWrapper);\n        } else {\n            // problem的id为其他表的外键的表中的对应数据都会被一起删除！\n            isOk = problemEntityService.removeById(pid);\n        }\n\n        if (isOk) {\n            if (tid == null) {\n                FileUtil.del(filePathProps.getTestcaseBaseFolder() + File.separator + \"problem_\" + pid);\n            }\n\n            // 更新训练最近更新时间\n            UpdateWrapper<Training> trainingUpdateWrapper = new UpdateWrapper<>();\n            trainingUpdateWrapper.set(\"gmt_modified\", new Date()).eq(\"id\", tid);\n            trainingEntityService.update(trainingUpdateWrapper);\n\n        } else {\n            throw new StatusFailException(\"删除失败！\");\n        }\n    }\n\n    @Override\n    public void addProblemFromPublic(TrainingProblemDTO trainingProblemDTO) {\n\n        Long pid = trainingProblemDTO.getPid();\n        Long tid = trainingProblemDTO.getTid();\n        String displayId = trainingProblemDTO.getDisplayId();\n\n        QueryWrapper<TrainingProblem> trainingProblemQueryWrapper = new QueryWrapper<>();\n        trainingProblemQueryWrapper.eq(\"tid\", tid)\n                .and(wrapper -> wrapper.eq(\"pid\", pid).or().eq(\"display_id\", displayId));\n        TrainingProblem trainingProblem = trainingProblemEntityService.getOne(trainingProblemQueryWrapper, false);\n        if (trainingProblem != null) {\n            throw new StatusFailException(\"添加失败，该题目已添加或者题目的训练展示ID已存在！\");\n        }\n\n        TrainingProblem problem = new TrainingProblem();\n        boolean isOk = trainingProblemEntityService\n                .saveOrUpdate(problem.setTid(tid).setPid(pid).setDisplayId(displayId));\n        if (isOk) {\n            // 更新训练最近更新时间\n            UpdateWrapper<Training> trainingUpdateWrapper = new UpdateWrapper<>();\n            trainingUpdateWrapper.set(\"gmt_modified\", new Date()).eq(\"id\", tid);\n            trainingEntityService.update(trainingUpdateWrapper);\n\n            // 异步地同步用户对该题目的提交数据\n            adminTrainingRecordService.syncAlreadyRegisterUserRecord(tid, pid, problem.getId());\n        } else {\n            throw new StatusFailException(\"添加失败！\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void importTrainingRemoteOjProblem(String name, String problemId, Long tid) {\n        QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"problem_id\", name.toUpperCase() + \"-\" + problemId);\n        Problem problem = problemEntityService.getOne(queryWrapper, false);\n\n        // 如果该题目不存在，需要先导入\n        if (problem == null) {\n            try {\n                AbstractProblemCrawler.RemoteProblemInfo otherOjProblemInfo = remoteProblemService\n                        .getOtherOJProblemInfo(name.toUpperCase(), problemId);\n                if (otherOjProblemInfo != null) {\n                    problem = remoteProblemService.adminAddOtherOJProblem(otherOjProblemInfo, name);\n                    if (problem == null) {\n                        throw new StatusFailException(\"导入新题目失败！请重新尝试！\");\n                    }\n                } else {\n                    throw new StatusFailException(\"导入新题目失败！原因：可能是与该OJ链接超时或题号格式错误！\");\n                }\n            } catch (Exception e) {\n                log.error(\"导入远程题目异常-------------->\", e);\n                throw new StatusFailException(e.getMessage());\n            }\n        }\n\n        QueryWrapper<TrainingProblem> trainingProblemQueryWrapper = new QueryWrapper<>();\n        Problem finalProblem = problem;\n        trainingProblemQueryWrapper.eq(\"tid\", tid).and(\n                wrapper -> wrapper.eq(\"pid\", finalProblem.getId()).or().eq(\"display_id\", finalProblem.getProblemId()));\n        TrainingProblem trainingProblem = trainingProblemEntityService.getOne(trainingProblemQueryWrapper, false);\n        if (trainingProblem != null) {\n            throw new StatusFailException(\"添加失败，该题目已添加或者题目的训练展示ID已存在！\");\n        }\n\n        TrainingProblem newProblem = new TrainingProblem();\n        boolean isOk = trainingProblemEntityService\n                .saveOrUpdate(newProblem.setTid(tid).setPid(problem.getId()).setDisplayId(problem.getProblemId()));\n        // 添加成功\n        if (isOk) {\n            // 更新训练最近更新时间\n            UpdateWrapper<Training> trainingUpdateWrapper = new UpdateWrapper<>();\n            trainingUpdateWrapper.set(\"gmt_modified\", new Date()).eq(\"id\", tid);\n            trainingEntityService.update(trainingUpdateWrapper);\n\n            // 异步地同步用户对该题目的提交数据\n            adminTrainingRecordService.syncAlreadyRegisterUserRecord(tid, problem.getId(), newProblem.getId());\n        } else {\n            throw new StatusFailException(\"添加失败！\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/training/impl/AdminTrainingServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.training.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.constants.TrainingEnum;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.dao.training.MappingTrainingCategoryEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingCategoryEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingRegisterEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.TrainingDTO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.admin.training.AdminTrainingRecordService;\nimport com.simplefanc.voj.backend.service.admin.training.AdminTrainingService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.pojo.entity.training.MappingTrainingCategory;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRegister;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.Objects;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 19:46\n * @Description:\n */\n\n@Service\n@RequiredArgsConstructor\npublic class AdminTrainingServiceImpl implements AdminTrainingService {\n\n    private final TrainingEntityService trainingEntityService;\n\n    private final MappingTrainingCategoryEntityService mappingTrainingCategoryEntityService;\n\n    private final TrainingCategoryEntityService trainingCategoryEntityService;\n\n    private final TrainingRegisterEntityService trainingRegisterEntityService;\n\n    private final AdminTrainingRecordService adminTrainingRecordService;\n\n    @Override\n    public IPage<Training> getTrainingList(Integer limit, Integer currentPage, String keyword) {\n\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n        IPage<Training> iPage = new Page<>(currentPage, limit);\n        QueryWrapper<Training> queryWrapper = new QueryWrapper<>();\n        // 过滤密码\n        queryWrapper.select(Training.class, info -> !\"private_pwd\".equals(info.getColumn()));\n        if (StrUtil.isNotEmpty(keyword)) {\n            keyword = keyword.trim();\n            queryWrapper.like(\"title\", keyword).or().like(\"id\", keyword).or().like(\"`rank`\", keyword);\n        }\n        queryWrapper.orderByAsc(\"`rank`\");\n\n        return trainingEntityService.page(iPage, queryWrapper);\n    }\n\n    @Override\n    public TrainingDTO getTraining(Long tid) {\n        // 获取本场训练的信息\n        Training training = trainingEntityService.getById(tid);\n        // 查询不存在\n        if (training == null) {\n            throw new StatusFailException(\"查询失败：该训练不存在,请检查参数tid是否准确！\");\n        }\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        // 是否为超级管理员\n        boolean isRoot = UserSessionUtil.isRoot();\n        // 只有超级管理员和训练拥有者才能操作\n        if (!isRoot && !userRolesVO.getUsername().equals(training.getAuthor())) {\n            throw new StatusForbiddenException(\"对不起，你无权限操作！\");\n        }\n\n        TrainingDTO trainingDTO = new TrainingDTO();\n        trainingDTO.setTraining(training);\n\n        QueryWrapper<MappingTrainingCategory> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"tid\", tid);\n        MappingTrainingCategory mappingTrainingCategory = mappingTrainingCategoryEntityService.getOne(queryWrapper,\n                false);\n        TrainingCategory trainingCategory = null;\n        if (mappingTrainingCategory != null) {\n            trainingCategory = trainingCategoryEntityService.getById(mappingTrainingCategory.getCid());\n        }\n        trainingDTO.setTrainingCategory(trainingCategory);\n        return trainingDTO;\n    }\n\n    @Override\n    public void deleteTraining(Long tid) {\n        boolean isOk = trainingEntityService.removeById(tid);\n        // Training的id为其他表的外键的表中的对应数据都会被一起删除！\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败！\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addTraining(TrainingDTO trainingDTO) {\n\n        Training training = trainingDTO.getTraining();\n        trainingEntityService.save(training);\n        TrainingCategory trainingCategory = trainingDTO.getTrainingCategory();\n        if (trainingCategory.getId() == null) {\n            try {\n                trainingCategoryEntityService.save(trainingCategory);\n            } catch (Exception ignored) {\n                QueryWrapper<TrainingCategory> queryWrapper = new QueryWrapper<>();\n                queryWrapper.eq(\"name\", trainingCategory.getName());\n                trainingCategory = trainingCategoryEntityService.getOne(queryWrapper, false);\n            }\n        }\n\n        boolean isOk = mappingTrainingCategoryEntityService\n                .save(new MappingTrainingCategory().setTid(training.getId()).setCid(trainingCategory.getId()));\n        if (!isOk) {\n            throw new StatusFailException(\"添加失败！\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateTraining(TrainingDTO trainingDTO) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        // 是否为超级管理员\n        boolean isRoot = UserSessionUtil.isRoot();\n        // 只有超级管理员和训练拥有者才能操作\n        if (!isRoot && !userRolesVO.getUsername().equals(trainingDTO.getTraining().getAuthor())) {\n            throw new StatusForbiddenException(\"对不起，你无权限操作！\");\n        }\n        Training training = trainingDTO.getTraining();\n        Training oldTraining = trainingEntityService.getById(training.getId());\n        trainingEntityService.updateById(training);\n\n        // 私有训练 修改密码 需要清空之前注册训练的记录\n        if (training.getAuth().equals(TrainingEnum.AUTH_PRIVATE.getValue())) {\n            if (!Objects.equals(training.getPrivatePwd(), oldTraining.getPrivatePwd())) {\n                UpdateWrapper<TrainingRegister> updateWrapper = new UpdateWrapper<>();\n                updateWrapper.eq(\"tid\", training.getId());\n                trainingRegisterEntityService.remove(updateWrapper);\n            }\n        }\n\n        TrainingCategory trainingCategory = trainingDTO.getTrainingCategory();\n        if (trainingCategory.getId() == null) {\n            try {\n                trainingCategoryEntityService.save(trainingCategory);\n            } catch (Exception ignored) {\n                QueryWrapper<TrainingCategory> queryWrapper = new QueryWrapper<>();\n                queryWrapper.eq(\"name\", trainingCategory.getName());\n                trainingCategory = trainingCategoryEntityService.getOne(queryWrapper, false);\n            }\n        }\n\n        MappingTrainingCategory mappingTrainingCategory = mappingTrainingCategoryEntityService\n                .getOne(new QueryWrapper<MappingTrainingCategory>().eq(\"tid\", training.getId()), false);\n\n        if (mappingTrainingCategory == null) {\n            mappingTrainingCategoryEntityService\n                    .save(new MappingTrainingCategory().setTid(training.getId()).setCid(trainingCategory.getId()));\n            adminTrainingRecordService.checkSyncRecord(trainingDTO.getTraining());\n        } else {\n            if (!mappingTrainingCategory.getCid().equals(trainingCategory.getId())) {\n                UpdateWrapper<MappingTrainingCategory> updateWrapper = new UpdateWrapper<>();\n                updateWrapper.eq(\"tid\", training.getId()).set(\"cid\", trainingCategory.getId());\n                boolean isOk = mappingTrainingCategoryEntityService.update(null, updateWrapper);\n                if (isOk) {\n                    adminTrainingRecordService.checkSyncRecord(trainingDTO.getTraining());\n                } else {\n                    throw new StatusFailException(\"修改失败\");\n                }\n            }\n        }\n\n    }\n\n    @Override\n    public void changeTrainingStatus(Long tid, String author, Boolean status) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        // 是否为超级管理员\n        boolean isRoot = UserSessionUtil.isRoot();\n        // 只有超级管理员和训练拥有者才能操作\n        if (!isRoot && !userRolesVO.getUsername().equals(author)) {\n            throw new StatusForbiddenException(\"对不起，你无权限操作！\");\n        }\n\n        boolean isOk = trainingEntityService.saveOrUpdate(new Training().setId(tid).setStatus(status));\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/user/AdminUserService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.user;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.AdminEditUserDTO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:05\n * @Description:\n */\npublic interface AdminUserService {\n\n    IPage<UserRolesVO> getUserList(Integer limit, Integer currentPage, String keyword, Long role, Integer status);\n\n    void editUser(AdminEditUserDTO adminEditUserDTO);\n\n    void deleteUser(List<String> deleteUserIdList);\n\n    void forbidUser(List<String> deleteUserIdList);\n\n    void insertBatchUser(List<List<String>> users);\n\n    Map<Object, Object> generateUser(Map<String, Object> params);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/user/UserRecordService.java",
    "content": "package com.simplefanc.voj.backend.service.admin.user;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.vo.ACMRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.OIRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserHomeVO;\n\nimport java.util.List;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\npublic interface UserRecordService {\n\n    List<ACMRankVO> getRecent7ACRank();\n\n    UserHomeVO getUserHomeInfo(String uid, String username);\n\n    IPage<OIRankVO> getOIRankList(Page<OIRankVO> page, List<String> uidList);\n\n    IPage<ACMRankVO> getACMRankList(Page<ACMRankVO> page, List<String> uidList);\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/user/impl/AdminUserServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.user.impl;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.RandomUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.common.constants.Constant;\nimport com.simplefanc.voj.backend.common.constants.RoleEnum;\nimport com.simplefanc.voj.backend.common.constants.UserStatusEnum;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserRoleEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.AdminEditUserDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ExcelUserVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.admin.user.AdminUserService;\nimport com.simplefanc.voj.backend.service.msg.AdminNoticeService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.user.UserInfo;\nimport com.simplefanc.voj.common.pojo.entity.user.UserRole;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:05\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class AdminUserServiceImpl implements AdminUserService {\n\n    private final UserRoleEntityService userRoleEntityService;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final AdminNoticeService adminNoticeService;\n\n    private final RedisUtil redisUtil;\n\n    @Override\n    public IPage<UserRolesVO> getUserList(Integer limit, Integer currentPage, String keyword, Long roleId, Integer status) {\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n        if (keyword != null) {\n            keyword = keyword.trim();\n        }\n        return userRoleEntityService.getUserList(limit, currentPage, keyword, roleId, status);\n    }\n\n    @Override\n    public void editUser(AdminEditUserDTO adminEditUserDTO) {\n        String uid = adminEditUserDTO.getUid();\n\n        UpdateWrapper<UserInfo> userInfoUpdateWrapper = new UpdateWrapper<>();\n\n        userInfoUpdateWrapper.eq(\"uuid\", uid)\n                .set(\"username\", adminEditUserDTO.getUsername())\n                .set(\"realname\", adminEditUserDTO.getRealname())\n                .set(\"school\", adminEditUserDTO.getSchool())\n                .set(\"number\", adminEditUserDTO.getNumber())\n                .set(\"email\", adminEditUserDTO.getEmail())\n                .set(adminEditUserDTO.getSetNewPwd(), \"password\", SecureUtil.md5(adminEditUserDTO.getPassword()))\n                .set(\"status\", adminEditUserDTO.getStatus());\n        boolean addUserInfo = userInfoEntityService.update(userInfoUpdateWrapper);\n\n        QueryWrapper<UserRole> userRoleQueryWrapper = new QueryWrapper<>();\n        userRoleQueryWrapper.eq(\"uid\", uid);\n        UserRole userRole = userRoleEntityService.getOne(userRoleQueryWrapper, false);\n        boolean addUserRole = false;\n        int type = adminEditUserDTO.getType();\n        int oldType = userRole.getRoleId().intValue();\n        if (userRole.getRoleId().intValue() != type) {\n            userRole.setRoleId((long) type);\n            addUserRole = userRoleEntityService.updateById(userRole);\n            if (type == RoleEnum.ROOT.getId() || oldType == RoleEnum.ROOT.getId()) {\n                // 新增或者去除超级管理员需要删除缓存\n                String cacheKey = RedisConstant.SUPER_ADMIN_UID_LIST_CACHE;\n                redisUtil.del(cacheKey);\n            }\n        }\n        if (addUserInfo) {\n            // 需要重新登录\n            userRoleEntityService.deleteCache(uid, true);\n        } else if (addUserRole) {\n            // 需要重新授权\n            userRoleEntityService.deleteCache(uid, false);\n        }\n\n        if (addUserRole) {\n            // 获取当前登录的用户\n            UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n            String title = \"权限变更通知(Authority Change Notice)\";\n            String content = userRoleEntityService.getAuthChangeContent(oldType, type);\n            adminNoticeService.addSingleNoticeToUser(userRolesVO.getUid(), uid, title, content, \"Sys\");\n        }\n\n    }\n\n    @Override\n    public void deleteUser(List<String> deleteUserIdList) {\n        boolean isOk = userInfoEntityService.removeByIds(deleteUserIdList);\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败！\");\n        }\n    }\n\n    @Override\n    public void forbidUser(List<String> userIdList) {\n        final boolean isOk = userInfoEntityService.lambdaUpdate()\n                .set(UserInfo::getStatus, UserStatusEnum.FORBID.getStatus())\n                .in(UserInfo::getUuid, userIdList)\n                .update();\n        if (!isOk) {\n            throw new StatusFailException(\"封禁失败！\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void insertBatchUser(List<List<String>> users) {\n        List<UserInfo> userInfoList = new LinkedList<>();\n        List<UserRole> userRoleList = new LinkedList<>();\n        if (users != null) {\n            for (List<String> user : users) {\n                String uuid = IdUtil.simpleUUID();\n                UserInfo userInfo = new UserInfo().setUuid(uuid).setUsername(user.get(0))\n                        .setPassword(SecureUtil.md5(user.get(1)))\n                        .setEmail(StrUtil.isEmpty(user.get(2)) ? null : user.get(2));\n\n                if (user.size() >= 4) {\n                    String realname = user.get(3);\n                    if (StrUtil.isNotEmpty(realname)) {\n                        userInfo.setRealname(user.get(3));\n                    }\n                }\n\n                if (user.size() >= 5) {\n                    String gender = user.get(4);\n                    if (\"male\".equals(gender.toLowerCase()) || \"0\".equals(gender)) {\n                        userInfo.setGender(\"male\");\n                    } else if (\"female\".equals(gender.toLowerCase()) || \"1\".equals(gender)) {\n                        userInfo.setGender(\"female\");\n                    }\n                }\n\n                if (user.size() >= 6) {\n                    String nickname = user.get(5);\n                    if (StrUtil.isNotEmpty(nickname)) {\n                        userInfo.setNickname(nickname);\n                    }\n                }\n\n                if (user.size() >= 7) {\n                    String school = user.get(6);\n                    if (StrUtil.isNotEmpty(school)) {\n                        userInfo.setSchool(school);\n                    }\n                }\n\n                userInfoList.add(userInfo);\n                userRoleList.add(new UserRole()\n                        .setRoleId(RoleEnum.DEFAULT_USER.getId())\n                        .setUid(uuid));\n            }\n            boolean result1 = userInfoEntityService.saveBatch(userInfoList);\n            boolean result2 = userRoleEntityService.saveBatch(userRoleList);\n            if (result1 && result2) {\n                // 异步同步系统通知\n                List<String> uidList = userInfoList.stream().map(UserInfo::getUuid).collect(Collectors.toList());\n                adminNoticeService.syncNoticeToNewRegisterBatchUser(uidList);\n            } else {\n                throw new StatusFailException(\"删除失败\");\n            }\n        } else {\n            throw new StatusFailException(\"插入的用户数据不能为空！\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Map<Object, Object> generateUser(Map<String, Object> params) {\n        // TODO 参数\n        String prefix = (String) params.getOrDefault(\"prefix\", \"\");\n        String suffix = (String) params.getOrDefault(\"suffix\", \"\");\n        int numberFrom = (int) params.getOrDefault(\"number_from\", 1);\n        int numberTo = (int) params.getOrDefault(\"number_to\", 10);\n        int passwordLength = (int) params.getOrDefault(\"password_length\", 6);\n\n        List<UserInfo> userInfoList = new LinkedList<>();\n        List<UserRole> userRoleList = new LinkedList<>();\n        List<ExcelUserVO> userVOList = new LinkedList<>();\n        // 存储账号密码放入redis中，等待导出excel\n        final int numLen = String.valueOf(numberTo).length();\n        for (int num = numberFrom; num <= numberTo; num++) {\n            String uuid = IdUtil.simpleUUID();\n            String password = RandomUtil.randomString(passwordLength).toUpperCase();\n            String username = prefix + String.format(\"%0\" + numLen + \"d\", num) + suffix;\n            userInfoList.add(new UserInfo().setUuid(uuid).setUsername(username).setPassword(SecureUtil.md5(password)));\n            userVOList.add(new ExcelUserVO().setUsername(username).setPassword(password));\n            userRoleList.add(new UserRole()\n                    .setRoleId(RoleEnum.DEFAULT_USER.getId())\n                    .setUid(uuid));\n        }\n        boolean result1 = userInfoEntityService.saveBatch(userInfoList);\n        boolean result2 = userRoleEntityService.saveBatch(userRoleList);\n        if (result1 && result2) {\n            String key = IdUtil.simpleUUID();\n            // 存储半小时\n            redisUtil.hset(Constant.GENERATE_USER_INFO_LIST, key, userVOList, 1800);\n            // 异步同步系统通知\n            List<String> uidList = userInfoList.stream().map(UserInfo::getUuid).collect(Collectors.toList());\n            adminNoticeService.syncNoticeToNewRegisterBatchUser(uidList);\n            return MapUtil.builder().put(\"key\", key).map();\n        } else {\n            throw new StatusFailException(\"生成指定用户失败！\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/admin/user/impl/UserRecordServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.admin.user.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.service.admin.user.UserRecordService;\nimport com.simplefanc.voj.backend.mapper.UserRecordMapper;\nimport com.simplefanc.voj.backend.pojo.vo.ACMRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.OIRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserHomeVO;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class UserRecordServiceImpl implements UserRecordService {\n\n    private final UserRecordMapper userRecordMapper;\n\n    @Override\n    public List<ACMRankVO> getRecent7ACRank() {\n        return userRecordMapper.getRecent7ACRank();\n    }\n\n    @Override\n    public UserHomeVO getUserHomeInfo(String uid, String username) {\n        return userRecordMapper.getUserHomeInfo(uid, username);\n    }\n\n    @Override\n    public IPage<OIRankVO> getOIRankList(Page<OIRankVO> page, List<String> uidList) {\n        return userRecordMapper.getOIRankList(page, uidList);\n    }\n\n    @Override\n    public IPage<ACMRankVO> getACMRankList(Page<ACMRankVO> page, List<String> uidList) {\n        return userRecordMapper.getACMRankList(page, uidList);\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/email/EmailService.java",
    "content": "package com.simplefanc.voj.backend.service.email;\n\nimport cn.hutool.core.date.DateTime;\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.lang.Validator;\nimport cn.hutool.core.text.UnicodeUtil;\nimport com.simplefanc.voj.backend.common.constants.EmailConstant;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.mail.javamail.JavaMailSenderImpl;\nimport org.springframework.mail.javamail.MimeMessageHelper;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Component;\nimport org.thymeleaf.TemplateEngine;\nimport org.thymeleaf.context.Context;\n\nimport javax.mail.MessagingException;\nimport javax.mail.internet.MimeMessage;\nimport java.util.Date;\nimport java.util.Properties;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 22:01\n * @Description:\n */\n\n@Component\n@RefreshScope\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class EmailService {\n\n    @Value(\"${voj.web-config.base-url}\")\n    public String ojAddr;\n\n    @Value(\"${voj.web-config.name}\")\n    public String ojName;\n\n    @Value(\"${voj.web-config.short-name}\")\n    public String ojShortName;\n\n    @Value(\"${voj.mail.username}\")\n    public String ojEmailFrom;\n\n    @Value(\"${voj.mail.password}\")\n    public String ojEmailPassword;\n\n    @Value(\"${voj.mail.host}\")\n    public String ojEmailHost;\n\n    @Value(\"${voj.mail.port}\")\n    public Integer ojEmailPort;\n\n    @Value(\"${voj.mail.ssl}\")\n    public String ojEmailSsl;\n\n    private final TemplateEngine templateEngine;\n\n    /**\n     * @MethodName getMailSender\n     * @Params * @param\n     * @Description 获取邮件系统配置\n     * @Return\n     * @Since 2021/5/21\n     */\n    private JavaMailSenderImpl getMailSender() {\n\n        JavaMailSenderImpl sender = new JavaMailSenderImpl();\n        sender.setHost(ojEmailHost);\n        sender.setPort(ojEmailPort);\n        sender.setDefaultEncoding(\"UTF-8\");\n        sender.setUsername(ojEmailFrom);\n        sender.setPassword(ojEmailPassword);\n\n        Properties p = new Properties();\n        p.setProperty(\"mail.smtp.ssl.enable\", ojEmailSsl);\n        p.setProperty(\"mail.smtp.auth\", \"true\");\n        p.setProperty(\"mail.smtp.starttls.enable\", ojEmailSsl);\n        sender.setJavaMailProperties(p);\n        return sender;\n    }\n\n    /**\n     * @MethodName isOk\n     * @Params * @param null\n     * @Description 验证当前邮箱系统是否已配置。\n     * @Return\n     * @Since 2021/6/12\n     */\n    public boolean isOk() {\n        return !\"your_email_username\".equals(ojEmailFrom) && !\"your_email_password\".equals(ojEmailPassword)\n                && Validator.isEmail(ojEmailFrom);\n    }\n\n    /**\n     * @param email 用户邮箱\n     * @param code  生成的六位随机数字验证码\n     * @MethodName sendCode\n     * @Description 为正在注册的用户发送一份注册验证码。\n     * @Return\n     * @Since 2021/1/14\n     */\n\n    @Async\n    public void sendCode(String email, String code) {\n        DateTime expireTime = DateUtil.offsetMinute(new Date(), 10);\n        JavaMailSenderImpl mailSender = getMailSender();\n        MimeMessage mimeMessage = mailSender.createMimeMessage();\n        try {\n            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);\n            // 设置渲染到html页面对应的值\n            Context context = new Context();\n            context.setVariable(EmailConstant.OJ_NAME, UnicodeUtil.toString(ojName));\n            context.setVariable(EmailConstant.OJ_SHORT_NAME, UnicodeUtil.toString(ojShortName).toUpperCase());\n            context.setVariable(EmailConstant.OJ_URL, ojAddr);\n            context.setVariable(\"CODE\", code);\n            context.setVariable(\"EXPIRE_TIME\", expireTime.toString());\n\n            // 利用模板引擎加载html文件进行渲染并生成对应的字符串\n            String emailContent = templateEngine.process(\"emailTemplate_registerCode\", context);\n\n            // 设置邮件标题\n            mimeMessageHelper.setSubject(UnicodeUtil.toString(ojShortName).toUpperCase() + \"的注册邮件\");\n            mimeMessageHelper.setText(emailContent, true);\n            // 收件人\n            mimeMessageHelper.setTo(email);\n            // 发送人\n            mimeMessageHelper.setFrom(ojEmailFrom);\n\n            mailSender.send(mimeMessage);\n        } catch (MessagingException e) {\n            log.error(\"用户注册的邮件任务发生异常------------>\", e);\n        }\n    }\n\n    /**\n     * @param username 需要重置密码的用户名\n     * @param email    用户邮箱\n     * @param code     随机生成20位数字与字母的组合\n     * @MethodName sendResetPassword\n     * @Description 给指定的邮箱的用户发送重置密码链接的邮件。\n     * @Return\n     * @Since 2021/1/14\n     */\n    @Async\n    public void sendResetPassword(String username, String code, String email) {\n        DateTime expireTime = DateUtil.offsetMinute(new Date(), 10);\n        JavaMailSenderImpl mailSender = getMailSender();\n        MimeMessage mimeMessage = mailSender.createMimeMessage();\n        try {\n            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);\n            // 设置渲染到html页面对应的值\n            Context context = new Context();\n            context.setVariable(EmailConstant.OJ_NAME, UnicodeUtil.toString(ojName));\n            context.setVariable(EmailConstant.OJ_SHORT_NAME, UnicodeUtil.toString(ojShortName).toUpperCase());\n            context.setVariable(EmailConstant.OJ_URL, ojAddr);\n\n            String resetUrl;\n            if (ojAddr.endsWith(\"/\")) {\n                resetUrl = ojAddr + \"reset-password?username=\" + username + \"&code=\" + code;\n            } else {\n                resetUrl = ojAddr + \"/reset-password?username=\" + username + \"&code=\" + code;\n            }\n\n            context.setVariable(\"RESET_URL\", resetUrl);\n            context.setVariable(\"EXPIRE_TIME\", expireTime.toString());\n            context.setVariable(\"USERNAME\", username);\n\n            // 利用模板引擎加载html文件进行渲染并生成对应的字符串\n            String emailContent = templateEngine.process(\"emailTemplate_resetPassword\", context);\n\n            mimeMessageHelper.setSubject(UnicodeUtil.toString(ojShortName).toUpperCase() + \"的重置密码邮件\");\n\n            mimeMessageHelper.setText(emailContent, true);\n            // 收件人\n            mimeMessageHelper.setTo(email);\n            // 发送人\n            mimeMessageHelper.setFrom(ojEmailFrom);\n            mailSender.send(mimeMessage);\n        } catch (MessagingException e) {\n            log.error(\"用户重置密码的邮件任务发生异常------------>\", e);\n        }\n    }\n\n    /**\n     * @param email 用户邮箱\n     * @MethodName testEmail\n     * @Description 超级管理员后台修改邮件系统配置后发送的测试邮箱可用性的测试邮件。\n     * @Return\n     * @Since 2021/1/14\n     */\n    @Async\n    public void testEmail(String email) {\n        JavaMailSenderImpl mailSender = getMailSender();\n        MimeMessage mimeMessage = mailSender.createMimeMessage();\n        try {\n            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);\n            // 设置渲染到html页面对应的值\n            Context context = new Context();\n            context.setVariable(EmailConstant.OJ_NAME, UnicodeUtil.toString(ojName));\n            context.setVariable(EmailConstant.OJ_SHORT_NAME, UnicodeUtil.toString(ojShortName).toUpperCase());\n            context.setVariable(EmailConstant.OJ_URL, ojAddr);\n            // 利用模板引擎加载html文件进行渲染并生成对应的字符串\n            String emailContent = templateEngine.process(\"emailTemplate_testEmail\", context);\n\n            mimeMessageHelper.setSubject(UnicodeUtil.toString(ojShortName).toUpperCase() + \"的测试邮件\");\n\n            mimeMessageHelper.setText(emailContent, true);\n            // 收件人\n            mimeMessageHelper.setTo(email);\n            // 发送人\n            mimeMessageHelper.setFrom(ojEmailFrom);\n            mailSender.send(mimeMessage);\n        } catch (MessagingException e) {\n            log.error(\"超级管理员重置邮件系统配置的测试邮箱可用性的任务发生异常------------>\", e);\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/ContestFileService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:27\n * @Description:\n */\npublic interface ContestFileService {\n\n    void downloadContestRank(Long cid, Boolean forceRefresh, Boolean removeStar, HttpServletResponse response)\n            throws IOException;\n\n    void downloadContestAcSubmission(Long cid, Boolean excludeAdmin, Boolean allStatus, String splitType, HttpServletResponse response);\n\n    void downloadContestPrintText(Long id, HttpServletResponse response);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/ImageService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:31\n * @Description:\n */\npublic interface ImageService {\n\n    Map<Object, Object> uploadAvatar(MultipartFile image);\n\n    Map<Object, Object> uploadCarouselImg(MultipartFile image);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/ImportDSOJProblemService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:47\n * @Description:\n */\npublic interface ImportDSOJProblemService {\n\n    void importDSOJProblem();\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/ImportFpsProblemService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.IOException;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:44\n * @Description:\n */\npublic interface ImportFpsProblemService {\n\n    /**\n     * @param file\n     * @MethodName importFpsProblem\n     * @Description zip文件导入题目 仅超级管理员可操作\n     * @Return\n     * @Since 2021/10/06\n     */\n    void importFPSProblem(MultipartFile file) throws IOException;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/ImportLOJProblemService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\n/**\n * @Author: chenfan\n * @Date: 2022/11/07 14:44\n * @Description:\n */\npublic interface ImportLOJProblemService {\n\n    boolean importLOJProblem(Integer problemId);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/ImportQDUOJProblemService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\nimport org.springframework.web.multipart.MultipartFile;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:47\n * @Description:\n */\npublic interface ImportQDUOJProblemService {\n\n    /**\n     * @param file\n     * @MethodName importQDOJProblem\n     * @Description zip文件导入题目 仅超级管理员可操作\n     * @Return\n     * @Since 2021/5/27\n     */\n    void importQDOJProblem(MultipartFile file);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/MarkDownFileService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:50\n * @Description:\n */\n\npublic interface MarkDownFileService {\n\n    Map<Object, Object> uploadMDImg(MultipartFile image);\n\n    void deleteMDImg(Long fileId);\n\n    Map<Object, Object> uploadMd(MultipartFile file);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/ProblemFileService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\nimport org.springframework.web.multipart.MultipartFile;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:40\n * @Description:\n */\npublic interface ProblemFileService {\n\n    /**\n     * @param file\n     * @MethodName importProblem\n     * @Description zip文件导入题目 仅超级管理员可操作\n     * @Return\n     * @Since 2021/5/27\n     */\n    void importProblem(MultipartFile file);\n\n    /**\n     * @param pidList\n     * @param response\n     * @MethodName exportProblem\n     * @Description 导出指定的题目包括测试数据生成zip 仅超级管理员可操作\n     * @Return\n     * @Since 2021/5/28\n     */\n    void exportProblem(List<Long> pidList, HttpServletResponse response);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/TestCaseService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\nimport org.springframework.web.multipart.MultipartFile;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:57\n * @Description:\n */\npublic interface TestCaseService {\n\n    Map<Object, Object> uploadTestcaseZip(MultipartFile file);\n\n    void downloadTestcase(Long pid, HttpServletResponse response);\n\n    void downloadSingleTestCase(Long caseId, String inputData, String outputData, HttpServletResponse response);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/UserFileService.java",
    "content": "package com.simplefanc.voj.backend.service.file;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 15:02\n * @Description:\n */\npublic interface UserFileService {\n\n    void generateUserExcel(String key, HttpServletResponse response) throws IOException;\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/ContestFileServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileWriter;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.ZipUtil;\nimport com.alibaba.excel.EasyExcel;\nimport com.alibaba.excel.ExcelWriter;\nimport com.alibaba.excel.write.metadata.WriteSheet;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.utils.ExcelUtil;\nimport com.simplefanc.voj.backend.common.utils.MyFileUtil;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestPrintEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestProblemEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.ACMContestRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.ExcelIpVO;\nimport com.simplefanc.voj.backend.pojo.vo.OIContestRankVO;\nimport com.simplefanc.voj.backend.service.file.ContestFileService;\nimport com.simplefanc.voj.backend.service.oj.ContestACMRankService;\nimport com.simplefanc.voj.backend.service.oj.ContestOIRankService;\nimport com.simplefanc.voj.backend.service.oj.ContestService;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestPrint;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.File;\nimport java.io.IOException;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:27\n * @Description:\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class ContestFileServiceImpl implements ContestFileService {\n\n    private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL_TIME = ThreadLocal.withInitial(() -> new SimpleDateFormat(\"yyyyMMddHHmmss\"));\n\n    private final ContestEntityService contestEntityService;\n\n    private final ContestProblemEntityService contestProblemEntityService;\n\n    private final ContestPrintEntityService contestPrintEntityService;\n\n    private final ContestService contestService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final ContestACMRankService contestACMRankService;\n\n    private final ContestOIRankService contestOIRankService;\n\n    private final ContestValidator contestValidator;\n\n    private final FilePathProperties filePathProps;\n\n    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {\n        Map<Object, Boolean> seen = new ConcurrentHashMap<>();\n        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;\n    }\n\n    private static String languageToFileSuffix(String language) {\n\n        List<String> CLang = Arrays.asList(\"c\", \"gcc\", \"clang\");\n        List<String> CPPLang = Arrays.asList(\"c++\", \"g++\", \"clang++\");\n        List<String> PythonLang = Arrays.asList(\"python\", \"pypy\");\n\n        for (String lang : CPPLang) {\n            if (language.contains(lang)) {\n                return \"cpp\";\n            }\n        }\n\n        if (language.contains(\"c#\")) {\n            return \"cs\";\n        }\n\n        for (String lang : CLang) {\n            if (language.contains(lang)) {\n                return \"c\";\n            }\n        }\n\n        for (String lang : PythonLang) {\n            if (language.contains(lang)) {\n                return \"py\";\n            }\n        }\n\n        if (language.contains(\"javascript\")) {\n            return \"js\";\n        }\n\n        if (language.contains(\"java\")) {\n            return \"java\";\n        }\n\n        if (language.contains(\"pascal\")) {\n            return \"pas\";\n        }\n\n        if (language.contains(\"go\")) {\n            return \"go\";\n        }\n\n        if (language.contains(\"php\")) {\n            return \"php\";\n        }\n\n        return \"txt\";\n    }\n\n    @Override\n    public void downloadContestRank(Long cid, Boolean forceRefresh, Boolean removeStar, HttpServletResponse response)\n            throws IOException {\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(cid);\n\n        if (contest == null) {\n            throw new StatusFailException(\"错误：该比赛不存在！\");\n        }\n\n        if (!contestValidator.isContestAdmin(contest)) {\n            throw new StatusForbiddenException(\"错误：您并非该比赛的管理员，无权下载榜单！\");\n        }\n\n        // 检查是否开启封榜模式\n        boolean isOpenSealRank = contestValidator.isOpenSealRank(contest, forceRefresh);\n\n        // 获取题目displayID列表\n        QueryWrapper<ContestProblem> contestProblemQueryWrapper = new QueryWrapper<>();\n        contestProblemQueryWrapper.eq(\"cid\", contest.getId()).select(\"display_id\").orderByAsc(\"display_id\");\n        List<String> contestProblemDisplayIdList = contestProblemEntityService.list(contestProblemQueryWrapper)\n                .stream()\n                .sorted()\n                .map(ContestProblem::getDisplayId)\n                .collect(Collectors.toList());\n\n        List<List<String>> head;\n        List data;\n        // ACM比赛\n        if (contest.getType().intValue() == ContestEnum.TYPE_ACM.getCode()) {\n            List<ACMContestRankVO> acmContestRankVOList = contestACMRankService.calculateACMRank(isOpenSealRank, removeStar, contest,\n                    null, null, false, null);\n            head = getContestRankExcelHead(contestProblemDisplayIdList, true);\n            data = changeACMContestRankToExcelRowList(acmContestRankVOList,\n                            contestProblemDisplayIdList, contest.getRankShowName());\n        } else {\n            List<OIContestRankVO> oiContestRankVOList = contestOIRankService.calculateOIRank(isOpenSealRank,\n                    removeStar, contest, null, null, false, null);\n            head = getContestRankExcelHead(contestProblemDisplayIdList, false);\n            data = changeOIContestRankToExcelRowList(oiContestRankVOList, contestProblemDisplayIdList, contest.getRankShowName());\n        }\n\n        final String fileName = \"contest_\" + contest.getId() + \"_rank\";\n        ExcelUtil.wrapExcelResponse(response, fileName);\n        final ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();\n        WriteSheet rankSheet = EasyExcel.writerSheet(0, \"rank\").head(head).build();\n        WriteSheet ipSheet = EasyExcel.writerSheet(1, \"ip\").head(ExcelIpVO.class).build();\n        excelWriter.write(data, rankSheet)\n                .write(getExcelIpVO(contest), ipSheet);\n        excelWriter.finish();\n    }\n\n    private List<ExcelIpVO> getExcelIpVO(Contest contest) {\n        final Set<String> contestAdminUidList = contestService.getContestAdminUidList(contest);\n        final List<Judge> judgeList = judgeEntityService.list(new QueryWrapper<Judge>().select(\"DISTINCT username, ip\")\n                .eq(\"cid\", contest.getId())\n                .between(\"submit_time\", contest.getStartTime(), contest.getEndTime()));\n\n        final Map<String, String> userNameIpMap = judgeList.stream()\n                .filter(judge -> !contestAdminUidList.contains(judge.getUid()))\n                .collect(Collectors.groupingBy(Judge::getUsername,\n                        Collectors.mapping(Judge::getIp,\n                                Collectors.joining(\" & \"))));\n        return userNameIpMap.entrySet().stream()\n                .map(entry -> new ExcelIpVO(entry.getKey(), entry.getValue()))\n                .sorted(Comparator.comparing(ExcelIpVO::getUsername))\n                .collect(Collectors.toList());\n    }\n\n    @Override\n    public void downloadContestAcSubmission(Long cid, Boolean excludeAdmin, Boolean allStatus, String splitType,\n                                            HttpServletResponse response) {\n        Contest contest = contestEntityService.getById(cid);\n        if (contest == null) {\n            throw new StatusFailException(\"错误：该比赛不存在！\");\n        }\n\n        if (!contestValidator.isContestAdmin(contest)) {\n            throw new StatusForbiddenException(\"错误：您并非该比赛的管理员，无权下载AC记录！\");\n        }\n\n        boolean isACM = contest.getType().intValue() == ContestEnum.TYPE_ACM.getCode();\n\n        QueryWrapper<ContestProblem> contestProblemQueryWrapper = new QueryWrapper<>();\n        contestProblemQueryWrapper.eq(\"cid\", contest.getId());\n        List<ContestProblem> contestProblemList = contestProblemEntityService.list(contestProblemQueryWrapper);\n\n        List<String> superAdminUidList = userInfoEntityService.getSuperAdminUidList();\n        QueryWrapper<Judge> judgeQueryWrapper = new QueryWrapper<>();\n        judgeQueryWrapper.eq(\"cid\", cid)\n                .eq(!allStatus, \"status\", JudgeStatus.STATUS_ACCEPTED.getStatus())\n                // OI模式取得分不为null的\n                .isNotNull(!isACM, \"score\")\n                .between(\"submit_time\", contest.getStartTime(), contest.getEndTime())\n                // 排除比赛创建者和root\n                .ne(excludeAdmin, \"uid\", contest.getUid())\n                .notIn(excludeAdmin && superAdminUidList.size() > 0, \"uid\", superAdminUidList)\n                .orderByDesc(\"submit_time\");\n\n        List<Judge> judgeList = judgeEntityService.list(judgeQueryWrapper);\n\n        // 打包文件的临时路径 -> username为文件夹名字\n        String tmpFilesDir = filePathProps.getContestAcSubmissionTmpFolder() + File.separator + IdUtil.fastSimpleUUID();\n        FileUtil.mkdir(tmpFilesDir);\n\n        if (\"user\".equals(splitType)) {\n            splitCodeByUser(isACM, contestProblemList, judgeList, tmpFilesDir);\n        } else if (\"problem\".equals(splitType)) {\n            splitByProblem(isACM, contestProblemList, judgeList, tmpFilesDir);\n        }\n\n        String zipFileName = \"contest_\" + contest.getId() + \"_\" + System.currentTimeMillis() + \".zip\";\n        String zipPath = filePathProps.getContestAcSubmissionTmpFolder() + File.separator + zipFileName;\n        ZipUtil.zip(tmpFilesDir, zipPath);\n        MyFileUtil.download(response, zipPath, zipFileName, \"下载比赛AC代码失败，请重新尝试！\");\n        FileUtil.del(tmpFilesDir);\n        FileUtil.del(zipPath);\n    }\n\n    /**\n     * 以比赛题目编号来分割提交的代码\n     */\n    private void splitByProblem(boolean isACM, List<ContestProblem> contestProblemList, List<Judge> judgeList, String tmpFilesDir) {\n        for (ContestProblem contestProblem : contestProblemList) {\n            // 对于每题目生成对应的文件夹\n            String problemDir = tmpFilesDir + File.separator + contestProblem.getDisplayId();\n            FileUtil.mkdir(problemDir);\n            // 如果是ACM模式，则所有提交代码都要生成，如果同一题多次提交AC，加上提交时间秒后缀 ---> username_(666666).c\n            // 如果是OI模式就生成最近一次提交即可，且带上分数 ---> username_(666666)_100.c\n            List<Judge> problemSubmissionList = judgeList.stream()\n                    // 过滤出对应题目的提交\n                    .filter(judge -> judge.getPid().equals(contestProblem.getPid()))\n                    // 根据提交时间进行降序\n                    .sorted(Comparator.comparing(Judge::getSubmitTime).reversed()).collect(Collectors.toList());\n\n            for (Judge judge : problemSubmissionList) {\n                String filePath = problemDir + File.separator + judge.getUsername();\n                if (!isACM) {\n                    String key = judge.getUsername() + \"_\" + contestProblem.getDisplayId();\n                    // OI模式只取最后一次提交\n//                    if (!recordMap.containsKey(key)) {\n                        filePath += \"_\" + judge.getScore() + \"_(\"\n                                + THREAD_LOCAL_TIME.get().format(judge.getSubmitTime()) + \").\"\n                                + languageToFileSuffix(judge.getLanguage().toLowerCase());\n                        FileWriter fileWriter = new FileWriter(filePath);\n                        fileWriter.write(judge.getCode());\n//                        recordMap.put(key, true);\n//                    }\n                } else {\n                    filePath += \"_(\" + THREAD_LOCAL_TIME.get().format(judge.getSubmitTime()) + \").\"\n                            + languageToFileSuffix(judge.getLanguage().toLowerCase());\n                    FileWriter fileWriter = new FileWriter(filePath);\n                    fileWriter.write(judge.getCode());\n                }\n            }\n        }\n    }\n\n    /**\n     * 以用户来分割提交的代码\n     */\n    private void splitCodeByUser(boolean isACM, List<ContestProblem> contestProblemList, List<Judge> judgeList, String tmpFilesDir) {\n        List<String> usernameList = judgeList.stream()\n                // 根据用户名过滤唯一\n                .filter(distinctByKey(Judge::getUsername))\n                // 映射出用户名列表\n                .map(Judge::getUsername)\n                .collect(Collectors.toList());\n\n        HashMap<Long, String> cpIdMap = new HashMap<>();\n        for (ContestProblem contestProblem : contestProblemList) {\n            cpIdMap.put(contestProblem.getId(), contestProblem.getDisplayId());\n        }\n\n        for (String username : usernameList) {\n            // 对于每个用户生成对应的文件夹\n            String userDir = tmpFilesDir + File.separator + username;\n            FileUtil.mkdir(userDir);\n            // 如果是ACM模式，则所有提交代码都要生成，如果同一题多次提交AC，加上提交时间秒后缀 ---> A_(666666).c\n            // 如果是OI模式就生成最近一次提交即可，且带上分数 ---> A_(666666)_100.c\n            List<Judge> userSubmissionList = judgeList.stream()\n                    // 过滤出对应用户的提交\n                    .filter(judge -> judge.getUsername().equals(username))\n                    // 根据提交时间进行降序\n                    .sorted(Comparator.comparing(Judge::getSubmitTime).reversed()).collect(Collectors.toList());\n\n            for (Judge judge : userSubmissionList) {\n                String filePath = userDir + File.separator + cpIdMap.getOrDefault(judge.getCpid(), \"null\");\n                // OI模式只取最后一次提交\n                if (!isACM) {\n                    String key = judge.getUsername() + \"_\" + judge.getPid();\n//                    if (!recordMap.containsKey(key)) {\n                        filePath += \"_\" + judge.getScore() + \"_(\"\n                                + THREAD_LOCAL_TIME.get().format(judge.getSubmitTime()) + \").\"\n                                + languageToFileSuffix(judge.getLanguage().toLowerCase());\n                        FileWriter fileWriter = new FileWriter(filePath);\n                        fileWriter.write(judge.getCode());\n//                        recordMap.put(key, true);\n//                    }\n                } else {\n                    filePath += \"_(\" + THREAD_LOCAL_TIME.get().format(judge.getSubmitTime()) + \").\"\n                            + languageToFileSuffix(judge.getLanguage().toLowerCase());\n                    FileWriter fileWriter = new FileWriter(filePath);\n                    fileWriter.write(judge.getCode());\n                }\n            }\n        }\n    }\n\n    @Override\n    public void downloadContestPrintText(Long id, HttpServletResponse response) {\n        ContestPrint contestPrint = contestPrintEntityService.getById(id);\n        String filename = contestPrint.getUsername() + \"_Contest_Print.txt\";\n        String filePath = filePathProps.getContestTextPrintFolder() + File.separator + id + File.separator + filename;\n        if (!FileUtil.exist(filePath)) {\n            FileWriter fileWriter = new FileWriter(filePath);\n            fileWriter.write(contestPrint.getContent());\n        }\n\n        MyFileUtil.download(response, filePath, filename, \"下载比赛打印文本文件失败，请重新尝试！\");\n    }\n\n    public List<List<String>> getContestRankExcelHead(List<String> contestProblemDisplayIdList, Boolean isACM) {\n        List<List<String>> headList = new LinkedList<>();\n        List<String> head = new LinkedList<>();\n        head.add(\"No\");\n\n        List<String> head0 = new LinkedList<>();\n        head0.add(\"Rank\");\n\n        List<String> head1 = new LinkedList<>();\n        head1.add(\"Username\");\n//        List<String> head2 = new LinkedList<>();\n//        head2.add(\"ShowName\");\n        List<String> head3 = new LinkedList<>();\n        head3.add(\"Real Name\");\n        List<String> head4 = new LinkedList<>();\n        head4.add(\"School\");\n\n        headList.add(head);\n        headList.add(head0);\n        headList.add(head1);\n//        headList.add(head2);\n        headList.add(head3);\n        headList.add(head4);\n\n        List<String> head5 = new LinkedList<>();\n        if (isACM) {\n            head5.add(\"AC\");\n            List<String> head6 = new LinkedList<>();\n            head6.add(\"Total Submission\");\n            List<String> head7 = new LinkedList<>();\n            head7.add(\"Total Penalty Time\");\n            headList.add(head5);\n            headList.add(head6);\n            headList.add(head7);\n        } else {\n            head5.add(\"Total Score\");\n            headList.add(head5);\n        }\n\n        // 添加题目头\n        for (String displayId : contestProblemDisplayIdList) {\n            List<String> tmp = new LinkedList<>();\n            tmp.add(displayId);\n            headList.add(tmp);\n        }\n        return headList;\n    }\n\n    public List<List<Object>> changeACMContestRankToExcelRowList(List<ACMContestRankVO> acmContestRankVOList,\n                                                                 List<String> contestProblemDisplayIdList, String rankShowName) {\n        List<List<Object>> allRowDataList = new LinkedList<>();\n        for (ACMContestRankVO acmContestRankVO : acmContestRankVOList) {\n            List<Object> rowData = new LinkedList<>();\n            rowData.add(acmContestRankVO.getSeq());\n            rowData.add(acmContestRankVO.getRank() == -1 ? \"*\" : acmContestRankVO.getRank().toString());\n            rowData.add(acmContestRankVO.getUsername());\n//            if (\"username\".equals(rankShowName)) {\n//                rowData.add(acmContestRankVO.getUsername());\n//            } else if (\"realname\".equals(rankShowName)) {\n//                rowData.add(acmContestRankVO.getRealname());\n//            } else if (\"nickname\".equals(rankShowName)) {\n//                rowData.add(acmContestRankVO.getNickname());\n//            } else {\n//                rowData.add(\"\");\n//            }\n            rowData.add(acmContestRankVO.getRealname());\n            rowData.add(acmContestRankVO.getSchool());\n            rowData.add(acmContestRankVO.getAc());\n            rowData.add(acmContestRankVO.getTotal());\n            rowData.add(acmContestRankVO.getTotalTime());\n            HashMap<String, HashMap<String, Object>> submissionInfo = acmContestRankVO.getSubmissionInfo();\n            for (String displayId : contestProblemDisplayIdList) {\n                HashMap<String, Object> problemInfo = submissionInfo.getOrDefault(displayId, null);\n                // 如果是有提交记录的\n                if (problemInfo != null) {\n                    boolean isAC = (boolean) problemInfo.getOrDefault(\"isAC\", false);\n                    String info = \"\";\n                    int errorNum = (int) problemInfo.getOrDefault(\"errorNum\", 0);\n                    int tryNum = (int) problemInfo.getOrDefault(\"tryNum\", 0);\n                    if (isAC) {\n                        if (errorNum == 0) {\n                            info = \"+(1)\";\n                        } else {\n                            info = \"-(\" + (errorNum + 1) + \")\";\n                        }\n                    } else {\n                        if (tryNum != 0 && errorNum != 0) {\n                            info = \"-(\" + errorNum + \"+\" + tryNum + \")\";\n                        } else if (errorNum != 0) {\n                            info = \"-(\" + errorNum + \")\";\n                        } else if (tryNum != 0) {\n                            info = \"?(\" + tryNum + \")\";\n                        }\n                    }\n                    rowData.add(info);\n                } else {\n                    rowData.add(\"\");\n                }\n            }\n            allRowDataList.add(rowData);\n        }\n        return allRowDataList;\n    }\n\n    public List<List<Object>> changeOIContestRankToExcelRowList(List<OIContestRankVO> oiContestRankVOList,\n                                                                List<String> contestProblemDisplayIdList, String rankShowName) {\n        List<List<Object>> allRowDataList = new LinkedList<>();\n        for (OIContestRankVO oiContestRankVO : oiContestRankVOList) {\n            List<Object> rowData = new LinkedList<>();\n            rowData.add(oiContestRankVO.getSeq());\n            rowData.add(oiContestRankVO.getRank() == -1 ? \"*\" : oiContestRankVO.getRank().toString());\n            rowData.add(oiContestRankVO.getUsername());\n//            if (\"username\".equals(rankShowName)) {\n//                rowData.add(oiContestRankVO.getUsername());\n//            } else if (\"realname\".equals(rankShowName)) {\n//                rowData.add(oiContestRankVO.getRealname());\n//            } else if (\"nickname\".equals(rankShowName)) {\n//                rowData.add(oiContestRankVO.getNickname());\n//            } else {\n//                rowData.add(\"\");\n//            }\n            rowData.add(oiContestRankVO.getRealname());\n            rowData.add(oiContestRankVO.getSchool());\n            rowData.add(oiContestRankVO.getTotalScore());\n            Map<String, Integer> submissionInfo = oiContestRankVO.getSubmissionInfo();\n            for (String displayId : contestProblemDisplayIdList) {\n                Integer score = submissionInfo.getOrDefault(displayId, null);\n                // 如果是有提交记录的就写最后一次提交的分数，没有的就写空\n                if (score != null) {\n                    rowData.add(score);\n                } else {\n                    rowData.add(\"\");\n                }\n            }\n            allRowDataList.add(rowData);\n        }\n        return allRowDataList;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/ImageServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.IdUtil;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.simplefanc.voj.backend.common.constants.FileTypeEnum;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusSystemErrorException;\nimport com.simplefanc.voj.backend.dao.common.FileEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.file.ImageService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.pojo.entity.user.Role;\nimport com.simplefanc.voj.common.pojo.entity.user.UserInfo;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.File;\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:31\n * @Description:\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class ImageServiceImpl implements ImageService {\n\n    public static final String IMAGE_FORMAT = \"jpg,jpeg,gif,png,webp,jfif,svg\";\n\n    private final FileEntityService fileEntityService;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final FilePathProperties filePathProps;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Map<Object, Object> uploadAvatar(MultipartFile image) {\n        if (image == null) {\n            throw new StatusFailException(\"上传的头像图片文件不能为空！\");\n        }\n        if (image.getSize() > 1024 * 1024 * 2) {\n            throw new StatusFailException(\"上传的头像图片文件大小不能大于2M！\");\n        }\n        // 获取文件后缀\n        String suffix = image.getOriginalFilename().substring(image.getOriginalFilename().lastIndexOf(\".\") + 1);\n        if (!\"jpg,jpeg,gif,png,webp\".toUpperCase().contains(suffix.toUpperCase())) {\n            throw new StatusFailException(\"请选择jpg,jpeg,gif,png,webp格式的头像图片！\");\n        }\n        // 若不存在该目录，则创建目录\n        FileUtil.mkdir(filePathProps.getUserAvatarFolder());\n        // 通过UUID生成唯一文件名\n        String filename = IdUtil.simpleUUID() + \".\" + suffix;\n        try {\n            // 将文件保存指定目录\n            image.transferTo(FileUtil.file(filePathProps.getUserAvatarFolder() + File.separator + filename));\n        } catch (Exception e) {\n            log.error(\"头像文件上传异常-------------->\", e);\n            throw new StatusSystemErrorException(\"服务器异常：头像上传失败！\");\n        }\n\n        // 获取当前登录用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        // 将当前用户所属的file表中avatar类型的实体的delete设置为1；\n        fileEntityService.updateFileToDeleteByUidAndType(userRolesVO.getUid(), \"avatar\");\n\n        // 更新user_info里面的avatar\n        UpdateWrapper<UserInfo> userInfoUpdateWrapper = new UpdateWrapper<>();\n        userInfoUpdateWrapper.set(\"avatar\", filePathProps.getImgApi() + filename).eq(\"uuid\", userRolesVO.getUid());\n        userInfoEntityService.update(userInfoUpdateWrapper);\n\n        // 插入file表记录\n        com.simplefanc.voj.common.pojo.entity.common.File imgFile = new com.simplefanc.voj.common.pojo.entity.common.File();\n        imgFile.setName(filename).setFolderPath(filePathProps.getUserAvatarFolder())\n                .setFilePath(filePathProps.getUserAvatarFolder() + File.separator + filename).setSuffix(suffix)\n                .setType(FileTypeEnum.AVATAR.getType()).setUid(userRolesVO.getUid());\n        fileEntityService.saveOrUpdate(imgFile);\n\n        // 更新session\n        userRolesVO.setAvatar(filePathProps.getImgApi() + filename);\n        UserSessionUtil.setUserInfo(userRolesVO);\n        return MapUtil.builder().put(\"uid\", userRolesVO.getUid()).put(\"username\", userRolesVO.getUsername())\n                .put(\"nickname\", userRolesVO.getNickname()).put(\"avatar\", filePathProps.getImgApi() + filename)\n                .put(\"email\", userRolesVO.getEmail()).put(\"number\", userRolesVO.getNumber())\n                .put(\"school\", userRolesVO.getSchool()).put(\"course\", userRolesVO.getCourse())\n                .put(\"signature\", userRolesVO.getSignature()).put(\"realname\", userRolesVO.getRealname())\n                .put(\"github\", userRolesVO.getGithub()).put(\"blog\", userRolesVO.getBlog())\n                .put(\"cfUsername\", userRolesVO.getCfUsername())\n                .put(\"roleList\", userRolesVO.getRoles().stream().map(Role::getRole)).map();\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Map<Object, Object> uploadCarouselImg(MultipartFile image) {\n\n        if (image == null) {\n            throw new StatusFailException(\"上传的图片文件不能为空！\");\n        }\n\n        // 获取文件后缀\n        String suffix = image.getOriginalFilename().substring(image.getOriginalFilename().lastIndexOf(\".\") + 1);\n        if (!IMAGE_FORMAT.toUpperCase().contains(suffix.toUpperCase())) {\n            throw new StatusFailException(\"请选择\" + IMAGE_FORMAT + \"格式的头像图片！\");\n        }\n        // 若不存在该目录，则创建目录\n        FileUtil.mkdir(filePathProps.getHomeCarouselFolder());\n        // 通过UUID生成唯一文件名\n        String filename = IdUtil.simpleUUID() + \".\" + suffix;\n        try {\n            // 将文件保存指定目录\n            image.transferTo(FileUtil.file(filePathProps.getHomeCarouselFolder() + File.separator + filename));\n        } catch (Exception e) {\n            log.error(\"图片文件上传异常-------------->\", e);\n            throw new StatusSystemErrorException(\"服务器异常：图片上传失败！\");\n        }\n\n        // 获取当前登录用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        // 插入file表记录\n        com.simplefanc.voj.common.pojo.entity.common.File imgFile = new com.simplefanc.voj.common.pojo.entity.common.File();\n        imgFile.setName(filename).setFolderPath(filePathProps.getHomeCarouselFolder())\n                .setFilePath(filePathProps.getHomeCarouselFolder() + File.separator + filename)\n                .setSuffix(suffix)\n                .setType(FileTypeEnum.CAROUSEL.getType())\n                .setUid(userRolesVO.getUid());\n        fileEntityService.saveOrUpdate(imgFile);\n\n        return MapUtil.builder().put(\"id\", imgFile.getId()).put(\"url\", filePathProps.getImgApi() + filename).map();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/ImportDSOJProblemServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileWriter;\nimport cn.hutool.core.util.ReUtil;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.dao.problem.LanguageEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.service.file.ImportDSOJProblemService;\nimport com.simplefanc.voj.common.constants.*;\nimport com.simplefanc.voj.common.pojo.entity.problem.Language;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:47\n * @Description:\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class ImportDSOJProblemServiceImpl implements ImportDSOJProblemService {\n\n    private final LanguageEntityService languageEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final FilePathProperties filePathProps;\n\n    private static final Pattern EXAMPLE_PATTERN = Pattern.compile(\"[#\\\\s]*?输入[\\\\s\\\\S]*?```\\r\\n([\\\\s\\\\S]*?)[\\\\r\\\\n]*?```[\\\\s\\\\S]*?[#\\\\s]*?输出[\\\\s\\\\S]*?```\\r\\n([\\\\s\\\\S]*?)\\r\\n```\");\n    private static final Pattern TAG_PATTERN = Pattern.compile(\"<option value=\\\"\\\\d*\\\" selected>([\\\\s\\\\S]*?)</option>\");\n\n    public static void main(String[] args) {\n//        String fileDir = \"/Users/simplefanc/Downloads/test/dsoj/data\";\n////        String fileDir = \"/Users/simplefanc/Downloads/test/dsoj/html\";\n//        File file = new File(fileDir);\n//        File[] files = file.listFiles();\n//        for (File tmp : files) {\n//            String name = tmp.getName();\n//            int i = name.lastIndexOf(\".\");\n//            String prefix = name.substring(0, i);\n//            String suffix = name.substring(i + 1);\n//            if (\"zip\".equals(suffix)) {\n//                // 文件夹不存在就新建\n//                String dirPath = fileDir + File.separator + prefix;\n//                FileUtil.mkdir(dirPath);\n//                // 将压缩包压缩到指定文件夹\n//                try {\n//                    ZipUtil.unzip(tmp.getPath(), dirPath);\n//                } catch (Exception e) {\n//                    System.out.println(prefix);\n//                }\n//            } else if (\"html\".equals(suffix)) {\n//\n//            }\n//        }\n    }\n\n\n    @Transactional(rollbackFor = Exception.class)\n    public void importDSOJProblem() {\n//        String fileDir = \"/Users/simplefanc/Downloads/test/dsoj\";\n        String fileDir = filePathProps.getTestcaseTmpFolder() + File.separator + \"dsoj\";\n        // 检查文件是否存在\n        File fileList = new File(fileDir);\n        File[] files = fileList.listFiles();\n        for (File file : files) {\n            if (file.isFile()) {\n                String name = file.getName();\n                int idx = name.lastIndexOf(\".\");\n                String prefix = name.substring(0, idx);\n                String suffix = name.substring(idx + 1);\n                if (\"html\".equals(suffix)) {\n                    // 读取html文件\n                    String content = FileUtil.readString(file.getPath(), StandardCharsets.UTF_8);\n                    ProblemDTO problemDTO = getProblemDTO(prefix, content);\n\n                    processTestCase(fileDir, prefix, problemDTO);\n                    problemEntityService.adminAddProblem(problemDTO);\n                }\n            }\n        }\n    }\n\n    private void processTestCase(String fileDir, String prefix, ProblemDTO problemDTO) {\n        // 检查文件是否存在\n        String testCaseFileDir = fileDir + File.separator + prefix;\n        File testCaseFileList = new File(testCaseFileDir);\n        File[] testCaseFiles = testCaseFileList.listFiles();\n        if (testCaseFiles == null || testCaseFiles.length == 0) {\n            System.err.println(prefix);\n            throw new StatusFailException(\"评测数据压缩包里文件不能为空！\");\n        }\n\n        HashMap<String, String> inputData = new HashMap<>();\n        HashMap<String, String> outputData = new HashMap<>();\n\n        // 遍历读取与检查是否in和out文件一一对应，否则报错\n        for (File tmp : testCaseFiles) {\n            String tmpPreName;\n            if (tmp.getName().endsWith(\".in\")) {\n                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(\".in\"));\n                inputData.put(tmpPreName, tmp.getName());\n            } else if (tmp.getName().endsWith(\".out\")) {\n                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(\".out\"));\n                outputData.put(tmpPreName, tmp.getName());\n            } else if (tmp.getName().endsWith(\".ans\")) {\n                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(\".ans\"));\n                outputData.put(tmpPreName, tmp.getName());\n            }\n        }\n\n        // 设置 测试用例\n        List<ProblemCase> testCaseList = new ArrayList<>();\n        // 进行数据对应检查,同时生成返回数据\n        for (String key : inputData.keySet()) {\n            String inputFileName = inputData.get(key);\n            // 若有名字对应的out文件不存在的，直接生成对应的out文件\n            final String outputFileName = outputData.getOrDefault(key, null);\n            if (outputFileName == null) {\n                FileWriter fileWriter = new FileWriter(testCaseFileDir + File.separator + key + \".out\");\n                fileWriter.write(\"\");\n            }\n            testCaseList.add(new ProblemCase().setInput(inputFileName).setOutput(outputFileName));\n        }\n        int averageScore = 100 / testCaseList.size();\n        int add1Num = 100 - averageScore * testCaseList.size();\n        for (int i = 0; i < testCaseList.size(); i++) {\n            if (i >= testCaseList.size() - add1Num) {\n                testCaseList.get(i).setScore(averageScore + 1);\n            } else {\n                testCaseList.get(i).setScore(averageScore);\n            }\n        }\n        testCaseList = testCaseList.stream().sorted((o1, o2) -> {\n            String a = o1.getInput().split(\"\\\\.\")[0];\n            String b = o2.getInput().split(\"\\\\.\")[0];\n            if (a.length() > b.length()) {\n                return 1;\n            }\n            if (a.length() < b.length()) {\n                return -1;\n            }\n            return a.compareTo(b);\n        }).collect(Collectors.toList());\n\n        problemDTO.setUploadTestcaseDir(testCaseFileDir)\n                .setIsUploadTestCase(true)\n                .setSamples(testCaseList);\n    }\n\n    private ProblemDTO getProblemDTO(String id, String content) {\n        String description = ReUtil.getGroup1(\"<textarea class=\\\"markdown-edit\\\" rows=\\\"15\\\" id=\\\"description\\\" name=\\\"description\\\">([\\\\s\\\\S]*?)<\\\\/textarea>\",\n                content);\n        String title = ReUtil.getGroup1(\"<input class=\\\"font-content\\\" type=\\\"text\\\" id=\\\"title\\\" name=\\\"title\\\" value=\\\"([\\\\s\\\\S]*?)\\\">\",\n                content);\n        String inputFormat = ReUtil.getGroup1(\"<textarea class=\\\"markdown-edit\\\" rows=\\\"10\\\" id=\\\"input\\\" name=\\\"input_format\\\">([\\\\s\\\\S]*?)<\\\\/textarea>\",\n                content);\n        String outputFormat = ReUtil.getGroup1(\"<textarea class=\\\"markdown-edit\\\" rows=\\\"10\\\" id=\\\"output\\\" name=\\\"output_format\\\">([\\\\s\\\\S]*?)<\\\\/textarea>\",\n                content);\n//        String example = ReUtil.getGroup1(\"<textarea class=\\\"markdown-edit\\\" rows=\\\"15\\\" id=\\\"example\\\" name=\\\"example\\\">([\\\\s\\\\S]*?)<\\\\/textarea>\",\n//                content);\n\n        String hint = ReUtil.getGroup1(\"<textarea class=\\\"markdown-edit\\\" rows=\\\"10\\\" id=\\\"hint\\\" name=\\\"limit_and_hint\\\">([\\\\s\\\\S]*?)<\\\\/textarea>\",\n                content);\n//        String tags = ReUtil.getGroup1(\"<option value=\\\"\\\\d*\\\" selected>([\\\\s\\\\S]*?)<\\\\/option>\",\n//                content);\n\n        // 设置 tag\n        Matcher tagMatcher = TAG_PATTERN.matcher(content);\n        List<Tag> tagList = new ArrayList<>();\n        tagList.add(new Tag().setName(\"DSOJ\"));\n        while (tagMatcher.find()) {\n            tagList.add(new Tag().setName(tagMatcher.group(1)));\n        }\n\n        Matcher exampleMatcher = EXAMPLE_PATTERN.matcher(content);\n        StringBuilder sb = new StringBuilder();\n        while (exampleMatcher.find()) {\n            sb.append(\"<input>\").append(exampleMatcher.group(1)).append(\"</input>\")\n                    .append(\"<output>\").append(exampleMatcher.group(2)).append(\"</output>\");\n        }\n\n        Problem problem = Problem.builder()\n                .isRemote(true)\n                .type(ContestEnum.TYPE_ACM.getCode())\n                .auth(ProblemEnum.AUTH_PUBLIC.getCode())\n                .author(\"root\")\n                .openCaseResult(false)\n                .isRemoveEndBlank(false)\n                .difficulty(ProblemLevelEnum.PROBLEM_LEVEL_MID.getCode())\n                .isRemote(false)\n                .problemId(\"DSOJ-\" + id)\n                .title(title)\n                .description(description)\n                .input(inputFormat)\n                .output(outputFormat)\n                .examples(sb.toString())\n                .openCaseResult(true)\n                .isRemoveEndBlank(true)\n                // 数据范围及提示\n                .hint(hint)\n                .build();\n\n        List<Language> languages = languageEntityService.lambdaQuery().eq(Language::getOj, Constant.LOCAL).list();\n\n        return new ProblemDTO()\n                // 设置 Problem\n                .setProblem(problem)\n                // 设置题目语言\n                .setLanguages(languages)\n                .setTags(tagList)\n                .setJudgeMode(JudgeMode.DEFAULT.getMode());\n    }\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/ImportFpsProblemServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileWriter;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.core.util.XmlUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.utils.MyFileUtil;\nimport com.simplefanc.voj.backend.dao.problem.LanguageEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.file.ImportFpsProblemService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.*;\nimport com.simplefanc.voj.common.pojo.entity.problem.CodeTemplate;\nimport com.simplefanc.voj.common.pojo.entity.problem.Language;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.*;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:44\n * @Description:\n */\n\n@Service\n@RequiredArgsConstructor\npublic class ImportFpsProblemServiceImpl implements ImportFpsProblemService {\n\n    private final static List<String> TIME_UNITS = Arrays.asList(\"ms\", \"s\");\n\n    private final static List<String> MEMORY_UNITS = Arrays.asList(\"kb\", \"mb\");\n\n    private static final Map<String, String> FPS_MAP_VOJ = new HashMap<>() {\n        {\n            put(\"Python\", \"Python3\");\n            put(\"Go\", \"Golang\");\n            put(\"C\", \"C\");\n            put(\"C++\", \"C++\");\n            put(\"Java\", \"Java\");\n            put(\"C#\", \"C#\");\n        }\n    };\n\n    private final LanguageEntityService languageEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final FilePathProperties filePathProps;\n\n    /**\n     * @param file\n     * @MethodName importFpsProblem\n     * @Description zip文件导入题目 仅超级管理员可操作\n     * @Return\n     * @Since 2021/10/06\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void importFPSProblem(MultipartFile file) throws IOException {\n        String suffix = MyFileUtil.getFileSuffix(file);\n        if (!\"xml\".toUpperCase().contains(suffix.toUpperCase())) {\n            throw new StatusFailException(\"请上传xml后缀格式的fps题目文件！\");\n        }\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        List<ProblemDTO> problemDTOList = parseFps(file.getInputStream(), userRolesVO.getUsername());\n        for (ProblemDTO problemDTO : problemDTOList) {\n            problemEntityService.adminAddProblem(problemDTO);\n        }\n\n    }\n\n    // TODO 行数过多\n    private List<ProblemDTO> parseFps(InputStream inputStream, String username) {\n        Document document = XmlUtil.readXML(inputStream);\n        Element rootElement = XmlUtil.getRootElement(document);\n        String version = rootElement.getAttribute(\"version\");\n\n        List<ProblemDTO> problemDTOList = new ArrayList<>();\n\n        String fileDirId = IdUtil.simpleUUID();\n        String fileDir = filePathProps.getTestcaseTmpFolder() + File.separator + fileDirId;\n\n        int index = 1;\n        for (Element item : XmlUtil.getElements(rootElement, \"item\")) {\n\n            Problem problem = new Problem();\n\n            problem.setAuthor(username)\n                    .setType(ContestEnum.TYPE_ACM.getCode())\n                    .setIsUploadCase(true)\n                    .setDifficulty(ProblemLevelEnum.PROBLEM_LEVEL_MID.getCode())\n                    .setIsRemoveEndBlank(true)\n                    .setOpenCaseResult(true)\n                    .setCodeShare(false).setIsRemote(false)\n                    .setAuth(ProblemEnum.AUTH_PUBLIC.getCode())\n                    .setProblemId(String.valueOf(System.currentTimeMillis()));\n\n            Element title = XmlUtil.getElement(item, \"title\");\n            // 标题\n            problem.setTitle(title.getTextContent());\n\n            HashMap<String, String> srcMapUrl = new HashMap<>();\n            List<Element> images = XmlUtil.getElements(item, \"img\");\n            for (Element img : images) {\n                Element srcElement = XmlUtil.getElement(img, \"src\");\n                if (srcElement == null) {\n                    continue;\n                }\n                String src = srcElement.getTextContent();\n                String base64 = XmlUtil.getElement(img, \"base64\").getTextContent();\n                String[] split = src.split(\"\\\\.\");\n\n                byte[] decode = Base64.getDecoder().decode(base64);\n                String fileName = IdUtil.fastSimpleUUID() + \".\" + split[split.length - 1];\n\n                FileUtil.writeBytes(decode, filePathProps.getMarkdownFileFolder() + File.separator + fileName);\n                srcMapUrl.put(src, filePathProps.getImgApi() + fileName);\n            }\n\n            Element descriptionElement = XmlUtil.getElement(item, \"description\");\n            String description = descriptionElement.getTextContent();\n            for (Map.Entry<String, String> entry : srcMapUrl.entrySet()) {\n                description = description.replaceAll(entry.getKey(), entry.getValue());\n            }\n            // 题目描述\n            problem.setDescription(description);\n\n            Element inputElement = XmlUtil.getElement(item, \"input\");\n            String input = inputElement.getTextContent();\n            for (Map.Entry<String, String> entry : srcMapUrl.entrySet()) {\n                input = input.replaceAll(entry.getKey(), entry.getValue());\n            }\n            // 输入描述\n            problem.setInput(input);\n\n            Element outputElement = XmlUtil.getElement(item, \"output\");\n            String output = outputElement.getTextContent();\n            for (Map.Entry<String, String> entry : srcMapUrl.entrySet()) {\n                output = output.replaceAll(entry.getKey(), entry.getValue());\n            }\n            // 输出描述\n            problem.setOutput(output);\n\n            // 提示\n            Element hintElement = XmlUtil.getElement(item, \"hint\");\n            String hint = hintElement.getTextContent();\n            for (Map.Entry<String, String> entry : srcMapUrl.entrySet()) {\n                hint = hint.replaceAll(entry.getKey(), entry.getValue());\n            }\n            problem.setHint(hint);\n\n            // 来源\n            Element sourceElement = XmlUtil.getElement(item, \"source\");\n            String source = sourceElement.getTextContent();\n            problem.setSource(source);\n\n            // ms\n            Integer timeLimit = getTimeLimit(version, item);\n            problem.setTimeLimit(timeLimit);\n\n            // mb\n            Integer memoryLimit = getMemoryLimit(item);\n            problem.setMemoryLimit(memoryLimit);\n\n            // 题面用例\n            List<Element> sampleInputs = XmlUtil.getElements(item, \"sample_input\");\n            List<Element> sampleOutputs = XmlUtil.getElements(item, \"sample_output\");\n            StringBuilder sb = new StringBuilder();\n            for (int i = 0; i < sampleInputs.size(); i++) {\n                sb.append(\"<input>\").append(sampleInputs.get(i).getTextContent()).append(\"</input>\");\n                sb.append(\"<output>\").append(sampleOutputs.get(i).getTextContent()).append(\"</output>\");\n            }\n            problem.setExamples(sb.toString());\n\n            QueryWrapper<Language> languageQueryWrapper = new QueryWrapper<>();\n            languageQueryWrapper.eq(\"oj\", Constant.LOCAL);\n            List<Language> languageList = languageEntityService.list(languageQueryWrapper);\n\n            HashMap<String, Long> languageMap = new HashMap<>();\n            for (Language language : languageList) {\n                languageMap.put(language.getName(), language.getId());\n            }\n\n            // 题目模板\n            List<Element> templateNodes = XmlUtil.getElements(item, \"template\");\n            List<CodeTemplate> codeTemplates = new ArrayList<>();\n            for (Element templateNode : templateNodes) {\n                String templateLanguage = templateNode.getAttribute(\"language\");\n                String templateCode = templateNode.getTextContent();\n                if (templateLanguage == null || templateCode == null) {\n                    continue;\n                }\n                String lang = FPS_MAP_VOJ.get(templateLanguage);\n                if (lang != null) {\n                    codeTemplates.add(new CodeTemplate().setCode(templateCode).setLid(languageMap.get(lang)));\n                }\n\n            }\n\n            // spj\n            Element spjNode = XmlUtil.getElement(item, \"spj\");\n            if (spjNode != null) {\n                String spjLanguage = spjNode.getAttribute(\"language\");\n                String spjCode = spjNode.getTextContent();\n                if ((\"C\".equals(spjLanguage) || \"C++\".equals(spjLanguage)) && StrUtil.isNotEmpty(spjCode)) {\n                    problem.setSpjLanguage(spjLanguage).setSpjCode(spjCode);\n                }\n            }\n\n            // 题目评测数据\n            List<Element> testInputs = XmlUtil.getElements(item, \"test_input\");\n            List<Element> testOutputs = XmlUtil.getElements(item, \"test_output\");\n            List<ProblemCase> problemSamples = new LinkedList<>();\n            String problemTestCaseDir = fileDir + File.separator + index;\n            for (int i = 0; i < testInputs.size(); i++) {\n                String infileName = (i + 1) + \".in\";\n                String outfileName = (i + 1) + \".out\";\n                FileWriter infileWriter = new FileWriter(problemTestCaseDir + File.separator + infileName);\n                FileWriter outfileWriter = new FileWriter(problemTestCaseDir + File.separator + outfileName);\n                infileWriter.write(testInputs.get(i).getTextContent());\n                outfileWriter.write(testOutputs.get(i).getTextContent());\n                problemSamples.add(new ProblemCase().setInput(infileName).setOutput(outfileName));\n            }\n            String mode = JudgeMode.DEFAULT.getMode();\n            if (problem.getSpjLanguage() != null) {\n                mode = JudgeMode.SPJ.getMode();\n            }\n            ProblemDTO problemDTO = new ProblemDTO();\n            problemDTO.setSamples(problemSamples).setIsUploadTestCase(true).setUploadTestcaseDir(problemTestCaseDir)\n                    .setLanguages(languageList).setTags(null).setJudgeMode(mode).setProblem(problem)\n                    .setCodeTemplates(codeTemplates);\n\n            problemDTOList.add(problemDTO);\n            index++;\n        }\n        return problemDTOList;\n    }\n\n    private Integer getTimeLimit(String version, Element item) {\n        Element timeLimitNode = XmlUtil.getElement(item, \"time_limit\");\n        String timeUnit = timeLimitNode.getAttribute(\"unit\");\n        String timeLimit = timeLimitNode.getTextContent();\n        int index = TIME_UNITS.indexOf(timeUnit.toLowerCase());\n        if (index == -1) {\n            throw new RuntimeException(\"Invalid time limit unit:\" + timeUnit);\n        }\n        if (\"1.1\".equals(version)) {\n            return Integer.parseInt(timeLimit) * (int) Math.pow(1000, index);\n        } else {\n            double tmp = (Double.parseDouble(timeLimit) * Math.pow(1000, index));\n            return (int) tmp;\n        }\n    }\n\n    private Integer getMemoryLimit(Element item) {\n        Element memoryLimitNode = XmlUtil.getElement(item, \"memory_limit\");\n        String memoryUnit = memoryLimitNode.getAttribute(\"unit\");\n        String memoryLimit = memoryLimitNode.getTextContent();\n        int index = MEMORY_UNITS.indexOf(memoryUnit.toLowerCase());\n        if (index == -1) {\n            throw new RuntimeException(\"Invalid memory limit unit:\" + memoryUnit);\n        }\n\n        return Integer.parseInt(memoryLimit) * (int) Math.pow(1000, index - 1);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/ImportLOJProblemServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.http.HttpRequest;\nimport cn.hutool.http.HttpUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusNotFoundException;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.dao.problem.LanguageEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.judge.remote.crawler.AbstractProblemCrawler;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.service.file.ImportLOJProblemService;\nimport com.simplefanc.voj.common.constants.Constant;\nimport com.simplefanc.voj.common.constants.JudgeMode;\nimport com.simplefanc.voj.common.pojo.entity.problem.Language;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport lombok.Data;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.*;\nimport java.util.stream.Collectors;\n\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class ImportLOJProblemServiceImpl implements ImportLOJProblemService {\n    public static final String JUDGE_NAME = \"LOJ\";\n    private static final String GET_PROBLEM_URL = \"https://api.loj.ac.cn/api/problem/getProblem\";\n    private static final String DOWNLOAD_PROBLEM_FILES_URL = \"https://api.loj.ac.cn/api/problem/downloadProblemFiles\";\n\n    private final FilePathProperties filePathProps;\n    private final LanguageEntityService languageEntityService;\n    private final ProblemEntityService problemEntityService;\n\n\n    public boolean importLOJProblem(Integer problemId) {\n        Problem problem = problemEntityService.lambdaQuery()\n                .eq(Problem::getProblemId, JUDGE_NAME + \"-\" + problemId)\n                .one();\n        if (problem != null) {\n            throw new StatusFailException(\"该题目已添加，请勿重复添加！\");\n        }\n        return problemEntityService.adminAddProblem(getProblemDTO(problemId));\n    }\n\n    private ProblemDTO getProblemDTO(Integer problemId) {\n        ProblemDTO problemDTO = new ProblemDTO();\n\n        Map<String, Object> map = MapUtil.builder(new HashMap<String, Object>())\n                .put(\"displayId\", problemId)\n                .put(\"localizedContentsOfLocale\", \"zh_CN\")\n                .put(\"tagsOfLocale\", \"zh_CN\")\n                .put(\"samples\", true)\n                .put(\"judgeInfo\", true)\n                .put(\"judgeInfoToBePreprocessed\", true)\n                .put(\"statistics\", true)\n                .put(\"discussionCount\", true)\n                .put(\"permissionOfCurrentUser\", true)\n                .put(\"lastSubmissionAndLastAcceptedSubmission\", true).build();\n        String json = JSONUtil.toJsonStr(map);\n        String body = HttpRequest.post(GET_PROBLEM_URL).body(json).execute().body();\n        LOJProblem lojProblem = JSONUtil.toBean(body, LOJProblem.class);\n        Contents contents = lojProblem.getLocalizedContentsOfLocale();\n        if (contents == null) {\n            throw new StatusNotFoundException(\"该题号对应的题目不存在\");\n        }\n        JudgeInfo judgeInfo = lojProblem.getJudgeInfo();\n        List<Sample> samples = lojProblem.getSamples();\n        // 设置 tag\n        List<LOJTag> tags = lojProblem.getTagsOfLocale();\n        List<Tag> tagList = tags.stream().map(lojTag -> new Tag().setName(lojTag.name)).collect(Collectors.toList());\n        problemDTO.setTags(tagList);\n\n        AbstractProblemCrawler.RemoteProblemInfo problemInfo = new AbstractProblemCrawler.RemoteProblemInfo();\n        problemInfo.setTagList(tagList);\n        Problem problem = problemInfo.getProblem();\n        String examples = samples.stream().map(sample -> \"<input>\" + sample.getInputData() + \"</input>\" +\n                        \"<output>\" + sample.getOutputData() + \"</output>\")\n                .collect(Collectors.joining(\"\"));\n        List<Contents.Section> contentSections = contents.contentSections;\n        problem.setIsRemote(false)\n                .setProblemId(JUDGE_NAME + \"-\" + problemId)\n                .setTitle(contents.title)\n                .setTimeLimit(judgeInfo.timeLimit)\n                .setMemoryLimit(judgeInfo.memoryLimit)\n                .setDescription(contentSections.get(0).text)\n                .setInput(contentSections.get(1).text)\n                .setOutput(contentSections.get(2).text)\n                .setExamples(examples)\n                .setOpenCaseResult(true)\n                .setIsRemoveEndBlank(true)\n                // 数据范围及提示\n                .setHint(contentSections.size() > 4 ? contentSections.get(4).text : null)\n                .setSource(String.format(\"<a style='color:#1A5CC8' href='https://loj.ac/p/%d'>%s</a>\", problemId, JUDGE_NAME + \"-\" + problemId));\n        // 设置 Problem\n        problemDTO.setProblem(problem);\n\n        // 设置 测试用例\n        downloadProblemFiles(problemDTO, problemId, judgeInfo);\n\n        // 设置题目语言\n        List<Language> languages = languageEntityService.lambdaQuery().eq(Language::getOj, Constant.LOCAL).list();\n        problemDTO.setLanguages(languages).setJudgeMode(JudgeMode.DEFAULT.getMode());\n\n        return problemDTO;\n    }\n\n    private void downloadProblemFiles(ProblemDTO problemDTO, Integer problemId, JudgeInfo judgeInfo) {\n        // 下载测试用例到本地\n        // 多线程\n        List<JudgeInfo.Task.Testcase> testcases = judgeInfo.subtasks.get(0).testcases;\n        // 每个Testcase需要下载 inputFile 和 outputFile\n        List<String> filenameList = new ArrayList<>();\n        testcases.forEach(testcase -> {\n            filenameList.add(testcase.inputFile);\n            filenameList.add(testcase.outputFile);\n        });\n        Map<String, Object> map = MapUtil.builder(new HashMap<String, Object>())\n                .put(\"problemId\", problemId)\n                .put(\"type\", \"TestData\")\n                .put(\"filenameList\", filenameList).build();\n        String json = JSONUtil.toJsonStr(map);\n        String body = HttpRequest.post(DOWNLOAD_PROBLEM_FILES_URL).body(json).execute().body();\n        DownloadInfo downloadInfo = JSONUtil.toBean(body, DownloadInfo.class);\n        List<DownloadInfo.Info> downloadInfos = downloadInfo.downloadInfo;\n        CompletableFuture[] futures = new CompletableFuture[downloadInfos.size()];\n        // 使用线程池\n        ExecutorService threadPool = new ThreadPoolExecutor(\n                // 核心线程数\n                2,\n                // 最大线程数。最多几个线程并发。\n                4,\n                // 当非核心线程无任务时，几秒后结束该线程\n                3,\n                // 结束线程时间单位\n                TimeUnit.SECONDS,\n                // 阻塞队列，限制等候线程数\n                new LinkedBlockingDeque<>(200), Executors.defaultThreadFactory(),\n                // 队列满了，尝试去和最早的竞争，也不会抛出异常！\n                new ThreadPoolExecutor.DiscardOldestPolicy());\n        String fileDir = filePathProps.getTestcaseTmpFolder() + File.separator + IdUtil.simpleUUID();\n        // 新建文件夹\n        FileUtil.mkdir(fileDir);\n        for (int i = 0; i < downloadInfos.size(); i++) {\n            DownloadInfo.Info info = downloadInfos.get(i);\n            futures[i] = CompletableFuture.supplyAsync(() ->\n                            HttpUtil.downloadFile(info.downloadUrl, FileUtil.file(fileDir + File.separator + info.filename)),\n                    threadPool);\n        }\n\n        // allOf() 方法会等到所有的 CompletableFuture 都运行完成之后再返回\n        CompletableFuture<Void> headerFuture = CompletableFuture.allOf(futures);\n        // 都运行完了之后再继续执行\n        headerFuture.join();\n        // 关闭线程池\n        threadPool.shutdownNow();\n\n        List<ProblemCase> problemCaseList = testcases.stream()\n                .map(testcase -> new ProblemCase().setInput(testcase.inputFile).setOutput(testcase.outputFile))\n                .collect(Collectors.toList());\n        int averageScore = 100 / problemCaseList.size();\n        int add1Num = 100 - averageScore * problemCaseList.size();\n        for (int i = 0; i < problemCaseList.size(); i++) {\n            if (i >= problemCaseList.size() - add1Num) {\n                problemCaseList.get(i).setScore(averageScore + 1);\n            } else {\n                problemCaseList.get(i).setScore(averageScore);\n            }\n        }\n        problemDTO.setUploadTestcaseDir(fileDir)\n                .setIsUploadTestCase(true)\n                .setSamples(problemCaseList);\n//        List<Long> res = new ArrayList<>();\n//        for (int i = 0; i < downloadInfos.size(); i++) {\n//            res.add((Long) futures[i].get());\n//        }\n    }\n\n    @Data\n    class LOJProblem {\n        Contents localizedContentsOfLocale;\n\n        JudgeInfo judgeInfo;\n\n        List<Sample> samples;\n\n        List<LOJTag> tagsOfLocale;\n    }\n\n    @Data\n    class Contents {\n        String title;\n        List<Section> contentSections;\n\n        @Data\n        class Section {\n            String sectionTitle;\n            String type;\n            String text;\n        }\n    }\n\n    @Data\n    class JudgeInfo {\n        Integer timeLimit;\n        Integer memoryLimit;\n        List<Task> subtasks;\n\n        @Data\n        class Task {\n            List<Testcase> testcases;\n\n            @Data\n            class Testcase {\n                String inputFile;\n                String outputFile;\n            }\n        }\n    }\n\n    @Data\n    class Sample {\n        String inputData;\n        String outputData;\n    }\n\n    @Data\n    class LOJTag {\n        String name;\n    }\n\n    ///////////////////////////////////\n    @Data\n    class DownloadInfo {\n        List<Info> downloadInfo;\n\n        @Data\n        class Info {\n            String filename;\n            String downloadUrl;\n        }\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/ImportQDUOJProblemServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileReader;\nimport cn.hutool.core.text.UnicodeUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.ZipUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusSystemErrorException;\nimport com.simplefanc.voj.backend.common.utils.MyFileUtil;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.dao.problem.LanguageEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.dao.problem.TagEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.pojo.dto.QDOJProblemDTO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.file.ImportQDUOJProblemService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.*;\nimport com.simplefanc.voj.common.pojo.entity.problem.Language;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:47\n * @Description:\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class ImportQDUOJProblemServiceImpl implements ImportQDUOJProblemService {\n\n    private final LanguageEntityService languageEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final TagEntityService tagEntityService;\n\n    private final FilePathProperties filePathProps;\n\n    /**\n     * @param file\n     * @MethodName importQDOJProblem\n     * @Description zip文件导入题目 仅超级管理员可操作\n     * @Return\n     * @Since 2021/5/27\n     */\n    // TODO 行数过多\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void importQDOJProblem(MultipartFile file) {\n        String suffix = MyFileUtil.getFileSuffix(file);\n        if (!\"zip\".toUpperCase().contains(suffix.toUpperCase())) {\n            throw new StatusFailException(\"请上传zip格式的题目文件压缩包！\");\n        }\n\n        String fileDirId = IdUtil.simpleUUID();\n        String fileDir = filePathProps.getTestcaseTmpFolder() + File.separator + fileDirId;\n        String filePath = fileDir + File.separator + file.getOriginalFilename();\n        // 文件夹不存在就新建\n        FileUtil.mkdir(fileDir);\n        try {\n            file.transferTo(new File(filePath));\n        } catch (IOException e) {\n            FileUtil.del(fileDir);\n            throw new StatusSystemErrorException(\"服务器异常：qduoj题目上传失败！\");\n        }\n\n        // 将压缩包压缩到指定文件夹\n        ZipUtil.unzip(filePath, fileDir);\n\n        // 删除zip文件\n        FileUtil.del(filePath);\n\n        // 检查文件是否存在\n        File testCaseFileList = new File(fileDir);\n        File[] files = testCaseFileList.listFiles();\n        if (files == null || files.length == 0) {\n            FileUtil.del(fileDir);\n            throw new StatusFailException(\"评测数据压缩包里文件不能为空！\");\n        }\n\n        HashMap<String, File> problemInfo = new HashMap<>();\n        for (File tmp : files) {\n            if (tmp.isDirectory()) {\n                File[] problemAndTestcase = tmp.listFiles();\n                if (problemAndTestcase == null || problemAndTestcase.length == 0) {\n                    FileUtil.del(fileDir);\n                    throw new StatusFailException(\"编号为：\" + tmp.getName() + \"的文件夹为空！\");\n                }\n                for (File problemFile : problemAndTestcase) {\n                    if (problemFile.isFile()) {\n                        // 检查文件是否时json文件\n                        if (!problemFile.getName().endsWith(\"json\")) {\n                            FileUtil.del(fileDir);\n                            throw new StatusFailException(\"编号为：\" + tmp.getName() + \"的文件夹里面的题目数据格式错误，请使用json文件！\");\n                        }\n                        problemInfo.put(tmp.getName(), problemFile);\n                    }\n                }\n            }\n        }\n\n        // 读取json文件生成对象\n        HashMap<String, QDOJProblemDTO> problemVOMap = new HashMap<>();\n        for (String key : problemInfo.keySet()) {\n            try {\n                FileReader fileReader = new FileReader(problemInfo.get(key));\n                JSONObject problemJson = JSONUtil.parseObj(fileReader.readString());\n                QDOJProblemDTO qdojProblemDTO = QDOJProblemToProblemVO(problemJson);\n                problemVOMap.put(key, qdojProblemDTO);\n            } catch (Exception e) {\n                FileUtil.del(fileDir);\n                throw new StatusFailException(\"请检查编号为：\" + key + \"的题目json文件的格式：\" + e.getLocalizedMessage());\n            }\n        }\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        List<Tag> tagList = tagEntityService.list(new QueryWrapper<Tag>().eq(\"oj\", Constant.LOCAL));\n        HashMap<String, Tag> tagMap = new HashMap<>();\n        for (Tag tag : tagList) {\n            tagMap.put(tag.getName().toUpperCase(), tag);\n        }\n\n        List<ProblemDTO> problemDTOs = new LinkedList<>();\n        for (String key : problemInfo.keySet()) {\n            QDOJProblemDTO qdojProblemDTO = problemVOMap.get(key);\n            // 格式化题目语言\n            List<Language> languages = languageEntityService.lambdaQuery().eq(Language::getOj, Constant.LOCAL).list();\n\n            // 格式化标签\n            List<Tag> tags = new LinkedList<>();\n            for (String tagStr : qdojProblemDTO.getTags()) {\n                Tag tag = tagMap.getOrDefault(tagStr.toUpperCase(), null);\n                if (tag == null) {\n                    tags.add(new Tag().setName(tagStr).setOj(Constant.LOCAL));\n                } else {\n                    tags.add(tag);\n                }\n            }\n\n            Problem problem = qdojProblemDTO.getProblem();\n            if (problem.getAuthor() == null) {\n                problem.setAuthor(userRolesVO.getUsername());\n            }\n            ProblemDTO problemDTO = new ProblemDTO();\n\n            String mode = JudgeMode.DEFAULT.getMode();\n            if (qdojProblemDTO.getIsSpj()) {\n                mode = JudgeMode.SPJ.getMode();\n            }\n\n            problemDTO.setJudgeMode(mode).setProblem(problem).setCodeTemplates(qdojProblemDTO.getCodeTemplates())\n                    .setTags(tags).setLanguages(languages)\n                    .setUploadTestcaseDir(fileDir + File.separator + key + File.separator + \"testcase\")\n                    .setIsUploadTestCase(true).setSamples(qdojProblemDTO.getSamples());\n\n            problemDTOs.add(problemDTO);\n        }\n        for (ProblemDTO problemDTO : problemDTOs) {\n            problemEntityService.adminAddProblem(problemDTO);\n        }\n    }\n\n    private QDOJProblemDTO QDOJProblemToProblemVO(JSONObject problemJson) {\n        QDOJProblemDTO qdojProblemDTO = new QDOJProblemDTO();\n        List<String> tags = (List<String>) problemJson.get(\"tags\");\n        qdojProblemDTO.setTags(tags.stream().map(UnicodeUtil::toString).collect(Collectors.toList()));\n        Object spj = problemJson.getObj(\"spj\");\n        boolean isSpj = !JSONUtil.isNull(spj);\n        qdojProblemDTO.setIsSpj(isSpj);\n\n        Problem problem = new Problem();\n        if (isSpj) {\n            JSONObject spjJson = JSONUtil.parseObj(spj);\n            problem.setSpjCode(spjJson.getStr(\"code\")).setSpjLanguage(spjJson.getStr(\"language\"));\n        }\n        problem.setAuth(ProblemEnum.AUTH_PUBLIC.getCode())\n                .setIsUploadCase(true).setSource(problemJson.getStr(\"source\", null))\n                .setDifficulty(ProblemLevelEnum.PROBLEM_LEVEL_MID.getCode())\n                .setProblemId(problemJson.getStr(\"display_id\")).setIsRemoveEndBlank(true).setOpenCaseResult(true)\n                .setCodeShare(false).setType(\"ACM\".equals(problemJson.getStr(\"rule_type\")) ? ContestEnum.TYPE_ACM.getCode() : ContestEnum.TYPE_OI.getCode())\n                .setTitle(problemJson.getStr(\"title\"))\n                .setDescription(UnicodeUtil.toString(problemJson.getJSONObject(\"description\").getStr(\"value\")))\n                .setInput(UnicodeUtil.toString(problemJson.getJSONObject(\"input_description\").getStr(\"value\")))\n                .setOutput(UnicodeUtil.toString(problemJson.getJSONObject(\"output_description\").getStr(\"value\")))\n                .setHint(UnicodeUtil.toString(problemJson.getJSONObject(\"hint\").getStr(\"value\")))\n                .setTimeLimit(problemJson.getInt(\"time_limit\")).setMemoryLimit(problemJson.getInt(\"memory_limit\"));\n\n        JSONArray samples = problemJson.getJSONArray(\"samples\");\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < samples.size(); i++) {\n            JSONObject sample = (JSONObject) samples.get(i);\n            String input = sample.getStr(\"input\");\n            String output = sample.getStr(\"output\");\n            sb.append(\"<input>\").append(input).append(\"</input>\");\n            sb.append(\"<output>\").append(output).append(\"</output>\");\n        }\n        problem.setExamples(sb.toString());\n\n        JSONArray testcaseList = problemJson.getJSONArray(\"test_case_score\");\n        List<ProblemCase> problemSamples = new LinkedList<>();\n        for (int i = 0; i < testcaseList.size(); i++) {\n            JSONObject testcase = (JSONObject) testcaseList.get(i);\n            String input = testcase.getStr(\"input_name\");\n            String output = testcase.getStr(\"output_name\");\n            Integer score = testcase.getInt(\"score\", null);\n            problemSamples.add(new ProblemCase().setInput(input).setOutput(output).setScore(score));\n        }\n        problem.setIsRemote(false);\n        qdojProblemDTO.setSamples(problemSamples);\n        qdojProblemDTO.setProblem(problem);\n        return qdojProblemDTO;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/MarkDownFileServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.IdUtil;\nimport com.simplefanc.voj.backend.common.constants.FileTypeEnum;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.exception.StatusSystemErrorException;\nimport com.simplefanc.voj.backend.common.utils.MyFileUtil;\nimport com.simplefanc.voj.backend.dao.common.FileEntityService;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.file.MarkDownFileService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.File;\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:50\n * @Description:\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class MarkDownFileServiceImpl implements MarkDownFileService {\n\n    private final FileEntityService fileEntityService;\n\n    private final FilePathProperties filePathProps;\n\n    @Override\n    public Map<Object, Object> uploadMDImg(MultipartFile image) {\n        if (image == null) {\n            throw new StatusFailException(\"上传的图片不能为空！\");\n        }\n        if (image.getSize() > 1024 * 1024 * 4) {\n            throw new StatusFailException(\"上传的图片文件大小不能大于4M！\");\n        }\n        // 获取文件后缀\n        String suffix = image.getOriginalFilename().substring(image.getOriginalFilename().lastIndexOf(\".\") + 1);\n        if (!\"jpg,jpeg,gif,png,webp\".toUpperCase().contains(suffix.toUpperCase())) {\n            throw new StatusFailException(\"请选择jpg,jpeg,gif,png,webp格式的图片！\");\n        }\n\n        // 若不存在该目录，则创建目录\n        FileUtil.mkdir(filePathProps.getMarkdownFileFolder());\n\n        // 通过UUID生成唯一文件名\n        String filename = IdUtil.simpleUUID() + \".\" + suffix;\n        try {\n            // 将文件保存指定目录\n            image.transferTo(FileUtil.file(filePathProps.getMarkdownFileFolder() + File.separator + filename));\n        } catch (Exception e) {\n            log.error(\"图片文件上传异常-------------->\", e);\n            throw new StatusSystemErrorException(\"服务器异常：图片文件上传失败！\");\n        }\n\n        // 获取当前登录用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        com.simplefanc.voj.common.pojo.entity.common.File file = new com.simplefanc.voj.common.pojo.entity.common.File();\n        file.setFolderPath(filePathProps.getMarkdownFileFolder()).setName(filename)\n                .setFilePath(filePathProps.getMarkdownFileFolder() + File.separator + filename).setSuffix(suffix)\n                .setType(FileTypeEnum.MARKDOWN.getType()).setUid(userRolesVO.getUid());\n        fileEntityService.save(file);\n\n        return MapUtil.builder().put(\"link\", filePathProps.getImgApi() + filename).put(\"fileId\", file.getId()).map();\n\n    }\n\n    @Override\n    public void deleteMDImg(Long fileId) {\n\n        // 获取当前登录用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        com.simplefanc.voj.common.pojo.entity.common.File file = fileEntityService.getById(fileId);\n\n        if (file == null) {\n            throw new StatusFailException(\"错误：文件不存在！\");\n        }\n\n        if (!FileTypeEnum.MARKDOWN.getType().equals(file.getType())) {\n            throw new StatusForbiddenException(\"错误：不支持删除！\");\n        }\n\n        boolean isRoot = UserSessionUtil.isRoot();\n        boolean isProblemAdmin = UserSessionUtil.isProblemAdmin();\n        boolean isAdmin = UserSessionUtil.isAdmin();\n\n        if (!file.getUid().equals(userRolesVO.getUid()) && !isRoot && !isAdmin && !isProblemAdmin) {\n            throw new StatusForbiddenException(\"错误：无权删除他人文件！\");\n        }\n\n        boolean isOk = FileUtil.del(file.getFilePath());\n        if (isOk) {\n            fileEntityService.removeById(fileId);\n        } else {\n            throw new StatusFailException(\"删除失败\");\n        }\n\n    }\n\n    @Override\n    public Map<Object, Object> uploadMd(MultipartFile file) {\n        if (file == null) {\n            throw new StatusFailException(\"上传的文件不能为空！\");\n        }\n        if (file.getSize() >= 1024 * 1024 * 128) {\n            throw new StatusFailException(\"上传的文件大小不能大于128M！\");\n        }\n        // 获取文件后缀\n        String suffix = \"\";\n        String filename = \"\";\n        if (file.getOriginalFilename() != null && file.getOriginalFilename().contains(\".\")) {\n            suffix = MyFileUtil.getFileSuffix(file);\n            // 通过UUID生成唯一文件名\n            filename = IdUtil.simpleUUID() + \".\" + suffix;\n        } else {\n            filename = IdUtil.simpleUUID();\n        }\n        // 若不存在该目录，则创建目录\n        FileUtil.mkdir(filePathProps.getMarkdownFileFolder());\n\n        try {\n            // 将文件保存指定目录\n            file.transferTo(FileUtil.file(filePathProps.getMarkdownFileFolder() + File.separator + filename));\n        } catch (Exception e) {\n            log.error(\"文件上传异常-------------->\", e);\n            throw new StatusSystemErrorException(\"服务器异常：文件上传失败！\");\n        }\n\n        return MapUtil.builder().put(\"link\", filePathProps.getFileApi() + filename).map();\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/ProblemFileServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileReader;\nimport cn.hutool.core.io.file.FileWriter;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.ZipUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusSystemErrorException;\nimport com.simplefanc.voj.backend.common.utils.MyFileUtil;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.dao.problem.LanguageEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemCaseEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.dao.problem.TagEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.ProblemDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ImportProblemVO;\nimport com.simplefanc.voj.backend.service.file.ProblemFileService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.Constant;\nimport com.simplefanc.voj.common.pojo.entity.problem.*;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.*;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:40\n * @Description:\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class ProblemFileServiceImpl implements ProblemFileService {\n\n    private final LanguageEntityService languageEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final ProblemCaseEntityService problemCaseEntityService;\n\n    private final TagEntityService tagEntityService;\n\n    private final FilePathProperties filePathProps;\n\n    /**\n     * @param file\n     * @MethodName importProblem\n     * @Description zip文件导入题目 仅超级管理员可操作\n     * @Return\n     * @Since 2021/5/27\n     */\n    // TODO 行数过多\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void importProblem(MultipartFile file) {\n        String suffix = MyFileUtil.getFileSuffix(file);\n        if (!\"zip\".toUpperCase().contains(suffix.toUpperCase())) {\n            throw new StatusFailException(\"请上传zip格式的题目文件压缩包！\");\n        }\n\n        String fileDirId = IdUtil.simpleUUID();\n        String fileDir = filePathProps.getTestcaseTmpFolder() + File.separator + fileDirId;\n        String filePath = fileDir + File.separator + file.getOriginalFilename();\n        // 文件夹不存在就新建\n        FileUtil.mkdir(fileDir);\n        try {\n            file.transferTo(new File(filePath));\n        } catch (IOException e) {\n            FileUtil.del(fileDir);\n            throw new StatusSystemErrorException(\"服务器异常：评测数据上传失败！\");\n        }\n\n        // 将压缩包压缩到指定文件夹\n        ZipUtil.unzip(filePath, fileDir);\n\n        // 删除zip文件\n        FileUtil.del(filePath);\n\n        // 检查文件是否存在\n        File testCaseFileList = new File(fileDir);\n        File[] files = testCaseFileList.listFiles();\n        if (files == null || files.length == 0) {\n            FileUtil.del(fileDir);\n            throw new StatusFailException(\"评测数据压缩包里文件不能为空！\");\n        }\n\n        HashMap<String, File> problemInfo = new HashMap<>();\n        HashMap<String, File> testcaseInfo = new HashMap<>();\n\n        for (File tmp : files) {\n            if (tmp.isFile()) {\n                // 检查文件是否是json文件\n                if (!tmp.getName().endsWith(\"json\")) {\n                    FileUtil.del(fileDir);\n                    throw new StatusFailException(\"编号为：\" + tmp.getName() + \"的文件格式错误，请使用json文件！\");\n                }\n                String tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(\".\"));\n                problemInfo.put(tmpPreName, tmp);\n            }\n            if (tmp.isDirectory()) {\n                testcaseInfo.put(tmp.getName(), tmp);\n            }\n        }\n\n        // 读取json文件生成对象\n        HashMap<String, ImportProblemVO> problemVOMap = new HashMap<>();\n        for (String key : problemInfo.keySet()) {\n            // 若有名字不对应，直接返回失败\n            if (testcaseInfo.getOrDefault(key, null) == null) {\n                FileUtil.del(fileDir);\n                throw new StatusFailException(\"请检查编号为：\" + key + \"的题目数据文件与测试数据文件夹是否一一对应！\");\n            }\n            try {\n                FileReader fileReader = new FileReader(problemInfo.get(key));\n                ImportProblemVO importProblemVO = JSONUtil.toBean(fileReader.readString(), ImportProblemVO.class);\n                problemVOMap.put(key, importProblemVO);\n            } catch (Exception e) {\n                FileUtil.del(fileDir);\n                throw new StatusFailException(\"请检查编号为：\" + key + \"的题目json文件的格式：\" + e.getLocalizedMessage());\n            }\n        }\n\n        QueryWrapper<Language> languageQueryWrapper = new QueryWrapper<>();\n        languageQueryWrapper.eq(\"oj\", Constant.LOCAL);\n        List<Language> languageList = languageEntityService.list(languageQueryWrapper);\n        HashMap<String, Long> languageMap = new HashMap<>();\n        for (Language language : languageList) {\n            languageMap.put(language.getName(), language.getId());\n        }\n\n        List<ProblemDTO> problemDTOs = new LinkedList<>();\n        for (String key : problemInfo.keySet()) {\n            ImportProblemVO importProblemVO = problemVOMap.get(key);\n            // 1. 格式化题目语言\n            List<Language> languages = new LinkedList<>();\n            for (String lang : importProblemVO.getLanguages()) {\n                Long lid = languageMap.getOrDefault(lang, null);\n\n                if (lid == null) {\n                    throw new StatusFailException(\"请检查编号为：\" + key + \"的题目的代码语言是否有错，不要添加不支持的语言！\");\n                }\n                languages.add(new Language().setId(lid).setName(lang));\n            }\n\n            // 2. 格式化题目代码模板\n            List<CodeTemplate> codeTemplates = new LinkedList<>();\n            for (Map<String, String> tmp : importProblemVO.getCodeTemplates()) {\n                String language = tmp.getOrDefault(\"language\", null);\n                String code = tmp.getOrDefault(\"code\", null);\n                Long lid = languageMap.getOrDefault(language, null);\n                if (language == null || code == null || lid == null) {\n                    FileUtil.del(fileDir);\n                    throw new StatusFailException(\"请检查编号为：\" + key + \"的题目的代码模板列表是否有错，不要添加不支持的语言！\");\n                }\n                codeTemplates.add(new CodeTemplate().setCode(code).setStatus(true).setLid(lid));\n            }\n\n            // 3. 格式化标签\n            List<Tag> tagList = tagEntityService.list(new QueryWrapper<Tag>().eq(\"oj\", Constant.LOCAL));\n            HashMap<String, Tag> tagMap = new HashMap<>();\n            for (Tag tag : tagList) {\n                tagMap.put(tag.getName().toUpperCase(), tag);\n            }\n            List<Tag> tags = new LinkedList<>();\n            for (String tagStr : importProblemVO.getTags()) {\n                Tag tag = tagMap.getOrDefault(tagStr.toUpperCase(), null);\n                if (tag == null) {\n                    tags.add(new Tag().setName(tagStr).setOj(Constant.LOCAL));\n                } else {\n                    tags.add(tag);\n                }\n            }\n\n            // 4. 格式化测试样例\n            List<ProblemCase> problemCaseList = new LinkedList<>();\n            for (Map<String, Object> tmp : importProblemVO.getSamples()) {\n                problemCaseList.add(BeanUtil.toBeanIgnoreError(tmp, ProblemCase.class));\n            }\n\n            Problem problem = BeanUtil.toBeanIgnoreError(importProblemVO.getProblem(), Problem.class);\n            // 5. 获取当前登录的用户\n            if (problem.getAuthor() == null) {\n                problem.setAuthor(UserSessionUtil.getUserInfo().getUsername());\n            }\n\n            // 6. 格式化用户额外文件和判题额外文件\n            if (importProblemVO.getUserExtraFile() != null) {\n                JSONObject userExtraFileJson = JSONUtil.parseObj(importProblemVO.getUserExtraFile());\n                problem.setUserExtraFile(userExtraFileJson.toString());\n            }\n            if (importProblemVO.getJudgeExtraFile() != null) {\n                JSONObject judgeExtraFileJson = JSONUtil.parseObj(importProblemVO.getJudgeExtraFile());\n                problem.setJudgeExtraFile(judgeExtraFileJson.toString());\n            }\n\n            ProblemDTO problemDTO = new ProblemDTO();\n            problemDTO.setJudgeMode(importProblemVO.getJudgeMode()).setProblem(problem).setCodeTemplates(codeTemplates)\n                    .setTags(tags).setLanguages(languages).setUploadTestcaseDir(fileDir + File.separator + key)\n                    .setIsUploadTestCase(true).setSamples(problemCaseList);\n\n            problemDTOs.add(problemDTO);\n        }\n        for (ProblemDTO problemDTO : problemDTOs) {\n            problemEntityService.adminAddProblem(problemDTO);\n        }\n    }\n\n    /**\n     * @param pidList\n     * @param response\n     * @MethodName exportProblem\n     * @Description 导出指定的题目包括测试数据生成zip 仅超级管理员可操作\n     * @Return\n     * @Since 2021/5/28\n     */\n    @Override\n    public void exportProblem(List<Long> pidList, HttpServletResponse response) {\n\n        QueryWrapper<Language> languageQueryWrapper = new QueryWrapper<>();\n        languageQueryWrapper.eq(\"oj\", Constant.LOCAL);\n        List<Language> languageList = languageEntityService.list(languageQueryWrapper);\n\n        HashMap<Long, String> languageMap = new HashMap<>();\n        for (Language language : languageList) {\n            languageMap.put(language.getId(), language.getName());\n        }\n\n        List<Tag> tagList = tagEntityService.list();\n\n        HashMap<Long, String> tagMap = new HashMap<>();\n        for (Tag tag : tagList) {\n            tagMap.put(tag.getId(), tag.getName());\n        }\n\n        String workDir = filePathProps.getFileDownloadTmpFolder() + File.separator + IdUtil.simpleUUID();\n\n        // 使用线程池\n        ExecutorService threadPool = new ThreadPoolExecutor(\n                // 核心线程数\n                2,\n                // 最大线程数。最多几个线程并发。\n                4,\n                // 当非核心线程无任务时，几秒后结束该线程\n                3,\n                // 结束线程时间单位\n                TimeUnit.SECONDS,\n                // 阻塞队列，限制等候线程数\n                new LinkedBlockingDeque<>(200),\n                Executors.defaultThreadFactory(),\n                // 队列满了，尝试去和最早的竞争，也不会抛出异常！\n                new ThreadPoolExecutor.DiscardOldestPolicy());\n\n        List<FutureTask<Void>> futureTasks = new ArrayList<>();\n        for (Long pid : pidList) {\n            futureTasks.add(new FutureTask<>(new ExportProblemTask(workDir, pid, languageMap, tagMap)));\n        }\n        // 提交到线程池进行执行\n        for (FutureTask<Void> futureTask : futureTasks) {\n            threadPool.submit(futureTask);\n        }\n        // 所有任务执行完成且等待队列中也无任务关闭线程池\n        if (!threadPool.isShutdown()) {\n            threadPool.shutdown();\n        }\n        // 阻塞主线程, 直至线程池关闭\n        try {\n            threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);\n        } catch (InterruptedException e) {\n            log.error(\"线程池异常--------------->\", e);\n        }\n\n        String fileName = \"problem_export_\" + System.currentTimeMillis() + \".zip\";\n        // 将对应文件夹的文件压缩成zip\n        ZipUtil.zip(workDir, filePathProps.getFileDownloadTmpFolder() + File.separator + fileName);\n        MyFileUtil.download(response, filePathProps.getFileDownloadTmpFolder() + File.separator + fileName, fileName, \"导出题目数据的压缩文件异常，请重新尝试！\");\n        // 清空临时文件\n        FileUtil.del(workDir);\n        FileUtil.del(filePathProps.getFileDownloadTmpFolder() + File.separator + fileName);\n    }\n\n\n    class ExportProblemTask implements Callable<Void> {\n        String workDir;\n        Long pid;\n        HashMap<Long, String> languageMap;\n        HashMap<Long, String> tagMap;\n\n        public ExportProblemTask(String workDir, Long pid, HashMap<Long, String> languageMap, HashMap<Long, String> tagMap) {\n            this.workDir = workDir;\n            this.pid = pid;\n            this.languageMap = languageMap;\n            this.tagMap = tagMap;\n        }\n\n        @Override\n        public Void call() throws Exception {\n            String testcaseWorkDir = filePathProps.getTestcaseBaseFolder() + File.separator + \"problem_\" + pid;\n            File file = new File(testcaseWorkDir);\n\n            List<HashMap<String, Object>> problemCases = new LinkedList<>();\n            // 本地为空 尝试去数据库查找\n            if (!file.exists() || file.listFiles() == null) {\n                QueryWrapper<ProblemCase> problemCaseQueryWrapper = new QueryWrapper<>();\n                problemCaseQueryWrapper.eq(\"pid\", pid);\n                List<ProblemCase> problemCaseList = problemCaseEntityService.list(problemCaseQueryWrapper);\n                FileUtil.mkdir(testcaseWorkDir);\n                // 写入本地\n                for (int i = 0; i < problemCaseList.size(); i++) {\n                    String filePreName = testcaseWorkDir + File.separator + (i + 1);\n                    String inputName = filePreName + \".in\";\n                    String outputName = filePreName + \".out\";\n                    FileWriter infileWriter = new FileWriter(inputName);\n                    infileWriter.write(problemCaseList.get(i).getInput());\n                    FileWriter outfileWriter = new FileWriter(outputName);\n                    outfileWriter.write(problemCaseList.get(i).getOutput());\n\n                    ProblemCase problemCase = problemCaseList.get(i).setPid(null).setInput(inputName)\n                            .setOutput(outputName).setGmtCreate(null).setStatus(null).setId(null)\n                            .setGmtModified(null);\n                    HashMap<String, Object> problemCaseMap = new HashMap<>();\n                    BeanUtil.beanToMap(problemCase, problemCaseMap, false, true);\n                    problemCases.add(problemCaseMap);\n                }\n                FileUtil.copy(testcaseWorkDir, workDir, true);\n\n            } else {\n                String infoPath = testcaseWorkDir + File.separator + \"info\";\n                if (FileUtil.exist(infoPath)) {\n                    FileReader reader = new FileReader(infoPath);\n                    JSONObject jsonObject = JSONUtil.parseObj(reader.readString());\n                    JSONArray testCases = jsonObject.getJSONArray(\"testCases\");\n                    for (int i = 0; i < testCases.size(); i++) {\n                        JSONObject jsonObject1 = testCases.get(i, JSONObject.class);\n                        HashMap<String, Object> problemCaseMap = new HashMap<>();\n                        problemCaseMap.put(\"input\", jsonObject1.getStr(\"inputName\"));\n                        problemCaseMap.put(\"output\", jsonObject1.getStr(\"outputName\"));\n                        Integer score = jsonObject1.getInt(\"score\");\n                        if (score != null && score > 0) {\n                            problemCaseMap.put(\"score\", score);\n                        }\n                        problemCases.add(problemCaseMap);\n                    }\n                }\n                FileUtil.copy(testcaseWorkDir, workDir, true);\n            }\n            ImportProblemVO importProblemVO = problemEntityService.buildExportProblem(pid, problemCases,\n                    languageMap, tagMap);\n            String content = JSONUtil.toJsonStr(importProblemVO);\n            FileWriter fileWriter = new FileWriter(workDir + File.separator + \"problem_\" + pid + \".json\");\n            fileWriter.write(content);\n            return null;\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/TestCaseServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileWriter;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.ZipUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusSystemErrorException;\nimport com.simplefanc.voj.backend.common.utils.MyFileUtil;\nimport com.simplefanc.voj.backend.dao.problem.ProblemCaseEntityService;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.service.file.TestCaseService;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 14:57\n * @Description:\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class TestCaseServiceImpl implements TestCaseService {\n\n    private final ProblemCaseEntityService problemCaseEntityService;\n\n    private final FilePathProperties filePathProps;\n\n    // TODO 行数过多\n    @Override\n    public Map<Object, Object> uploadTestcaseZip(MultipartFile file) {\n        // 获取文件后缀\n        String suffix = MyFileUtil.getFileSuffix(file);\n        if (!\"zip\".toUpperCase().contains(suffix.toUpperCase())) {\n            throw new StatusFailException(\"请上传zip格式的测试数据压缩包！\");\n        }\n        String fileDir = filePathProps.getTestcaseTmpFolder() + File.separator + IdUtil.simpleUUID();\n        String filePath = fileDir + File.separator + file.getOriginalFilename();\n        // 文件夹不存在就新建\n        FileUtil.mkdir(fileDir);\n        try {\n            file.transferTo(new File(filePath));\n        } catch (IOException e) {\n            log.error(\"评测数据文件上传异常-------------->\", e);\n            throw new StatusSystemErrorException(\"服务器异常：评测数据上传失败！\");\n        }\n\n        // 将压缩包解压缩到指定文件夹\n        ZipUtil.unzip(filePath, fileDir);\n        // 删除zip文件\n        FileUtil.del(filePath);\n        // 检查文件是否存在\n        File testCaseFileList = new File(fileDir);\n        File[] files = testCaseFileList.listFiles();\n        if (files == null || files.length == 0) {\n            FileUtil.del(fileDir);\n            throw new StatusFailException(\"评测数据压缩包里文件不能为空！\");\n        }\n\n        HashMap<String, String> inputData = new HashMap<>();\n        HashMap<String, String> outputData = new HashMap<>();\n\n        // 遍历读取与检查是否in和out文件一一对应，否则报错\n        for (File tmp : files) {\n            String tmpPreName;\n            if (tmp.getName().endsWith(\".in\")) {\n                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(\".in\"));\n                inputData.put(tmpPreName, tmp.getName());\n            } else if (tmp.getName().endsWith(\".out\")) {\n                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(\".out\"));\n                outputData.put(tmpPreName, tmp.getName());\n            } else if (tmp.getName().endsWith(\".ans\")) {\n                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(\".ans\"));\n                outputData.put(tmpPreName, tmp.getName());\n            }\n        }\n\n        // 进行数据对应检查,同时生成返回数据\n        List<HashMap<String, String>> problemCaseList = new LinkedList<>();\n        for (String key : inputData.keySet()) {\n            HashMap<String, String> testcaseMap = new HashMap<>();\n            String inputFileName = inputData.get(key);\n            testcaseMap.put(\"input\", inputFileName);\n\n            // 若有名字对应的out文件不存在的，直接生成对应的out文件\n            final String outputFileName = outputData.getOrDefault(key, null);\n            if (outputFileName == null) {\n                FileWriter fileWriter = new FileWriter(fileDir + File.separator + key + \".out\");\n                fileWriter.write(\"\");\n            }\n\n            testcaseMap.put(\"output\", outputFileName);\n            problemCaseList.add(testcaseMap);\n        }\n\n        List<HashMap<String, String>> fileList = problemCaseList.stream().sorted((o1, o2) -> {\n            String a = o1.get(\"input\").split(\"\\\\.\")[0];\n            String b = o2.get(\"input\").split(\"\\\\.\")[0];\n            if (a.length() > b.length()) {\n                return 1;\n            } else if (a.length() < b.length()) {\n                return -1;\n            }\n            return a.compareTo(b);\n        }).collect(Collectors.toList());\n\n        return MapUtil.builder().put(\"fileList\", fileList).put(\"fileListDir\", fileDir).map();\n    }\n\n    @Override\n    public void downloadTestcase(Long pid, HttpServletResponse response) {\n        String workDir = filePathProps.getTestcaseBaseFolder() + File.separator + \"problem_\" + pid;\n        File file = new File(workDir);\n        // 本地为空 尝试去数据库查找\n        if (!file.exists()) {\n            QueryWrapper<ProblemCase> problemCaseQueryWrapper = new QueryWrapper<>();\n            problemCaseQueryWrapper.eq(\"pid\", pid);\n            List<ProblemCase> problemCaseList = problemCaseEntityService.list(problemCaseQueryWrapper);\n\n            if (CollectionUtils.isEmpty(problemCaseList)) {\n                throw new StatusFailException(\"对不起，该题目的评测数据为空！\");\n            }\n\n            boolean hasTestCase = true;\n            if (problemCaseList.get(0).getInput().endsWith(\".in\")\n                    && (problemCaseList.get(0).getOutput().endsWith(\".out\")\n                    || problemCaseList.get(0).getOutput().endsWith(\".ans\"))) {\n                hasTestCase = false;\n            }\n            if (!hasTestCase) {\n                throw new StatusFailException(\"对不起，该题目的评测数据为空！\");\n            }\n\n            // 为手动输入的测试用例\n            FileUtil.mkdir(workDir);\n            // 写入本地\n            for (int i = 0; i < problemCaseList.size(); i++) {\n                String filePreName = workDir + File.separator + (i + 1);\n                String inputName = filePreName + \".in\";\n                String outputName = filePreName + \".out\";\n                FileWriter infileWriter = new FileWriter(inputName);\n                infileWriter.write(problemCaseList.get(i).getInput());\n                FileWriter outfileWriter = new FileWriter(outputName);\n                outfileWriter.write(problemCaseList.get(i).getOutput());\n            }\n        }\n\n        String fileName = \"problem_\" + pid + \"_testcase_\" + System.currentTimeMillis() + \".zip\";\n        // 将对应文件夹的文件压缩成zip\n        final String zipPath = filePathProps.getFileDownloadTmpFolder() + File.separator + fileName;\n        ZipUtil.zip(workDir, zipPath);\n        MyFileUtil.download(response, zipPath, fileName, \"下载题目测试数据的压缩文件失败，请重新尝试！\");\n        // 清空临时文件\n        FileUtil.del(zipPath);\n    }\n\n    @Override\n    public void downloadSingleTestCase(Long pid, String inputData, String outputData, HttpServletResponse response) {\n        String workDir = filePathProps.getTestcaseBaseFolder() + File.separator + \"problem_\" + pid;\n        String fileName = \"problem_\" + pid + \"_testcase_\" + System.currentTimeMillis() + \".zip\";\n        // 将对应文件夹的文件压缩成zip\n        final String zipPath = filePathProps.getFileDownloadTmpFolder() + File.separator + fileName;\n\n        ZipUtil.zip(FileUtil.file(zipPath), false,\n                FileUtil.file(workDir + File.separator + inputData),\n                FileUtil.file(workDir + File.separator + outputData)\n        );\n\n        MyFileUtil.download(response, zipPath, fileName, \"下载题目测试数据的压缩文件失败，请重新尝试！\");\n        // 清空临时文件\n        FileUtil.del(zipPath);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/file/impl/UserFileServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.file.impl;\n\nimport com.alibaba.excel.EasyExcel;\nimport com.simplefanc.voj.backend.common.constants.Constant;\nimport com.simplefanc.voj.backend.common.utils.ExcelUtil;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.pojo.vo.ExcelUserVO;\nimport com.simplefanc.voj.backend.service.file.UserFileService;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 15:02\n * @Description:\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class UserFileServiceImpl implements UserFileService {\n\n    private final RedisUtil redisUtil;\n\n    @Override\n    public void generateUserExcel(String key, HttpServletResponse response) throws IOException {\n        ExcelUtil.wrapExcelResponse(response, key);\n        EasyExcel.write(response.getOutputStream(), ExcelUserVO.class)\n                .sheet(\"用户数据\")\n                .doWrite(getGenerateUsers(key));\n    }\n\n    private List<ExcelUserVO> getGenerateUsers(String key) {\n        return (List<ExcelUserVO>) redisUtil.hget(Constant.GENERATE_USER_INFO_LIST, key);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/msg/AdminNoticeService.java",
    "content": "package com.simplefanc.voj.backend.service.msg;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.AdminSysNoticeVO;\nimport com.simplefanc.voj.common.pojo.entity.msg.AdminSysNotice;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:19\n * @Description:\n */\npublic interface AdminNoticeService {\n\n    IPage<AdminSysNoticeVO> getSysNotice(Integer limit, Integer currentPage, String type);\n\n    void addSysNotice(AdminSysNotice adminSysNotice);\n\n    void deleteSysNotice(Long id);\n\n    void updateSysNotice(AdminSysNotice adminSysNotice);\n\n    void syncNoticeToNewRegisterBatchUser(List<String> uidList);\n\n    void addSingleNoticeToUser(String adminId, String recipientId, String title, String content, String type);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/msg/NoticeService.java",
    "content": "package com.simplefanc.voj.backend.service.msg;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.SysMsgVO;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 11:37\n * @Description:\n */\n\npublic interface NoticeService {\n\n    IPage<SysMsgVO> getSysNotice(Integer limit, Integer currentPage);\n\n    IPage<SysMsgVO> getMineNotice(Integer limit, Integer currentPage);\n\n    void updateSysOrMineMsgRead(IPage<SysMsgVO> userMsgList);\n\n    void syncNoticeToNewRegisterUser(String uid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/msg/UserMessageService.java",
    "content": "package com.simplefanc.voj.backend.service.msg;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.UserMsgVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserUnreadMsgCountVO;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 10:36\n * @Description:\n */\n\npublic interface UserMessageService {\n\n    UserUnreadMsgCountVO getUnreadMsgCount();\n\n    void cleanMsg(String type, Long id);\n\n    IPage<UserMsgVO> getCommentMsg(Integer limit, Integer currentPage);\n\n    IPage<UserMsgVO> getReplyMsg(Integer limit, Integer currentPage);\n\n    IPage<UserMsgVO> getLikeMsg(Integer limit, Integer currentPage);\n\n    void updateUserMsgRead(IPage<UserMsgVO> userMsgList);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/msg/impl/AdminNoticeServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.msg.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.dao.msg.AdminSysNoticeEntityService;\nimport com.simplefanc.voj.backend.dao.msg.UserSysNoticeEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.AdminSysNoticeVO;\nimport com.simplefanc.voj.backend.service.msg.AdminNoticeService;\nimport com.simplefanc.voj.common.pojo.entity.msg.AdminSysNotice;\nimport com.simplefanc.voj.common.pojo.entity.msg.UserSysNotice;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 21:19\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class AdminNoticeServiceImpl implements AdminNoticeService {\n\n    private final AdminSysNoticeEntityService adminSysNoticeEntityService;\n\n    private final UserSysNoticeEntityService userSysNoticeEntityService;\n\n    @Override\n    public IPage<AdminSysNoticeVO> getSysNotice(Integer limit, Integer currentPage, String type) {\n\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 5;\n        }\n\n        return adminSysNoticeEntityService.getSysNotice(limit, currentPage, type);\n    }\n\n    @Override\n    public void addSysNotice(AdminSysNotice adminSysNotice) {\n\n        boolean isOk = adminSysNoticeEntityService.saveOrUpdate(adminSysNotice);\n        if (!isOk) {\n            throw new StatusFailException(\"发布失败\");\n        }\n    }\n\n    @Override\n    public void deleteSysNotice(Long id) {\n\n        boolean isOk = adminSysNoticeEntityService.removeById(id);\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败\");\n        }\n    }\n\n    @Override\n    public void updateSysNotice(AdminSysNotice adminSysNotice) {\n        boolean isOk = adminSysNoticeEntityService.saveOrUpdate(adminSysNotice);\n        if (!isOk) {\n            throw new StatusFailException(\"更新失败\");\n        }\n    }\n\n    @Override\n    @Async\n    public void syncNoticeToNewRegisterBatchUser(List<String> uidList) {\n        QueryWrapper<AdminSysNotice> adminSysNoticeQueryWrapper = new QueryWrapper<>();\n        adminSysNoticeQueryWrapper.eq(\"type\", \"All\").le(\"gmt_create\", new Date()).eq(\"state\", true);\n        List<AdminSysNotice> adminSysNotices = adminSysNoticeEntityService.list(adminSysNoticeQueryWrapper);\n        if (adminSysNotices.size() == 0) {\n            return;\n        }\n        List<UserSysNotice> userSysNoticeList = new ArrayList<>();\n        for (String uid : uidList) {\n            for (AdminSysNotice adminSysNotice : adminSysNotices) {\n                UserSysNotice userSysNotice = new UserSysNotice();\n                userSysNotice.setType(\"Sys\").setSysNoticeId(adminSysNotice.getId()).setRecipientId(uid);\n                userSysNoticeList.add(userSysNotice);\n            }\n        }\n        userSysNoticeEntityService.saveOrUpdateBatch(userSysNoticeList);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    @Async\n    public void addSingleNoticeToUser(String adminId, String recipientId, String title, String content, String type) {\n        AdminSysNotice adminSysNotice = new AdminSysNotice();\n        adminSysNotice.setAdminId(adminId).setType(\"Single\").setTitle(title).setContent(content).setState(true)\n                .setRecipientId(recipientId);\n        boolean isOk = adminSysNoticeEntityService.save(adminSysNotice);\n        if (isOk) {\n            UserSysNotice userSysNotice = new UserSysNotice();\n            userSysNotice.setRecipientId(recipientId).setSysNoticeId(adminSysNotice.getId()).setType(type);\n            userSysNoticeEntityService.save(userSysNotice);\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/msg/impl/NoticeServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.msg.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.dao.msg.AdminSysNoticeEntityService;\nimport com.simplefanc.voj.backend.dao.msg.UserSysNoticeEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.SysMsgVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.msg.NoticeService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.pojo.entity.msg.AdminSysNotice;\nimport com.simplefanc.voj.common.pojo.entity.msg.UserSysNotice;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 11:37\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class NoticeServiceImpl implements NoticeService {\n\n    private final UserSysNoticeEntityService userSysNoticeEntityService;\n\n    private final AdminSysNoticeEntityService adminSysNoticeEntityService;\n\n    private final ApplicationContext applicationContext;\n\n    @Override\n    public IPage<SysMsgVO> getSysNotice(Integer limit, Integer currentPage) {\n\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 5;\n        }\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        IPage<SysMsgVO> sysNotice = userSysNoticeEntityService.getSysNotice(limit, currentPage, userRolesVO.getUid());\n        applicationContext.getBean(NoticeService.class).updateSysOrMineMsgRead(sysNotice);\n        return sysNotice;\n    }\n\n    @Override\n    public IPage<SysMsgVO> getMineNotice(Integer limit, Integer currentPage) {\n\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 5;\n        }\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        IPage<SysMsgVO> mineNotice = userSysNoticeEntityService.getMineNotice(limit, currentPage, userRolesVO.getUid());\n        applicationContext.getBean(NoticeService.class).updateSysOrMineMsgRead(mineNotice);\n        return mineNotice;\n    }\n\n    @Override\n    @Async\n    public void updateSysOrMineMsgRead(IPage<SysMsgVO> userMsgList) {\n        List<Long> idList = userMsgList.getRecords().stream().filter(userMsgVO -> !userMsgVO.getState())\n                .map(SysMsgVO::getId).collect(Collectors.toList());\n        if (idList.size() == 0) {\n            return;\n        }\n        UpdateWrapper<UserSysNotice> updateWrapper = new UpdateWrapper<>();\n        updateWrapper.in(\"id\", idList).set(\"state\", true);\n        userSysNoticeEntityService.update(null, updateWrapper);\n    }\n\n    @Override\n    @Async\n    public void syncNoticeToNewRegisterUser(String uid) {\n        QueryWrapper<AdminSysNotice> adminSysNoticeQueryWrapper = new QueryWrapper<>();\n        adminSysNoticeQueryWrapper.eq(\"type\", \"All\").le(\"gmt_create\", new Date()).eq(\"state\", true);\n        List<AdminSysNotice> adminSysNotices = adminSysNoticeEntityService.list(adminSysNoticeQueryWrapper);\n        if (adminSysNotices.size() == 0) {\n            return;\n        }\n        List<UserSysNotice> userSysNoticeList = new ArrayList<>();\n        for (AdminSysNotice adminSysNotice : adminSysNotices) {\n            UserSysNotice userSysNotice = new UserSysNotice();\n            userSysNotice.setType(\"Sys\").setSysNoticeId(adminSysNotice.getId()).setRecipientId(uid);\n            userSysNoticeList.add(userSysNotice);\n        }\n        userSysNoticeEntityService.saveOrUpdateBatch(userSysNoticeList);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/msg/impl/UserMessageServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.msg.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.CommentEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.ReplyEntityService;\nimport com.simplefanc.voj.backend.dao.msg.MsgRemindEntityService;\nimport com.simplefanc.voj.backend.dao.msg.UserSysNoticeEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.UserMsgVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserUnreadMsgCountVO;\nimport com.simplefanc.voj.backend.service.msg.UserMessageService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Comment;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\nimport com.simplefanc.voj.common.pojo.entity.msg.MsgRemind;\nimport com.simplefanc.voj.common.pojo.entity.msg.UserSysNotice;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 10:36\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class UserMessageServiceImpl implements UserMessageService {\n\n    private final MsgRemindEntityService msgRemindEntityService;\n\n    private final ContestEntityService contestEntityService;\n\n    private final ApplicationContext applicationContext;\n\n    private final DiscussionEntityService discussionEntityService;\n\n    private final CommentEntityService commentEntityService;\n\n    private final ReplyEntityService replyEntityService;\n\n    private final UserSysNoticeEntityService userSysNoticeEntityService;\n\n    @Override\n    public UserUnreadMsgCountVO getUnreadMsgCount() {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        UserUnreadMsgCountVO userUnreadMsgCount = msgRemindEntityService.getUserUnreadMsgCount(userRolesVO.getUid());\n        if (userUnreadMsgCount == null) {\n            userUnreadMsgCount = new UserUnreadMsgCountVO(0, 0, 0, 0, 0);\n        }\n        return userUnreadMsgCount;\n    }\n\n    @Override\n    public void cleanMsg(String type, Long id) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        boolean isOk = cleanMsgByType(type, id, userRolesVO.getUid());\n        if (!isOk) {\n            throw new StatusFailException(\"清空失败\");\n        }\n    }\n\n    @Override\n    public IPage<UserMsgVO> getCommentMsg(Integer limit, Integer currentPage) {\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 5;\n        }\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        return getUserMsgList(userRolesVO.getUid(), \"Discuss\", limit, currentPage);\n    }\n\n    @Override\n    public IPage<UserMsgVO> getReplyMsg(Integer limit, Integer currentPage) {\n\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 5;\n        }\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        return getUserMsgList(userRolesVO.getUid(), \"Reply\", limit, currentPage);\n    }\n\n    @Override\n    public IPage<UserMsgVO> getLikeMsg(Integer limit, Integer currentPage) {\n\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 5;\n        }\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        return getUserMsgList(userRolesVO.getUid(), \"Like\", limit, currentPage);\n    }\n\n    private boolean cleanMsgByType(String type, Long id, String uid) {\n\n        switch (type) {\n            case \"Like\":\n            case \"Discuss\":\n            case \"Reply\":\n                UpdateWrapper<MsgRemind> updateWrapper1 = new UpdateWrapper<>();\n                updateWrapper1.eq(id != null, \"id\", id).eq(\"recipient_id\", uid);\n                return msgRemindEntityService.remove(updateWrapper1);\n            case \"Sys\":\n            case \"Mine\":\n                UpdateWrapper<UserSysNotice> updateWrapper2 = new UpdateWrapper<>();\n                updateWrapper2.eq(id != null, \"id\", id).eq(\"recipient_id\", uid);\n                return userSysNoticeEntityService.remove(updateWrapper2);\n        }\n        return false;\n    }\n\n    private IPage<UserMsgVO> getUserMsgList(String uid, String action, int limit, int currentPage) {\n        Page<UserMsgVO> page = new Page<>(currentPage, limit);\n        IPage<UserMsgVO> userMsgList = msgRemindEntityService.getUserMsg(page, uid, action);\n        if (userMsgList.getTotal() > 0) {\n            switch (action) {\n                // 评论我的\n                case \"Discuss\":\n                    return getUserDiscussMsgList(userMsgList);\n                // 回复我的\n                case \"Reply\":\n                    return getUserReplyMsgList(userMsgList);\n                case \"Like\":\n                    return getUserLikeMsgList(userMsgList);\n                default:\n                    throw new RuntimeException(\"invalid action:\" + action);\n            }\n        } else {\n            return userMsgList;\n        }\n    }\n\n    private IPage<UserMsgVO> getUserDiscussMsgList(IPage<UserMsgVO> userMsgList) {\n\n        List<Integer> discussionIds = userMsgList.getRecords().stream().map(UserMsgVO::getSourceId)\n                .collect(Collectors.toList());\n        Collection<Discussion> discussions = discussionEntityService.listByIds(discussionIds);\n        for (Discussion discussion : discussions) {\n            for (UserMsgVO userMsgVO : userMsgList.getRecords()) {\n                if (Objects.equals(discussion.getId(), userMsgVO.getSourceId())) {\n                    userMsgVO.setSourceTitle(discussion.getTitle());\n                    break;\n                }\n            }\n        }\n        applicationContext.getBean(UserMessageService.class).updateUserMsgRead(userMsgList);\n        return userMsgList;\n    }\n\n    private IPage<UserMsgVO> getUserReplyMsgList(IPage<UserMsgVO> userMsgList) {\n\n        for (UserMsgVO userMsgVO : userMsgList.getRecords()) {\n            if (\"Discussion\".equals(userMsgVO.getSourceType())) {\n                Discussion discussion = discussionEntityService.getById(userMsgVO.getSourceId());\n                if (discussion != null) {\n                    userMsgVO.setSourceTitle(discussion.getTitle());\n                } else {\n                    userMsgVO.setSourceTitle(\"原讨论帖已被删除!【The original discussion post has been deleted!】\");\n                }\n            } else if (\"Contest\".equals(userMsgVO.getSourceType())) {\n                Contest contest = contestEntityService.getById(userMsgVO.getSourceId());\n                if (contest != null) {\n                    userMsgVO.setSourceTitle(contest.getTitle());\n                } else {\n                    userMsgVO.setSourceTitle(\"原比赛已被删除!【The original contest has been deleted!】\");\n                }\n            }\n\n            if (\"Comment\".equals(userMsgVO.getQuoteType())) {\n                Comment comment = commentEntityService.getById(userMsgVO.getQuoteId());\n                if (comment != null) {\n                    String content;\n                    if (comment.getContent().length() < 100) {\n                        content = comment.getFromName() + \" : \" + comment.getContent();\n\n                    } else {\n                        content = comment.getFromName() + \" : \" + comment.getContent().substring(0, 100) + \"...\";\n                    }\n                    userMsgVO.setQuoteContent(content);\n                } else {\n                    userMsgVO.setQuoteContent(\"您的原评论信息已被删除！【Your original comments have been deleted!】\");\n                }\n\n            } else if (\"Reply\".equals(userMsgVO.getQuoteType())) {\n                Reply reply = replyEntityService.getById(userMsgVO.getQuoteId());\n                if (reply != null) {\n                    String content;\n                    if (reply.getContent().length() < 100) {\n                        content = reply.getFromName() + \" : @\" + reply.getToName() + \"：\" + reply.getContent();\n\n                    } else {\n                        content = reply.getFromName() + \" : @\" + reply.getToName() + \"：\"\n                                + reply.getContent().substring(0, 100) + \"...\";\n                    }\n                    userMsgVO.setQuoteContent(content);\n                } else {\n                    userMsgVO.setQuoteContent(\"您的原回复信息已被删除！【Your original reply has been deleted!】\");\n                }\n            }\n\n        }\n\n        applicationContext.getBean(UserMessageService.class).updateUserMsgRead(userMsgList);\n        return userMsgList;\n    }\n\n    private IPage<UserMsgVO> getUserLikeMsgList(IPage<UserMsgVO> userMsgList) {\n        for (UserMsgVO userMsgVO : userMsgList.getRecords()) {\n            if (\"Discussion\".equals(userMsgVO.getSourceType())) {\n                Discussion discussion = discussionEntityService.getById(userMsgVO.getSourceId());\n                if (discussion != null) {\n                    userMsgVO.setSourceTitle(discussion.getTitle());\n                } else {\n                    userMsgVO.setSourceTitle(\"原讨论帖已被删除!【The original discussion post has been deleted!】\");\n                }\n            } else if (\"Contest\".equals(userMsgVO.getSourceType())) {\n                Contest contest = contestEntityService.getById(userMsgVO.getSourceId());\n                if (contest != null) {\n                    userMsgVO.setSourceTitle(contest.getTitle());\n                } else {\n                    userMsgVO.setSourceTitle(\"原比赛已被删除!【The original contest has been deleted!】\");\n                }\n            }\n        }\n        applicationContext.getBean(UserMessageService.class).updateUserMsgRead(userMsgList);\n        return userMsgList;\n    }\n\n    @Override\n    @Async\n    public void updateUserMsgRead(IPage<UserMsgVO> userMsgList) {\n        List<Long> idList = userMsgList.getRecords().stream().filter(userMsgVO -> !userMsgVO.getState())\n                .map(UserMsgVO::getId).collect(Collectors.toList());\n        if (idList.size() == 0) {\n            return;\n        }\n        UpdateWrapper<MsgRemind> updateWrapper = new UpdateWrapper<>();\n        updateWrapper.in(\"id\", idList).set(\"state\", true);\n        msgRemindEntityService.update(null, updateWrapper);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/AccountService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.simplefanc.voj.backend.pojo.dto.ChangeEmailDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ChangePasswordDTO;\nimport com.simplefanc.voj.backend.pojo.dto.CheckUsernameOrEmailDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ChangeAccountVO;\nimport com.simplefanc.voj.backend.pojo.vo.CheckUsernameOrEmailVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserHomeVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserInfoVO;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 16:53\n * @Description:\n */\npublic interface AccountService {\n\n    /**\n     * @MethodName checkUsernameOrEmail\n     * @Params * @param null\n     * @Description 检验用户名和邮箱是否存在\n     * @Return\n     * @Since 2021/11/5\n     */\n    CheckUsernameOrEmailVO checkUsernameOrEmail(CheckUsernameOrEmailDTO checkUsernameOrEmailDTO);\n\n    /**\n     * @param uid\n     * @MethodName getUserHomeInfo\n     * @Description 前端userHome用户个人主页的数据请求，主要是返回解决题目数，AC的题目列表，提交总数，AC总数，Rating分，\n     * @Return CommonResult\n     * @Since 2021/01/07\n     */\n    UserHomeVO getUserHomeInfo(String uid, String username);\n\n    /**\n     * @MethodName changePassword\n     * @Description 修改密码的操作，连续半小时内修改密码错误5次，则需要半个小时后才可以再次尝试修改密码\n     * @Return\n     * @Since 2021/1/8\n     */\n    ChangeAccountVO changePassword(ChangePasswordDTO changePasswordDTO);\n\n    /**\n     * @MethodName changeEmail\n     * @Description 修改邮箱的操作，连续半小时内密码错误5次，则需要半个小时后才可以再次尝试修改\n     * @Return\n     * @Since 2021/1/9\n     */\n    ChangeAccountVO changeEmail(ChangeEmailDTO changeEmailDTO);\n\n    UserInfoVO changeUserInfo(UserInfoVO userInfoVO);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/BeforeDispatchInitService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport cn.hutool.core.date.DateUnit;\nimport cn.hutool.core.date.DateUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.constants.TrainingEnum;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.exception.StatusNotFoundException;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestProblemEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestRecordEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingProblemEntityService;\nimport com.simplefanc.voj.backend.dao.training.TrainingRecordEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.backend.validator.TrainingValidator;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.ProblemEnum;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingProblem;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRecord;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\nimport org.springframework.transaction.annotation.Transactional;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 14:29\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class BeforeDispatchInitService {\n\n    private final ContestEntityService contestEntityService;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    private final ContestProblemEntityService contestProblemEntityService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final TrainingEntityService trainingEntityService;\n\n    private final TrainingProblemEntityService trainingProblemEntityService;\n\n    private final TrainingRecordEntityService trainingRecordEntityService;\n\n    private final TrainingValidator trainingValidator;\n\n    private final ContestValidator contestValidator;\n\n    public void initCommonSubmission(String problemId, Judge judge) {\n\n        QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();\n        problemQueryWrapper.eq(\"problem_id\", problemId);\n        Problem problem = problemEntityService.getOne(problemQueryWrapper, false);\n\n        if (problem.getAuth().equals(ProblemEnum.AUTH_PRIVATE.getCode())) {\n            throw new StatusForbiddenException(\"错误！当前题目不可提交！\");\n        }\n\n        judge.setCpid(0L).setPid(problem.getId()).setDisplayPid(problem.getProblemId());\n\n        // 将新提交数据插入数据库\n        judgeEntityService.save(judge);\n    }\n\n    @Transactional(rollbackFor = Exception.class)\n    public void initContestSubmission(Long cid, String displayId, Judge judge) {\n        // 首先判断一下比赛的状态是否是正在进行，结束状态都不能提交，比赛前比赛管理员可以提交\n        Contest contest = contestEntityService.getById(cid);\n\n        if (contest == null) {\n            throw new StatusNotFoundException(\"对不起，该比赛不存在！\");\n        }\n\n        if (contest.getStatus().intValue() == ContestEnum.STATUS_ENDED.getCode()) {\n            throw new StatusForbiddenException(\"比赛已结束，不可再提交！\");\n        }\n\n        final UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        // 是否为比赛管理者\n        if (!contestValidator.isContestAdmin(contest)) {\n            if (contest.getStatus().intValue() == ContestEnum.STATUS_SCHEDULED.getCode()) {\n                throw new StatusForbiddenException(\"比赛未开始，不可提交！\");\n            }\n            // 需要检查是否有权限在当前比赛进行提交\n            contestValidator.validateJudgeAuth(contest);\n\n            // 需要校验当前比赛是否为保护比赛，同时是否开启账号规则限制，如果有，需要对当前用户的用户名进行验证\n            if (contest.getAuth().equals(ContestEnum.AUTH_PROTECT.getCode()) && contest.getOpenAccountLimit()\n                    && !contestValidator.validateAccountRule(contest.getAccountLimitRule(), userRolesVO.getUsername())) {\n                throw new StatusForbiddenException(\"对不起！本次比赛只允许特定账号规则的用户参赛！\");\n            }\n        }\n\n        // 查询获取对应的pid和cpid\n        QueryWrapper<ContestProblem> contestProblemQueryWrapper = new QueryWrapper<>();\n        contestProblemQueryWrapper.eq(\"cid\", cid).eq(\"display_id\", displayId);\n        ContestProblem contestProblem = contestProblemEntityService.getOne(contestProblemQueryWrapper, false);\n        judge.setCpid(contestProblem.getId()).setPid(contestProblem.getPid());\n\n        Problem problem = problemEntityService.getById(contestProblem.getPid());\n        if (problem.getAuth().equals(ProblemEnum.AUTH_PRIVATE.getCode())) {\n            throw new StatusForbiddenException(\"错误！当前题目不可提交！\");\n        }\n        judge.setDisplayPid(problem.getProblemId());\n\n        // 将新提交数据插入数据库\n        judgeEntityService.save(judge);\n\n        // 同时初始化写入contest_record表\n        ContestRecord contestRecord = new ContestRecord();\n        contestRecord.setDisplayId(displayId).setCpid(contestProblem.getId()).setSubmitId(judge.getSubmitId())\n                .setPid(judge.getPid()).setUsername(userRolesVO.getUsername()).setRealname(userRolesVO.getRealname())\n                .setUid(userRolesVO.getUid()).setCid(judge.getCid()).setSubmitTime(judge.getSubmitTime());\n\n        if (contest.getStatus().intValue() == ContestEnum.STATUS_SCHEDULED.getCode()) {\n            contestRecord.setTime(0L);\n        } else {\n            // 设置比赛开始时间到提交时间之间的秒数\n            contestRecord.setTime(DateUtil.between(contest.getStartTime(), judge.getSubmitTime(), DateUnit.SECOND));\n        }\n        contestRecordEntityService.save(contestRecord);\n    }\n\n    @Transactional(rollbackFor = Exception.class)\n    public void initTrainingSubmission(Long tid, String displayId, Judge judge) {\n\n        Training training = trainingEntityService.getById(tid);\n        if (training == null || !training.getStatus()) {\n            throw new StatusFailException(\"该训练不存在或不允许显示！\");\n        }\n\n        trainingValidator.validateTrainingAuth(training);\n\n        // 查询获取对应的pid和cpid\n        QueryWrapper<TrainingProblem> trainingProblemQueryWrapper = new QueryWrapper<>();\n        trainingProblemQueryWrapper.eq(\"tid\", tid).eq(\"display_id\", displayId);\n        TrainingProblem trainingProblem = trainingProblemEntityService.getOne(trainingProblemQueryWrapper);\n        judge.setPid(trainingProblem.getPid());\n\n        Problem problem = problemEntityService.getById(trainingProblem.getPid());\n        if (problem.getAuth().equals(ProblemEnum.AUTH_PRIVATE.getCode())) {\n            throw new StatusForbiddenException(\"错误！当前题目不可提交！\");\n        }\n        judge.setDisplayPid(problem.getProblemId());\n\n        // 将新提交数据插入数据库\n        judgeEntityService.save(judge);\n\n        // 非私有训练不记录\n        if (!training.getAuth().equals(TrainingEnum.AUTH_PRIVATE.getValue())) {\n            return;\n        }\n\n        TrainingRecord trainingRecord = new TrainingRecord();\n        trainingRecord.setPid(problem.getId()).setTid(tid).setTpid(trainingProblem.getId())\n                .setSubmitId(judge.getSubmitId()).setUid(UserSessionUtil.getUserInfo().getUid());\n        trainingRecordEntityService.save(trainingRecord);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/CommentService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.simplefanc.voj.backend.pojo.dto.ReplyDTO;\nimport com.simplefanc.voj.backend.pojo.vo.CommentListVO;\nimport com.simplefanc.voj.backend.pojo.vo.CommentVO;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Comment;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 15:59\n * @Description:\n */\n\npublic interface CommentService {\n\n    CommentListVO getComments(Long cid, Integer did, Integer limit, Integer currentPage);\n\n    CommentVO addComment(Comment comment);\n\n    void deleteComment(Comment comment);\n\n    void addDiscussionLike(Integer cid, Boolean toLike, Integer sourceId, String sourceType);\n\n    List<Reply> getAllReply(Integer commentId, Long cid);\n\n    void addReply(ReplyDTO replyDTO);\n\n    void deleteReply(ReplyDTO replyDTO);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/CommonService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.simplefanc.voj.backend.pojo.vo.CaptchaVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemTagVO;\nimport com.simplefanc.voj.common.pojo.entity.problem.CodeTemplate;\nimport com.simplefanc.voj.common.pojo.entity.problem.Language;\nimport com.simplefanc.voj.common.pojo.entity.problem.Tag;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 16:28\n * @Description:\n */\n\npublic interface CommonService {\n\n    CaptchaVO getCaptcha();\n\n    List<TrainingCategory> getTrainingCategory();\n\n    List<Tag> getAllProblemTagsList(String oj);\n\n    List<ProblemTagVO> getProblemTagsAndClassification(String oj);\n\n    Collection<Tag> getProblemTags(Long pid);\n\n    List<Language> getLanguages(Long pid, Boolean all);\n\n    Collection<Language> getProblemLanguages(Long pid);\n\n    List<CodeTemplate> getProblemCodeTemplate(Long pid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/ContestACMRankService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.dao.contest.ContestRecordEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestRegisterEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.ACMContestRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.ContestRecordVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.user.UserInfo;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 20:11\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class ContestACMRankService {\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    private final ContestRegisterEntityService contestRegisterEntityService;\n\n    private final ContestValidator contestValidator;\n\n    /**\n     * @param isOpenSealRank\n     * @param removeStar\n     * @param concernedList\n     * @param contest\n     * @param currentPage\n     * @param limit\n     * @desc 获取ACM比赛排行榜，有分页\n     */\n    public IPage<ACMContestRankVO> getContestACMRankPage(Contest contest, Boolean isOpenSealRank, Boolean removeStar,\n                                                         List<String> concernedList, String keyword,\n                                                         Boolean useCache, Long cacheTime,\n                                                         int currentPage, int limit) {\n        List<ACMContestRankVO> orderResultList = this.calculateACMRank(isOpenSealRank, removeStar, contest,\n                concernedList, keyword, useCache, cacheTime);\n\n        return getACMContestRankVOPage(orderResultList, currentPage, limit);\n    }\n\n    private Page<ACMContestRankVO> getACMContestRankVOPage(List<ACMContestRankVO> orderResultList, int currentPage, int limit) {\n        // 计算好排行榜，然后进行分页\n        Page<ACMContestRankVO> page = new Page<>(currentPage, limit);\n        int count = orderResultList.size();\n        List<ACMContestRankVO> pageList = new ArrayList<>();\n        // 计算当前页第一条数据的下标\n        int currId = currentPage > 1 ? (currentPage - 1) * limit : 0;\n        for (int i = 0; i < limit && i < count - currId; i++) {\n            pageList.add(orderResultList.get(currId + i));\n        }\n        page.setSize(limit);\n        page.setCurrent(currentPage);\n        page.setTotal(count);\n        page.setRecords(pageList);\n\n        return page;\n    }\n\n    /**\n     * @param isOpenSealRank 是否是查询封榜后的数据\n     * @param removeStar     是否需要移除打星队伍\n     * @param contest        比赛实体信息\n     * @param concernedList  关注的用户（uuid）列表\n     * @param useCache       是否对初始排序计算的结果进行缓存\n     * @param cacheTime      缓存的时间 单位秒\n     * @MethodName calcACMRank\n     * @Description\n     * @Return\n     * @Since 2021/12/10\n     */\n    public List<ACMContestRankVO> calculateACMRank(boolean isOpenSealRank, boolean removeStar, Contest contest,\n                                                   List<String> concernedList, String keyword, boolean useCache, Long cacheTime) {\n        List<ACMContestRankVO> orderResultList = getACMOrderRank(contest, isOpenSealRank, useCache);\n//        if (useCache) {\n//            String key = ContestConstant.CONTEST_RANK_CAL_RESULT_CACHE + \"_\" + contest.getId();\n//            orderResultList = (List<ACMContestRankVO>) redisUtil.get(key);\n//            if (orderResultList == null) {\n//                orderResultList = getACMOrderRank(contest, isOpenSealRank, useCache);\n//                redisUtil.set(key, orderResultList, cacheTime);\n//            }\n//        } else {\n//            orderResultList = getACMOrderRank(contest, isOpenSealRank, useCache);\n//        }\n        // 记录当前用户排名数据和关注列表的用户排名数据\n        List<ACMContestRankVO> topACMRankVOList = new ArrayList<>();\n        computeACMRankNo(removeStar, contest, concernedList, orderResultList, topACMRankVOList);\n        topACMRankVOList.addAll(orderResultList);\n        if(StrUtil.isNotEmpty(keyword)) {\n            return topACMRankVOList.stream()\n                    .filter(rankVO -> filterKeyword(rankVO, keyword))\n                    .collect(Collectors.toList());\n        }\n        return topACMRankVOList;\n    }\n\n    boolean filterKeyword(ACMContestRankVO rankVO, String keyword){\n        final String realname = rankVO.getRealname();\n        if(StrUtil.isNotEmpty(realname)){\n            if(realname.contains(keyword)) {\n                return true;\n            }\n        }\n        final String username = rankVO.getUsername();\n        if(StrUtil.isNotEmpty(username)){\n            if(username.contains(keyword)) {\n                return true;\n            }\n        }\n        final String school = rankVO.getSchool();\n        if(StrUtil.isNotEmpty(school)){\n            if(school.contains(keyword)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * ACM机制的比赛排名规则：先按AC的题目数量排名，若AC的题目数量一样，则按罚时排名。\n     * @param contest\n     * @param isOpenSealRank\n     * @return\n     */\n    @Cacheable(value = RedisConstant.CONTEST_RANK_CAL_RESULT_CACHE, key = \"#contest.id\", condition=\"#useCache\")\n    public List<ACMContestRankVO> getACMOrderRank(Contest contest, Boolean isOpenSealRank, boolean useCache) {\n        List<String> superAdminUidList = userInfoEntityService.getSuperAdminUidList();\n        List<String> contestAdminUidList = new ArrayList<>(superAdminUidList);\n        contestAdminUidList.add(contest.getUid());\n\n        List<ContestRecordVO> contestRecordList = contestRecordEntityService.getACMContestRecord(contest.getId(), contest.getStartTime())\n                .stream()\n                .filter(contestRecord ->\n                        contest.getContestAdminRank() || !contestAdminUidList.contains(contestRecord.getUid())\n                )\n                .collect(Collectors.toList());\n        Set<String> hasRecordUserNameSet = contestRecordList.stream()\n                .map(ContestRecordVO::getUsername)\n                .collect(Collectors.toSet());\n        Map<String, ACMContestRankVO> uidContestRankVOMap = initACMContestRankVO(hasRecordUserNameSet);\n        List<ACMContestRankVO> result = new ArrayList<>(uidContestRankVOMap.values());\n\n        HashMap<String, Long> firstAcMap = new HashMap<>();\n        for (ContestRecordVO contestRecord : contestRecordList) {\n            ACMContestRankVO acmContestRankVO = uidContestRankVOMap.get(contestRecord.getUid());\n            HashMap<String, Object> problemSubmissionInfo = acmContestRankVO.getSubmissionInfo()\n                    .get(contestRecord.getDisplayId());\n\n            if (problemSubmissionInfo == null) {\n                problemSubmissionInfo = new HashMap<>();\n                problemSubmissionInfo.put(\"errorNum\", 0);\n            }\n\n            acmContestRankVO.setTotal(acmContestRankVO.getTotal() + 1);\n\n            // 如果是当前是开启封榜的时段和同时该提交是处于封榜时段 尝试次数+1\n            if (isOpenSealRank && isInSealTimeSubmission(contest, contestRecord.getSubmitTime())) {\n                int tryNum = (int) problemSubmissionInfo.getOrDefault(\"tryNum\", 0);\n                problemSubmissionInfo.put(\"tryNum\", tryNum + 1);\n            } else {\n                // 如果该题目已经AC过了，其它都不记录了\n                if ((Boolean) problemSubmissionInfo.getOrDefault(\"isAC\", false)) {\n                    continue;\n                }\n                processContestRecordVO(contestRecord, firstAcMap, acmContestRankVO, problemSubmissionInfo);\n            }\n            acmContestRankVO.getSubmissionInfo().put(contestRecord.getDisplayId(), problemSubmissionInfo);\n        }\n\n        // 先以总ac数降序，再以总耗时升序\n        result = result.stream()\n                .sorted(Comparator.comparing(ACMContestRankVO::getAc, Comparator.reverseOrder())\n                        .thenComparing(ACMContestRankVO::getTotalTime))\n                .collect(Collectors.toList());\n\n        if (contestValidator.isContestAdmin(contest)) {\n            result.addAll(getNoRecordUserACMContestRankVOs(contest, hasRecordUserNameSet));\n        }\n        return result;\n    }\n\n    private void processContestRecordVO(ContestRecordVO contestRecord, HashMap<String, Long> firstAcMap, ACMContestRankVO acmContestRankVO, HashMap<String, Object> problemSubmissionInfo) {\n        int errorNumber = (int) problemSubmissionInfo.getOrDefault(\"errorNum\", 0);\n        // 记录已经按题目提交耗时time升序了\n        // 通过的话\n        if (contestRecord.getStatus().intValue() == ContestEnum.RECORD_AC.getCode()) {\n            // 总解决题目次数ac+1\n            acmContestRankVO.setAc(acmContestRankVO.getAc() + 1);\n\n            // 判断是不是first AC\n            boolean isFirstAc = false;\n            Long time = firstAcMap.getOrDefault(contestRecord.getDisplayId(), null);\n            if (time == null) {\n                isFirstAc = true;\n                firstAcMap.put(contestRecord.getDisplayId(), contestRecord.getTime());\n            } else {\n                // 相同提交时间也是first AC\n                if (time.longValue() == contestRecord.getTime().longValue()) {\n                    isFirstAc = true;\n                }\n            }\n\n            problemSubmissionInfo.put(\"isAC\", true);\n            problemSubmissionInfo.put(\"isFirstAC\", isFirstAc);\n            problemSubmissionInfo.put(\"ACTime\", contestRecord.getTime());\n            problemSubmissionInfo.put(\"errorNum\", errorNumber);\n\n            // 所谓“罚时”指的是做出题目所用的总时间，加上提交错误所付出的代价，每提交错误一次，会罚时20分钟。\n            // 同时计算总耗时，总耗时加上 题目AC耗时+该题目未AC前的错误次数*20*60\n            acmContestRankVO.setTotalTime(\n                    acmContestRankVO.getTotalTime() + errorNumber * 20 * 60 + contestRecord.getTime());\n        } else if (contestRecord.getStatus().intValue() == ContestEnum.RECORD_NOT_AC_PENALTY.getCode()) {\n            // 未通过同时需要记录罚时次数\n            problemSubmissionInfo.put(\"errorNum\", errorNumber + 1);\n        } else {\n            problemSubmissionInfo.put(\"errorNum\", errorNumber);\n        }\n    }\n\n    private void computeACMRankNo(boolean removeStar, Contest contest, List<String> concernedList, List<ACMContestRankVO> orderResultList, List<ACMContestRankVO> topACMRankVOList) {\n        // 需要打星的用户名列表\n        Map<String, Boolean> starAccountMap = starAccountToMap(contest.getStarAccount());\n\n        // 如果选择了移除打星队伍，同时该用户属于打星队伍，则将其移除\n        if (removeStar) {\n            orderResultList.removeIf(acmContestRankVO -> starAccountMap.containsKey(acmContestRankVO.getUsername()));\n        }\n        String currentUserId = null;\n        final UserRolesVO userInfo = UserSessionUtil.getUserInfo();\n        // 外榜：可能未登录\n        if(userInfo != null) {\n            currentUserId = userInfo.getUid();\n        }\n        boolean needAddConcernedUser = false;\n        if (!CollectionUtils.isEmpty(concernedList)) {\n            needAddConcernedUser = true;\n            // 移除关注列表与当前用户重复\n            concernedList.remove(currentUserId);\n        }\n\n        int rankNum = 1;\n        ACMContestRankVO preACMRankVO = null;\n        for (int i = 0; i < orderResultList.size(); i++) {\n            ACMContestRankVO currentACMRankVO = orderResultList.get(i);\n            currentACMRankVO.setSeq(i + 1);\n            if (starAccountMap.containsKey(currentACMRankVO.getUsername())) {\n                // 打星队伍排名为-1\n                currentACMRankVO.setRank(-1);\n            } else {\n                if (rankNum == 1) {\n                    currentACMRankVO.setRank(rankNum);\n                } else {\n                    // 当前用户的总罚时和AC数跟前一个用户一样的话，同时前一个不应该为打星，排名则一样\n                    if (preACMRankVO.getAc().equals(currentACMRankVO.getAc())\n                            && preACMRankVO.getTotalTime().equals(currentACMRankVO.getTotalTime())) {\n                        currentACMRankVO.setRank(preACMRankVO.getRank());\n                    } else {\n                        currentACMRankVO.setRank(rankNum);\n                    }\n                }\n                preACMRankVO = currentACMRankVO;\n                rankNum++;\n            }\n\n            if (StrUtil.isNotEmpty(currentUserId) && currentUserId.equals(currentACMRankVO.getUid())) {\n                topACMRankVOList.add(currentACMRankVO);\n            }\n\n            // 需要添加关注用户\n            if (needAddConcernedUser) {\n                if (concernedList.contains(currentACMRankVO.getUid())) {\n                    topACMRankVOList.add(currentACMRankVO);\n                }\n            }\n        }\n    }\n\n    private List<UserInfo> getNoRecordUserInfos(Contest contest, Set<String> hasRecordUserNameSet) {\n        String extra = ReUtil.getGroup1(\"<extra>([\\\\S\\\\s]*?)<\\\\/extra>\", contest.getAccountLimitRule());\n        if (StrUtil.isBlank(extra)) {\n            return new ArrayList<>();\n        }\n        final Set<String> extraUserNameSet = Arrays.stream(extra.split(\"\\n\"))\n                .filter(u -> !hasRecordUserNameSet.contains(u))\n                .collect(Collectors.toSet());\n        if (CollUtil.isEmpty(extraUserNameSet)) {\n            return new ArrayList<>();\n        }\n        return userInfoEntityService.lambdaQuery()\n                .in(UserInfo::getUsername, extraUserNameSet)\n                .list();\n    }\n\n    private List<ACMContestRankVO> getNoRecordUserACMContestRankVOs(Contest contest, Set<String> userNameSet) {\n        final Set<String> registeredUsers = contestRegisterEntityService.getRegisteredUsers(contest.getId());\n        final List<UserInfo> noRecordUserInfos = getNoRecordUserInfos(contest, userNameSet);\n        return noRecordUserInfos.stream()\n                .map(this::initACMContestRankVO)\n                .map(contestRankVO -> contestRankVO.setRegistered(registeredUsers.contains(contestRankVO.getUid())))\n                .sorted(Comparator.comparing(ACMContestRankVO::getRegistered, Comparator.reverseOrder()))\n                .collect(Collectors.toList());\n    }\n\n    private Map<String, ACMContestRankVO> initACMContestRankVO(Set<String> userNameSet) {\n        if (CollUtil.isEmpty(userNameSet)) {\n            return new HashMap<>();\n        }\n\n        return userInfoEntityService.lambdaQuery()\n                .in(UserInfo::getUsername, userNameSet)\n                .list()\n                .stream()\n                .map(this::initACMContestRankVO)\n                .collect(Collectors.toMap(ACMContestRankVO::getUid, o -> o));\n    }\n\n    private ACMContestRankVO initACMContestRankVO(UserInfo userInfo) {\n        return new ACMContestRankVO()\n                .setRealname(userInfo.getRealname())\n                .setUid(userInfo.getUuid())\n                .setUsername(userInfo.getUsername())\n                .setSchool(userInfo.getSchool())\n                .setAvatar(userInfo.getAvatar())\n                .setGender(userInfo.getGender())\n                .setNickname(userInfo.getNickname())\n                .setAc(0)\n                .setTotalTime(0L)\n                .setTotal(0)\n                .setSubmissionInfo(new HashMap<>());\n    }\n\n    private boolean isInSealTimeSubmission(Contest contest, Date submissionDate) {\n        return DateUtil.isIn(submissionDate, contest.getSealRankTime(), contest.getEndTime());\n    }\n\n    private Map<String, Boolean> starAccountToMap(String starAccountStr) {\n        if (StrUtil.isEmpty(starAccountStr)) {\n            return new HashMap<>();\n        }\n        JSONObject jsonObject = JSONUtil.parseObj(starAccountStr);\n        List<String> accountList = jsonObject.get(\"star_account\", List.class);\n        return Optional.ofNullable(accountList)\n                .orElse(Collections.emptyList())\n                .stream()\n                .filter(StrUtil::isNotEmpty)\n                .collect(Collectors.toMap(str -> str, str -> true));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/ContestAdminService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.CheckAcDTO;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestPrint;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 19:40\n * @Description:\n */\n\npublic interface ContestAdminService {\n\n    IPage<ContestRecord> getContestACInfo(Long cid, Integer currentPage, Integer limit);\n\n    void checkContestAcInfo(CheckAcDTO checkAcDTO);\n\n    IPage<ContestPrint> getContestPrint(Long cid, Integer currentPage, Integer limit);\n\n    void checkContestPrintStatus(Long id, Long cid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/ContestOIRankService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.dao.contest.ContestRecordEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestRegisterEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.ContestRecordVO;\nimport com.simplefanc.voj.backend.pojo.vo.OIContestRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.common.constants.ContestConstant;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.user.UserInfo;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 20:11\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class ContestOIRankService {\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    private final ContestRegisterEntityService contestRegisterEntityService;\n\n    private final ContestValidator contestValidator;\n\n    /**\n     * @param isOpenSealRank\n     * @param removeStarUser\n     * @param concernedList\n     * @param contest\n     * @param currentPage\n     * @param limit\n     * @desc 获取OI比赛排行榜，有分页\n     */\n    public IPage<OIContestRankVO> getContestOIRankPage(Contest contest, Boolean isOpenSealRank, Boolean removeStarUser,\n                                                       List<String> concernedList, String keyword,\n                                                       Boolean useCache, Long cacheTime,\n                                                       int currentPage, int limit) {\n\n        List<OIContestRankVO> orderResultList = this.calculateOIRank(isOpenSealRank, removeStarUser,\n                contest, concernedList, keyword, useCache, cacheTime);\n\n        return getOiContestRankVOPage(currentPage, limit, orderResultList);\n    }\n\n    private Page<OIContestRankVO> getOiContestRankVOPage(int currentPage, int limit, List<OIContestRankVO> orderResultList) {\n        // 计算好排行榜，然后进行分页\n        Page<OIContestRankVO> page = new Page<>(currentPage, limit);\n        int count = orderResultList.size();\n        List<OIContestRankVO> pageList = new ArrayList<>();\n        // 计算当前页第一条数据的下标\n        int currId = currentPage > 1 ? (currentPage - 1) * limit : 0;\n        for (int i = 0; i < limit && i < count - currId; i++) {\n            pageList.add(orderResultList.get(currId + i));\n        }\n        page.setSize(limit);\n        page.setCurrent(currentPage);\n        page.setTotal(count);\n        page.setRecords(pageList);\n        return page;\n    }\n\n    /**\n     * @param isOpenSealRank 是否是查询封榜后的数据\n     * @param removeStar     是否需要移除打星队伍\n     * @param contest        比赛实体信息\n     * @param concernedList  关注的用户（uuid）列表\n     * @param useCache       是否对初始排序计算的结果进行缓存\n     * @param cacheTime      缓存的时间 单位秒\n     * @MethodName calcOIRank\n     * @Description\n     * @Return\n     * @Since 2021/12/10\n     */\n    public List<OIContestRankVO> calculateOIRank(boolean isOpenSealRank, boolean removeStar, Contest contest,\n                                                 List<String> concernedList, String keyword, boolean useCache, Long cacheTime) {\n        List<OIContestRankVO> orderResultList = getOiOrderRank(contest, isOpenSealRank, useCache);\n//        if (useCache) {\n//            String key = ContestConstant.CONTEST_RANK_CAL_RESULT_CACHE + \"_\" + contest.getId();\n//            orderResultList = (List<OIContestRankVO>) redisUtil.get(key);\n//            if (orderResultList == null) {\n//                orderResultList = getOiOrderRank(contest, isOpenSealRank);\n//                redisUtil.set(key, orderResultList, cacheTime);\n//            }\n//        } else {\n//            orderResultList = getOiOrderRank(contest, isOpenSealRank);\n//        }\n        // 记录当前用户排名数据和关注列表的用户排名数据\n        List<OIContestRankVO> topOIRankVOList = new ArrayList<>();\n        // 设置 rank 和 添加置顶\n        computeOIRankNo(contest, concernedList, removeStar, orderResultList, topOIRankVOList);\n        topOIRankVOList.addAll(orderResultList);\n        if(StrUtil.isNotEmpty(keyword)) {\n            return topOIRankVOList.stream()\n                    .filter(rankVO -> filterKeyword(rankVO, keyword))\n                    .collect(Collectors.toList());\n        }\n        return topOIRankVOList;\n    }\n\n    boolean filterKeyword(OIContestRankVO rankVO, String keyword){\n        final String realname = rankVO.getRealname();\n        if(StrUtil.isNotEmpty(realname)){\n            if(realname.contains(keyword)) {\n                return true;\n            }\n        }\n        final String username = rankVO.getUsername();\n        if(StrUtil.isNotEmpty(username)){\n            if(username.contains(keyword)) {\n                return true;\n            }\n        }\n        final String school = rankVO.getSchool();\n        if(StrUtil.isNotEmpty(school)){\n            if(school.contains(keyword)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Cacheable(value = RedisConstant.CONTEST_RANK_CAL_RESULT_CACHE, key = \"#contest.id\", condition=\"#useCache\")\n    public List<OIContestRankVO> getOiOrderRank(Contest contest, Boolean isOpenSealRank, boolean useCache) {\n        List<String> superAdminUidList = userInfoEntityService.getSuperAdminUidList();\n        List<String> contestAdminUidList = new ArrayList<>(superAdminUidList);\n        contestAdminUidList.add(contest.getUid());\n\n        final List<ContestRecordVO> oiContestRecordList = contestRecordEntityService.getOIContestRecord(contest, isOpenSealRank)\n                .stream()\n                .filter(contestRecord -> contest.getContestAdminRank() || !contestAdminUidList.contains(contestRecord.getUid()))\n                .collect(Collectors.toList());\n        Set<String> hasRecordUserNameSet = oiContestRecordList.stream()\n                .map(ContestRecordVO::getUsername)\n                .collect(Collectors.toSet());\n\n        Map<String, OIContestRankVO> uidContestRankVOMap = initOiContestRankVO(hasRecordUserNameSet);\n\n        List<OIContestRankVO> result = new ArrayList<>(uidContestRankVOMap.values());\n\n        boolean isHighestRankScore = ContestConstant.OI_RANK_HIGHEST_SCORE.equals(contest.getOiRankScoreType());\n        oiContestRecordList.forEach(contestRecord ->\n                setSubmissionInfo(contestRecord, uidContestRankVOMap.get(contestRecord.getUid()), isHighestRankScore));\n\n        HashMap<String, Map<String, Integer>> uidTimeInfoMap = getUidTimeInfoMap(oiContestRecordList);\n        setTimeInfoToResult(result, uidTimeInfoMap);\n\n        // 根据总得分进行降序，再根据总时耗升序排序\n        result = result.stream()\n                .sorted(Comparator.comparing(OIContestRankVO::getTotalScore, Comparator.reverseOrder())\n                        .thenComparing(OIContestRankVO::getTotalTime, Comparator.naturalOrder()))\n                .collect(Collectors.toList());\n\n        if (contestValidator.isContestAdmin(contest)) {\n            result.addAll(getNoRecordUserOiContestRankVOs(contest, hasRecordUserNameSet));\n        }\n        return result;\n    }\n\n    private void computeOIRankNo(Contest contest, List<String> concernedList, boolean removeStar, List<OIContestRankVO> orderResultList, List<OIContestRankVO> topOIRankVOList) {\n        // 需要打星的用户名列表\n        Map<String, Boolean> starAccountMap = starAccountToMap(contest.getStarAccount());\n        // 如果选择了移除打星队伍，同时该用户属于打星队伍，则将其移除\n        if (removeStar) {\n            orderResultList.removeIf(contestRankVO -> starAccountMap.containsKey(contestRankVO.getUsername()));\n        }\n        String currentUserId = null;\n        final UserRolesVO userInfo = UserSessionUtil.getUserInfo();\n        // 外榜：可能未登录\n        if(userInfo != null) {\n            currentUserId = userInfo.getUid();\n        }\n        boolean needAddConcernedUser = false;\n        if (!CollectionUtils.isEmpty(concernedList)) {\n            needAddConcernedUser = true;\n            concernedList.remove(currentUserId);\n        }\n\n        int rankNum = 1;\n        OIContestRankVO preOIRankVO = null;\n        for (int i = 0; i < orderResultList.size(); i++) {\n            OIContestRankVO contestRankVO = orderResultList.get(i);\n            contestRankVO.setSeq(i + 1);\n            // 设置 rank\n            if (starAccountMap.containsKey(contestRankVO.getUsername())) {\n                // 打星队伍排名为-1\n                contestRankVO.setRank(-1);\n            } else {\n                if (rankNum == 1) {\n                    contestRankVO.setRank(rankNum);\n                } else {\n                    // 当前用户的程序总运行时间和总得分跟前一个用户一样，同时前一个不应该为打星用户，排名则一样\n                    if (preOIRankVO.getTotalScore().equals(contestRankVO.getTotalScore())\n                            && preOIRankVO.getTotalTime().equals(contestRankVO.getTotalTime())) {\n                        contestRankVO.setRank(preOIRankVO.getRank());\n                    } else {\n                        contestRankVO.setRank(rankNum);\n                    }\n                }\n                preOIRankVO = contestRankVO;\n                rankNum++;\n            }\n\n            // 添加自己\n            if (StrUtil.isNotEmpty(currentUserId) && currentUserId.equals(contestRankVO.getUid())) {\n                topOIRankVOList.add(contestRankVO);\n            }\n\n            // 添加关注用户\n            if (needAddConcernedUser && concernedList.contains(contestRankVO.getUid())) {\n                topOIRankVOList.add(contestRankVO);\n            }\n        }\n    }\n\n    private List<UserInfo> getNoRecordUserInfos(Contest contest, Set<String> hasRecordUserNameSet) {\n        String extra = ReUtil.getGroup1(\"<extra>([\\\\S\\\\s]*?)<\\\\/extra>\", contest.getAccountLimitRule());\n        if (StrUtil.isBlank(extra)) {\n            return new ArrayList<>();\n        }\n        final Set<String> extraUserNameSet = Arrays.stream(extra.split(\"\\n\"))\n                .filter(u -> !hasRecordUserNameSet.contains(u))\n                .collect(Collectors.toSet());\n        if (CollUtil.isEmpty(extraUserNameSet)) {\n            return new ArrayList<>();\n        }\n        return userInfoEntityService.lambdaQuery()\n                .in(UserInfo::getUsername, extraUserNameSet)\n                .list();\n    }\n\n    /**\n     * 没有提交记录的用户 OIContestRankVO\n     * @param contest\n     * @param hasRecordUserNameSet\n     * @return\n     */\n    private List<OIContestRankVO> getNoRecordUserOiContestRankVOs(Contest contest, Set<String> hasRecordUserNameSet) {\n        final Set<String> registeredUsers = contestRegisterEntityService.getRegisteredUsers(contest.getId());\n        final List<UserInfo> noRecordUserInfos = getNoRecordUserInfos(contest, hasRecordUserNameSet);\n        return noRecordUserInfos.stream()\n                .map(this::initOiContestRankVO)\n                .map(contestRankVO -> contestRankVO.setRegistered(registeredUsers.contains(contestRankVO.getUid())))\n                .sorted(Comparator.comparing(OIContestRankVO::getRegistered, Comparator.reverseOrder()))\n                .collect(Collectors.toList());\n    }\n\n    private Map<String, OIContestRankVO> initOiContestRankVO(Set<String> userNameSet) {\n        if (CollUtil.isEmpty(userNameSet)) {\n            return new HashMap<>();\n        }\n\n        return userInfoEntityService.lambdaQuery()\n                .in(UserInfo::getUsername, userNameSet)\n                .list()\n                .stream()\n                .map(this::initOiContestRankVO)\n                .collect(Collectors.toMap(OIContestRankVO::getUid, o -> o));\n    }\n\n    private OIContestRankVO initOiContestRankVO(UserInfo userInfo) {\n        return new OIContestRankVO()\n                .setRealname(userInfo.getRealname())\n                .setUid(userInfo.getUuid())\n                .setUsername(userInfo.getUsername())\n                .setSchool(userInfo.getSchool())\n                .setAvatar(userInfo.getAvatar())\n                .setGender(userInfo.getGender())\n                .setNickname(userInfo.getNickname())\n                .setTotalScore(0)\n                .setTotalTime(0)\n                .setSubmissionInfo(new HashMap<>());\n    }\n\n    /**\n     * \"totalScore\": 400,\n     * \"submissionInfo\": {\n     * \"1\": 100,\n     * \"2\": 100,\n     * \"3\": 100,\n     * \"4\": 100\n     * }\n     */\n    private void setSubmissionInfo(ContestRecordVO contestRecord, OIContestRankVO oiContestRankVO, boolean isHighestRankScore) {\n        Map<String, Integer> submissionInfo = oiContestRankVO.getSubmissionInfo();\n        Integer score = submissionInfo.get(contestRecord.getDisplayId());\n        if (isHighestRankScore) {\n            if (score == null) {\n                oiContestRankVO.setTotalScore(oiContestRankVO.getTotalScore() + contestRecord.getScore());\n                submissionInfo.put(contestRecord.getDisplayId(), contestRecord.getScore());\n            }\n        } else {\n            if (contestRecord.getScore() != null) {\n                // 为了避免同个提交时间的重复计算\n                if (score != null) {\n                    oiContestRankVO.setTotalScore(oiContestRankVO.getTotalScore() - score + contestRecord.getScore());\n                } else {\n                    oiContestRankVO.setTotalScore(oiContestRankVO.getTotalScore() + contestRecord.getScore());\n                }\n            }\n            submissionInfo.put(contestRecord.getDisplayId(), contestRecord.getScore());\n        }\n    }\n\n    /**\n     * uid -> timeInfo\n     *\n     * @param oiContestRecord\n     * @return\n     */\n    private HashMap<String, Map<String, Integer>> getUidTimeInfoMap(List<ContestRecordVO> oiContestRecord) {\n        HashMap<String, Map<String, Integer>> uidTimeInfoMap = new HashMap<>();\n        oiContestRecord.stream()\n                .filter(contestRecord -> Objects.equals(contestRecord.getStatus(), ContestEnum.RECORD_AC.getCode()))\n                .forEach(contestRecord -> computeUidTimeInfoMap(uidTimeInfoMap, contestRecord));\n\n        return uidTimeInfoMap;\n    }\n\n    /**\n     * uid,pid,cpid,MAX(score)\n     * 相同分数，不同时间，e.g.\n     * 1,7772,311,100,6\n     * 1,7772,311,100,10\n     *\n     * @param uidTimeInfoMap\n     * @param contestRecord\n     */\n    private void computeUidTimeInfoMap(HashMap<String, Map<String, Integer>> uidTimeInfoMap, ContestRecordVO contestRecord) {\n        Map<String, Integer> pidTimeMap = uidTimeInfoMap.get(contestRecord.getUid());\n        if (pidTimeMap != null) {\n            Integer useTime = pidTimeMap.get(contestRecord.getDisplayId());\n            if (useTime != null) {\n                // 如果时间消耗比原来的少\n                if (useTime > contestRecord.getUseTime()) {\n                    pidTimeMap.put(contestRecord.getDisplayId(), contestRecord.getUseTime());\n                }\n            } else {\n                pidTimeMap.put(contestRecord.getDisplayId(), contestRecord.getUseTime());\n            }\n        } else {\n            uidTimeInfoMap.put(contestRecord.getUid(),\n                    MapUtil.builder(new HashMap<String, Integer>())\n                            .put(contestRecord.getDisplayId(), contestRecord.getUseTime()).build());\n        }\n    }\n\n    /**\n     * \"totalTime\": 11,\n     * \"timeInfo\": {\n     * \"1\": 3,\n     * \"2\": 3,\n     * \"3\": 3,\n     * \"4\": 2\n     * }\n     *\n     * @param result\n     * @param uidMapTime\n     */\n    private void setTimeInfoToResult(List<OIContestRankVO> result, HashMap<String, Map<String, Integer>> uidMapTime) {\n        result.forEach(oiContestRankVO -> {\n            Map<String, Integer> pidMapTime = uidMapTime.get(oiContestRankVO.getUid());\n            int sumTime = Optional.ofNullable(pidMapTime)\n                    .map(val -> val.values().stream().reduce(0, Integer::sum))\n                    .orElse(0);\n            oiContestRankVO.setTotalTime(sumTime);\n            oiContestRankVO.setTimeInfo(pidMapTime);\n        });\n    }\n\n    private Map<String, Boolean> starAccountToMap(String starAccountStr) {\n        if (StrUtil.isEmpty(starAccountStr)) {\n            return new HashMap<>();\n        }\n        JSONObject jsonObject = JSONUtil.parseObj(starAccountStr);\n        List<String> accountList = jsonObject.get(\"star_account\", List.class);\n        return Optional.ofNullable(accountList)\n                .orElse(Collections.emptyList())\n                .stream()\n                .filter(StrUtil::isNotEmpty)\n                .collect(Collectors.toMap(str -> str, str -> true));\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/ContestScoreboardService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.ContestRankDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ContestOutsideInfo;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 20:02\n * @Description:\n */\n\npublic interface ContestScoreboardService {\n\n    ContestOutsideInfo getContestOutsideInfo(Long cid);\n\n    IPage getContestOutsideScoreboard(ContestRankDTO contestRankDTO);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/ContestService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.ContestPrintDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ContestRankDTO;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterContestDTO;\nimport com.simplefanc.voj.backend.pojo.dto.UserReadContestAnnouncementDTO;\nimport com.simplefanc.voj.backend.pojo.vo.*;\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 22:26\n * @Description:\n */\npublic interface ContestService {\n\n    IPage<ContestVO> getContestList(Integer limit, Integer currentPage, Integer status, Integer type, String keyword);\n\n    ContestVO getContestInfo(Long cid);\n\n    void toRegisterContest(RegisterContestDTO registerContestDTO);\n\n    AccessVO getContestAccess(Long cid);\n\n    List<ContestProblemVO> getContestProblem(Long cid);\n\n    ProblemInfoVO getContestProblemDetails(Long cid, String displayId);\n\n    // TODO 参数过多\n    IPage<JudgeVO> getContestSubmissionList(Integer limit, Integer currentPage, Boolean onlyMine, String displayId,\n                                            Integer searchStatus, String searchUsername, Long searchCid, Boolean beforeContestSubmit,\n                                            Boolean completeProblemId);\n\n    IPage getContestRank(ContestRankDTO contestRankDTO);\n\n    Set<String> getContestAdminUidList(Contest contest);\n\n    IPage<AnnouncementVO> getContestAnnouncement(Long cid, Integer limit, Integer currentPage);\n\n    List<Announcement> getContestUserNotReadAnnouncement(UserReadContestAnnouncementDTO userReadContestAnnouncementDTO);\n\n    void submitPrintText(ContestPrintDTO contestPrintDTO);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/DiscussionService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.DiscussionVO;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionReport;\nimport com.simplefanc.voj.common.pojo.entity.problem.Category;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 15:21\n * @Description:\n */\n\npublic interface DiscussionService {\n\n    IPage<Discussion> getDiscussionList(Integer limit, Integer currentPage, Integer categoryId, String pid,\n                                        Boolean onlyMine, String keyword, Boolean admin);\n\n    DiscussionVO getDiscussion(Integer did);\n\n    void addDiscussion(Discussion discussion);\n\n    void updateDiscussion(Discussion discussion);\n\n    void removeDiscussion(Integer did);\n\n    void addDiscussionLike(Integer did, Boolean toLike);\n\n    List<Category> getDiscussionCategory();\n\n    void addDiscussionReport(DiscussionReport discussionReport);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/HomeService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.vo.ACMRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.backend.pojo.vo.ContestVO;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 21:00\n * @Description:\n */\n\npublic interface HomeService {\n\n    /**\n     * @MethodName getRecentContest\n     * @Params * @param null\n     * @Description 获取最近14天的比赛信息列表\n     * @Return CommonResult\n     * @Since 2021/12/29\n     */\n    List<ContestVO> getRecentContest();\n\n    /**\n     * @MethodName getHomeCarousel\n     * @Params\n     * @Description 获取主页轮播图\n     * @Return\n     * @Since 2021/9/4\n     */\n    List<HashMap<String, Object>> getHomeCarousel();\n\n    /**\n     * @MethodName getRecentSevenACRank\n     * @Params * @param null\n     * @Description 获取最近7天用户做题榜单\n     * @Return\n     * @Since 2021/1/15\n     */\n    List<ACMRankVO> getRecentSevenACRank();\n\n    /**\n     * @MethodName getRecentOtherContest\n     * @Params * @param null\n     * @Description 获取最近其他OJ的比赛信息列表\n     * @Return CommonResult\n     * @Since 2021/1/15\n     */\n    List<HashMap<String, Object>> getRecentOtherContest();\n\n    /**\n     * @MethodName getCommonAnnouncement\n     * @Params * @param null\n     * @Description 获取主页公告列表\n     * @Return CommonResult\n     * @Since 2021/12/29\n     */\n    IPage<AnnouncementVO> getCommonAnnouncement(Integer limit, Integer currentPage);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/JudgeService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.SubmitIdListDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ToJudgeDTO;\nimport com.simplefanc.voj.backend.pojo.vo.JudgeVO;\nimport com.simplefanc.voj.backend.pojo.vo.SubmissionInfoVO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 11:12\n * @Description:\n */\n\npublic interface JudgeService {\n\n    /**\n     * @MethodName submitProblemJudge\n     * @Description 核心方法 判题通过openfeign调用判题系统服务\n     * @Since 2021/10/30\n     */\n    Judge submitProblemJudge(ToJudgeDTO judgeDTO);\n\n    /**\n     * @MethodName resubmit\n     * @Description 调用判题服务器提交失败超过60s后，用户点击按钮重新提交判题进入的方法\n     * @Since 2021/2/12\n     */\n    Judge resubmit(Long submitId);\n\n    /**\n     * @MethodName getSubmission\n     * @Description 获取单个提交记录的详情\n     * @Since 2021/1/2\n     */\n    SubmissionInfoVO getSubmission(Long submitId);\n\n    /**\n     * @MethodName updateSubmission\n     * @Description 修改单个提交详情的分享权限\n     * @Since 2021/1/2\n     */\n    void updateSubmission(Judge judge);\n\n    /**\n     * @MethodName getJudgeList\n     * @Description 通用查询判题记录列表\n     * @Since 2021/10/29\n     */\n    IPage<JudgeVO> getJudgeList(Integer limit, Integer currentPage, Boolean onlyMine, String searchPid,\n                                Integer searchStatus, String searchUsername, Boolean completeProblemId);\n\n    /**\n     * @MethodName checkJudgeResult\n     * @Description 对提交列表状态为Pending和Judging的提交进行更新检查\n     * @Since 2021/1/3\n     */\n    HashMap<Long, Object> checkCommonJudgeResult(SubmitIdListDTO submitIdListDTO);\n\n    /**\n     * @MethodName checkContestJudgeResult\n     * @Description 需要检查是否为封榜，是否可以查询结果，避免有人恶意查询\n     * @Since 2021/6/11\n     */\n    HashMap<Long, Object> checkContestJudgeResult(SubmitIdListDTO submitIdListDTO);\n\n    /**\n     * @MethodName getJudgeCase\n     * @Description 获得指定提交id的测试样例结果，暂不支持查看测试数据，只可看测试点结果，时间，空间，或者IO得分\n     * @Since 2021/10/29\n     */\n    List<JudgeCase> getAllCaseResult(Long submitId);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/ProblemService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.pojo.dto.PidListDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemInfoVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.backend.pojo.vo.RandomProblemVO;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 10:37\n * @Description:\n */\n\npublic interface ProblemService {\n\n    /**\n     * @MethodName getProblemList\n     * @Params * @param null\n     * @Description 获取题目列表分页\n     * @Since 2021/10/27\n     */\n    Page<ProblemVO> getProblemList(Integer limit, Integer currentPage, String keyword, List<Long> tagId,\n                                   Integer difficulty, String oj, Boolean problemVisible);\n\n    /**\n     * @MethodName getRandomProblem\n     * @Description 随机选取一道题目\n     * @Since 2021/10/27\n     */\n    RandomProblemVO getRandomProblem();\n\n    /**\n     * @MethodName getUserProblemStatus\n     * @Description 获取用户对应该题目列表中各个题目的做题情况\n     * @Since 2021/12/29\n     */\n    HashMap<Long, Object> getUserProblemStatus(PidListDTO pidListDTO);\n\n    /**\n     * @MethodName getProblemInfo\n     * @Description 获取指定题目的详情信息，标签，所支持语言，做题情况（只能查询公开题目 也就是auth为1）\n     * @Since 2021/10/27\n     */\n    ProblemInfoVO getProblemInfo(String problemId);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/RankService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 20:47\n * @Description:\n */\n\npublic interface RankService {\n\n    /**\n     * @MethodName get-rank-list\n     * @Params * @param null\n     * @Description 获取排行榜数据\n     * @Return CommonResult\n     * @Since 2021/10/27\n     */\n    IPage getRankList(Integer limit, Integer currentPage, String searchUser, Integer type);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/TrainingService.java",
    "content": "package com.simplefanc.voj.backend.service.oj;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterTrainingDTO;\nimport com.simplefanc.voj.backend.pojo.vo.AccessVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemVO;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.TrainingVO;\nimport org.springframework.web.bind.annotation.RequestParam;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 17:12\n * @Description:\n */\n\npublic interface TrainingService {\n\n    /**\n     * @param limit\n     * @param currentPage\n     * @param keyword\n     * @param categoryId\n     * @param auth\n     * @MethodName getTrainingList\n     * @Description 获取训练题单列表，可根据关键词、类别、权限、类型过滤\n     * @Return\n     * @Since 2021/11/20\n     */\n    IPage<TrainingVO> getTrainingList(Integer limit, Integer currentPage, String keyword, Long categoryId, String auth);\n\n    /**\n     * @param tid\n     * @MethodName getTraining\n     * @Description 根据tid获取指定训练详情\n     * @Return\n     * @Since 2021/11/20\n     */\n    TrainingVO getTraining(@RequestParam(value = \"tid\") Long tid);\n\n    /**\n     * @param tid\n     * @MethodName getTrainingProblemList\n     * @Description 根据tid获取指定训练的题单题目列表\n     * @Return\n     * @Since 2021/11/20\n     */\n    List<ProblemVO> getTrainingProblemList(Long tid);\n\n    /**\n     * @param registerTrainingDTO\n     * @MethodName toRegisterTraining\n     * @Description 注册校验私有权限的训练\n     * @Return\n     * @Since 2021/11/20\n     */\n    void toRegisterTraining(RegisterTrainingDTO registerTrainingDTO);\n\n    /**\n     * @param tid\n     * @MethodName getTrainingAccess\n     * @Description 私有权限的训练需要获取当前用户是否有进入训练的权限\n     * @Return\n     * @Since 2021/11/20\n     */\n    AccessVO getTrainingAccess(Long tid);\n\n    /**\n     * @param tid\n     * @param limit\n     * @param currentPage\n     * @MethodName getTrainingRnk\n     * @Description 获取训练的排行榜分页\n     * @Return\n     * @Since 2021/11/22\n     */\n    IPage<TrainingRankVO> getTrainingRank(Long tid, Integer limit, Integer currentPage);\n\n    /**\n     * 未启用，该操作会导致公开训练也记录，但其实并不需求，会造成数据量无效增加\n     */\n    void checkAndSyncTrainingRecord(Long pid, Long submitId, String uid);\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/AccountServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.lang.Validator;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusSystemErrorException;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.dao.user.*;\nimport com.simplefanc.voj.backend.pojo.dto.ChangeEmailDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ChangePasswordDTO;\nimport com.simplefanc.voj.backend.pojo.dto.CheckUsernameOrEmailDTO;\nimport com.simplefanc.voj.backend.pojo.vo.*;\nimport com.simplefanc.voj.backend.service.admin.user.UserRecordService;\nimport com.simplefanc.voj.backend.service.oj.AccountService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.user.Role;\nimport com.simplefanc.voj.common.pojo.entity.user.Session;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport com.simplefanc.voj.common.pojo.entity.user.UserInfo;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 16:53\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class AccountServiceImpl implements AccountService {\n\n    private final RedisUtil redisUtil;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final UserRoleEntityService userRoleEntityService;\n\n    private final UserRecordService userRecordService;\n\n    private final UserAcproblemEntityService userAcproblemEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final SessionEntityService sessionEntityService;\n\n    /**\n     * @MethodName checkUsernameOrEmail\n     * @Params * @param null\n     * @Description 检验用户名和邮箱是否存在\n     * @Return\n     * @Since 2021/11/5\n     */\n    @Override\n    public CheckUsernameOrEmailVO checkUsernameOrEmail(CheckUsernameOrEmailDTO checkUsernameOrEmailDTO) {\n\n        String email = checkUsernameOrEmailDTO.getEmail();\n\n        String username = checkUsernameOrEmailDTO.getUsername();\n\n        boolean rightEmail = false;\n\n        boolean rightUsername = false;\n\n        if (StrUtil.isNotEmpty(email)) {\n            email = email.trim();\n            boolean isEmail = Validator.isEmail(email);\n            if (!isEmail) {\n                rightEmail = false;\n            } else {\n                QueryWrapper<UserInfo> wrapper = new QueryWrapper<UserInfo>().eq(\"email\", email);\n                UserInfo user = userInfoEntityService.getOne(wrapper, false);\n                if (user != null) {\n                    rightEmail = true;\n                } else {\n                    rightEmail = false;\n                }\n            }\n        }\n\n        if (StrUtil.isNotEmpty(username)) {\n            username = username.trim();\n            QueryWrapper<UserInfo> wrapper = new QueryWrapper<UserInfo>().eq(\"username\", username);\n            UserInfo user = userInfoEntityService.getOne(wrapper, false);\n            if (user != null) {\n                rightUsername = true;\n            } else {\n                rightUsername = false;\n            }\n        }\n\n        CheckUsernameOrEmailVO checkUsernameOrEmailVO = new CheckUsernameOrEmailVO();\n        checkUsernameOrEmailVO.setEmail(rightEmail);\n        checkUsernameOrEmailVO.setUsername(rightUsername);\n        return checkUsernameOrEmailVO;\n    }\n\n    /**\n     * @param uid\n     * @MethodName getUserHomeInfo\n     * @Description 前端userHome用户个人主页的数据请求，主要是返回解决题目数，AC的题目列表，提交总数，AC总数，Rating分，\n     * @Return CommonResult\n     * @Since 2021/01/07\n     */\n    @Override\n    public UserHomeVO getUserHomeInfo(String uid, String username) {\n\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        // 如果没有uid和username，默认查询当前登录用户的\n        if (StrUtil.isEmpty(uid) && StrUtil.isEmpty(username)) {\n            if (userRolesVO != null) {\n                uid = userRolesVO.getUid();\n            } else {\n                throw new StatusFailException(\"错误：uid和username不能都为空！\");\n            }\n        }\n\n        UserHomeVO userHomeInfo = userRecordService.getUserHomeInfo(uid, username);\n        if (userHomeInfo == null) {\n            throw new StatusFailException(\"用户不存在\");\n        }\n        QueryWrapper<UserAcproblem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"uid\", userHomeInfo.getUid()).select(\"distinct pid\");\n        List<Long> pidList = new LinkedList<>();\n        List<UserAcproblem> acProblemList = userAcproblemEntityService.list(queryWrapper);\n        acProblemList.forEach(acProblem -> {\n            pidList.add(acProblem.getPid());\n        });\n\n        List<String> disPlayIdList = new LinkedList<>();\n\n        if (pidList.size() > 0) {\n            QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();\n            problemQueryWrapper.in(\"id\", pidList);\n            List<Problem> problems = problemEntityService.list(problemQueryWrapper);\n            problems.forEach(problem -> {\n                disPlayIdList.add(problem.getProblemId());\n            });\n        }\n\n        userHomeInfo.setSolvedList(disPlayIdList);\n        QueryWrapper<Session> sessionQueryWrapper = new QueryWrapper<>();\n        sessionQueryWrapper.eq(\"uid\", userHomeInfo.getUid()).orderByDesc(\"gmt_create\").last(\"limit 1\");\n\n        Session recentSession = sessionEntityService.getOne(sessionQueryWrapper, false);\n        if (recentSession != null) {\n            userHomeInfo.setRecentLoginTime(recentSession.getGmtCreate());\n        }\n        return userHomeInfo;\n    }\n\n    /**\n     * @MethodName changePassword\n     * @Description 修改密码的操作，连续半小时内修改密码错误5次，则需要半个小时后才可以再次尝试修改密码\n     * @Return\n     * @Since 2021/1/8\n     */\n    @Override\n    public ChangeAccountVO changePassword(ChangePasswordDTO changePasswordDTO) {\n        String oldPassword = changePasswordDTO.getOldPassword();\n        String newPassword = changePasswordDTO.getNewPassword();\n\n        // 数据可用性判断\n        if (StrUtil.isEmpty(oldPassword) || StrUtil.isEmpty(newPassword)) {\n            throw new StatusFailException(\"错误：原始密码或新密码不能为空！\");\n        }\n        if (newPassword.length() < 6 || newPassword.length() > 20) {\n            throw new StatusFailException(\"新密码长度应该为6~20位！\");\n        }\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        // 如果已经被锁定半小时不能修改\n        String lockKey = RedisConstant.CODE_CHANGE_PASSWORD_LOCK + userRolesVO.getUid();\n        // 统计失败的key\n        String countKey = RedisConstant.CODE_CHANGE_PASSWORD_FAIL + userRolesVO.getUid();\n\n        ChangeAccountVO resp = new ChangeAccountVO();\n        if (redisUtil.hasKey(lockKey)) {\n            long expire = redisUtil.getExpire(lockKey);\n            Date now = new Date();\n            long minute = expire / 60;\n            long second = expire % 60;\n            SimpleDateFormat formatter = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n            resp.setCode(403);\n            Date afterDate = new Date(now.getTime() + expire * 1000);\n            String msg = \"由于您多次修改密码失败，修改密码功能已锁定，请在\" + minute + \"分\" + second + \"秒后(\" + formatter.format(afterDate)\n                    + \")再进行尝试！\";\n            resp.setMsg(msg);\n            return resp;\n        }\n        // 与当前登录用户的密码进行比较判断\n        // 如果相同，则进行修改密码操作\n        if (userRolesVO.getPassword().equals(SecureUtil.md5(oldPassword))) {\n            UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();\n            // 数据库用户密码全部用md5加密\n            updateWrapper.set(\"password\", SecureUtil.md5(newPassword)).eq(\"uuid\", userRolesVO.getUid());\n            boolean isOk = userInfoEntityService.update(updateWrapper);\n            if (isOk) {\n                resp.setCode(200);\n                resp.setMsg(\"修改密码成功！您将于5秒钟后退出进行重新登录操作！\");\n                // 清空记录\n                redisUtil.del(countKey);\n                // 更新session\n                userRolesVO.setPassword(SecureUtil.md5(newPassword));\n                UserSessionUtil.setUserInfo(userRolesVO);\n                return resp;\n            } else {\n                throw new StatusSystemErrorException(\"系统错误：修改密码失败！\");\n            }\n        } else {\n            // 如果不同，则进行记录，当失败次数达到5次，半个小时后才可重试\n            Integer count = redisUtil.get(countKey, Integer.class);\n            if (count == null) {\n                // 三十分钟不尝试，该限制会自动清空消失\n                redisUtil.set(countKey, 1, 60 * 30);\n                count = 0;\n            } else if (count < 5) {\n                redisUtil.incr(countKey, 1);\n            }\n            count++;\n            if (count == 5) {\n                // 清空统计\n                redisUtil.del(countKey);\n                // 设置锁定更改\n                redisUtil.set(lockKey, \"lock\", 60 * 30);\n            }\n            resp.setCode(400);\n            resp.setMsg(\"原始密码错误！您已累计修改密码失败\" + count + \"次...\");\n            return resp;\n        }\n    }\n\n    /**\n     * @MethodName changeEmail\n     * @Description 修改邮箱的操作，连续半小时内密码错误5次，则需要半个小时后才可以再次尝试修改\n     * @Return\n     * @Since 2021/1/9\n     */\n    @Override\n    public ChangeAccountVO changeEmail(ChangeEmailDTO changeEmailDTO) {\n\n        String password = changeEmailDTO.getPassword();\n        String newEmail = changeEmailDTO.getNewEmail();\n        // 数据可用性判断\n        if (StrUtil.isEmpty(password) || StrUtil.isEmpty(newEmail)) {\n            throw new StatusFailException(\"错误：密码或新邮箱不能为空！\");\n        }\n        if (!Validator.isEmail(newEmail)) {\n            throw new StatusFailException(\"邮箱格式错误！\");\n        }\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        // 如果已经被锁定半小时不能修改\n        String lockKey = RedisConstant.CODE_CHANGE_EMAIL_LOCK + userRolesVO.getUid();\n        // 统计失败的key\n        String countKey = RedisConstant.CODE_CHANGE_EMAIL_FAIL + userRolesVO.getUid();\n\n        ChangeAccountVO resp = new ChangeAccountVO();\n        if (redisUtil.hasKey(lockKey)) {\n            long expire = redisUtil.getExpire(lockKey);\n            Date now = new Date();\n            long minute = expire / 60;\n            long second = expire % 60;\n            SimpleDateFormat formatter = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n            resp.setCode(403);\n            Date afterDate = new Date(now.getTime() + expire * 1000);\n            String msg = \"由于您多次修改邮箱失败，修改邮箱功能已锁定，请在\" + minute + \"分\" + second + \"秒后(\" + formatter.format(afterDate)\n                    + \")再进行尝试！\";\n            resp.setMsg(msg);\n            return resp;\n        }\n        // 与当前登录用户的密码进行比较判断\n        // 如果相同，则进行修改操作\n        if (userRolesVO.getPassword().equals(SecureUtil.md5(password))) {\n            UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();\n            updateWrapper.set(\"email\", newEmail).eq(\"uuid\", userRolesVO.getUid());\n\n            boolean isOk = userInfoEntityService.update(updateWrapper);\n            if (isOk) {\n                UserInfoVO userInfoVO = new UserInfoVO();\n                BeanUtil.copyProperties(userRolesVO, userInfoVO, \"roles\");\n                userInfoVO.setRoleList(userRolesVO.getRoles().stream().map(Role::getRole).collect(Collectors.toList()));\n\n                resp.setCode(200);\n                resp.setMsg(\"修改邮箱成功！\");\n                resp.setUserInfo(userInfoVO);\n                // 清空记录\n                redisUtil.del(countKey);\n                // 更新session\n                userRolesVO.setEmail(newEmail);\n                UserSessionUtil.setUserInfo(userRolesVO);\n                return resp;\n            } else {\n                throw new StatusSystemErrorException(\"系统错误：修改邮箱失败！\");\n            }\n        } else {\n            // 如果不同，则进行记录，当失败次数达到5次，半个小时后才可重试\n            Integer count = redisUtil.get(countKey, Integer.class);\n            if (count == null) {\n                // 三十分钟不尝试，该限制会自动清空消失\n                redisUtil.set(countKey, 1, 60 * 30);\n                count = 0;\n            } else if (count < 5) {\n                redisUtil.incr(countKey, 1);\n            }\n            count++;\n            if (count == 5) {\n                // 清空统计\n                redisUtil.del(countKey);\n                // 设置锁定更改\n                redisUtil.set(lockKey, \"lock\", 60 * 30);\n            }\n\n            resp.setCode(400);\n            resp.setMsg(\"密码错误！您已累计修改邮箱失败\" + count + \"次...\");\n            return resp;\n        }\n    }\n\n    @Override\n    public UserInfoVO changeUserInfo(UserInfoVO userInfoVO) {\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        String realname = userInfoVO.getRealname();\n        String nickname = userInfoVO.getNickname();\n        if (StrUtil.isNotEmpty(realname) && realname.length() > 50) {\n            throw new StatusFailException(\"真实姓名的长度不能超过50位\");\n        }\n        if (StrUtil.isNotEmpty(nickname) && nickname.length() > 20) {\n            throw new StatusFailException(\"昵称的长度不能超过20位\");\n        }\n        UserInfo userInfo = new UserInfo();\n        userInfo.setUuid(userRolesVO.getUid()).setCfUsername(userInfoVO.getCfUsername()).setRealname(realname)\n                .setNickname(nickname).setSignature(userInfoVO.getSignature()).setBlog(userInfoVO.getBlog())\n                .setGender(userInfoVO.getGender()).setEmail(userRolesVO.getEmail()).setGithub(userInfoVO.getGithub())\n                .setSchool(userInfoVO.getSchool()).setNumber(userInfoVO.getNumber());\n\n        boolean isOk = userInfoEntityService.updateById(userInfo);\n\n        if (isOk) {\n            // 更新session\n            UserRolesVO userRoles = userRoleEntityService.getUserRoles(userRolesVO.getUid(), null);\n            UserSessionUtil.setUserInfo(userRoles);\n\n            UserInfoVO userInfoRes = new UserInfoVO();\n            BeanUtil.copyProperties(userRoles, userInfoRes, \"roles\");\n            userInfoVO.setRoleList(userRoles.getRoles().stream().map(Role::getRole).collect(Collectors.toList()));\n\n            return userInfoRes;\n        } else {\n            throw new StatusFailException(\"更新个人信息失败！\");\n        }\n\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/CommentServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.extra.emoji.EmojiUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.dao.discussion.CommentEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.CommentLikeEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.ReplyEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserAcproblemEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.ReplyDTO;\nimport com.simplefanc.voj.backend.pojo.vo.CommentListVO;\nimport com.simplefanc.voj.backend.pojo.vo.CommentVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.oj.CommentService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Comment;\nimport com.simplefanc.voj.common.pojo.entity.discussion.CommentLike;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Reply;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 15:59\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class CommentServiceImpl implements CommentService {\n\n    private final CommentEntityService commentEntityService;\n\n    private final CommentLikeEntityService commentLikeEntityService;\n\n    private final ReplyEntityService replyEntityService;\n\n    private final DiscussionEntityService discussionEntityService;\n\n    private final UserAcproblemEntityService userAcproblemEntityService;\n\n    @Override\n    public CommentListVO getComments(Long cid, Integer did, Integer limit, Integer currentPage) {\n\n        // 如果有登录，则获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        boolean isRoot = UserSessionUtil.isRoot();\n\n        IPage<CommentVO> commentList = commentEntityService.getCommentList(limit, currentPage, cid, did, isRoot,\n                userRolesVO != null ? userRolesVO.getUid() : null);\n\n        HashMap<Integer, Boolean> commentLikeMap = new HashMap<>();\n\n        if (userRolesVO != null) {\n            // 如果是有登录 需要检查是否对评论有点赞\n            List<Integer> commentIdList = new LinkedList<>();\n\n            for (CommentVO commentVO : commentList.getRecords()) {\n                commentIdList.add(commentVO.getId());\n            }\n\n            if (commentIdList.size() > 0) {\n\n                QueryWrapper<CommentLike> commentLikeQueryWrapper = new QueryWrapper<>();\n                commentLikeQueryWrapper.in(\"cid\", commentIdList);\n\n                List<CommentLike> commentLikeList = commentLikeEntityService.list(commentLikeQueryWrapper);\n\n                // 如果存在记录需要修正Map为true\n                for (CommentLike tmp : commentLikeList) {\n                    commentLikeMap.put(tmp.getCid(), true);\n                }\n            }\n        }\n\n        CommentListVO commentListVO = new CommentListVO();\n        commentListVO.setCommentList(commentList);\n        commentListVO.setCommentLikeMap(commentLikeMap);\n        return commentListVO;\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public CommentVO addComment(Comment comment) {\n\n        if (StrUtil.isEmpty(comment.getContent().trim())) {\n            throw new StatusFailException(\"评论内容不能为空！\");\n        }\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        // 比赛外的评论 除管理员外 只有AC 10道以上才可评论\n        if (comment.getCid() == null) {\n            if (!UserSessionUtil.isRoot() && !UserSessionUtil.isAdmin()\n                    && !UserSessionUtil.isProblemAdmin()) {\n                QueryWrapper<UserAcproblem> queryWrapper = new QueryWrapper<>();\n                queryWrapper.eq(\"uid\", userRolesVO.getUid()).select(\"distinct pid\");\n                int userAcProblemCount = userAcproblemEntityService.count(queryWrapper);\n\n                if (userAcProblemCount < 10) {\n                    throw new StatusForbiddenException(\"对不起，您暂时不能评论！请先去提交题目通过10道以上!\");\n                }\n            }\n        }\n\n        comment.setFromAvatar(userRolesVO.getAvatar())\n                .setFromName(userRolesVO.getUsername())\n                .setFromUid(userRolesVO.getUid());\n\n        if (UserSessionUtil.isRoot()) {\n            comment.setFromRole(\"root\");\n        } else if (UserSessionUtil.isAdmin() || UserSessionUtil.isProblemAdmin()) {\n            comment.setFromRole(\"admin\");\n        } else {\n            comment.setFromRole(\"user\");\n        }\n\n        // 带有表情的字符串转换为编码\n        comment.setContent(EmojiUtil.toHtml(comment.getContent()));\n\n        boolean isOk = commentEntityService.saveOrUpdate(comment);\n\n        if (isOk) {\n            CommentVO commentVO = new CommentVO();\n            commentVO.setContent(comment.getContent());\n            commentVO.setId(comment.getId());\n            commentVO.setFromAvatar(comment.getFromAvatar());\n            commentVO.setFromName(comment.getFromName());\n            commentVO.setFromUid(comment.getFromUid());\n            commentVO.setLikeNum(0);\n            commentVO.setGmtCreate(comment.getGmtCreate());\n            commentVO.setReplyList(new LinkedList<>());\n            // 如果是讨论区的回复，发布成功需要添加统计该讨论的回复数\n            if (comment.getDid() != null) {\n                Discussion discussion = discussionEntityService.getById(comment.getDid());\n                if (discussion != null) {\n                    discussion.setCommentNum(discussion.getCommentNum() + 1);\n                    discussionEntityService.updateById(discussion);\n                    // 更新消息\n                    commentEntityService.updateCommentMsg(discussion.getUid(), userRolesVO.getUid(),\n                            comment.getContent(), comment.getDid());\n                }\n            }\n            return commentVO;\n        } else {\n            throw new StatusFailException(\"评论失败，请重新尝试！\");\n        }\n\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteComment(Comment comment) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        // 如果不是评论本人 或者不是管理员 无权限删除该评论\n        if (comment.getFromUid().equals(userRolesVO.getUid()) || UserSessionUtil.isRoot()\n                || UserSessionUtil.isAdmin() || UserSessionUtil.isProblemAdmin()) {\n\n            // 获取需要删除该评论的回复数\n            int replyNum = replyEntityService.count(new QueryWrapper<Reply>().eq(\"comment_id\", comment.getId()));\n\n            // 删除该数据 包括关联外键的reply表数据\n            boolean isDeleteComment = commentEntityService.removeById(comment.getId());\n\n            // 同时需要删除该评论的回复表数据\n            replyEntityService.remove(new UpdateWrapper<Reply>().eq(\"comment_id\", comment.getId()));\n\n            if (isDeleteComment) {\n                // 如果是讨论区的回复，删除成功需要减少统计该讨论的回复数\n                if (comment.getDid() != null) {\n                    UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();\n                    discussionUpdateWrapper.eq(\"id\", comment.getDid())\n                            .setSql(\"comment_num=comment_num-\" + (replyNum + 1));\n                    discussionEntityService.update(discussionUpdateWrapper);\n                }\n            } else {\n                throw new StatusFailException(\"删除失败，请重新尝试\");\n            }\n\n        } else {\n            throw new StatusForbiddenException(\"无权删除该评论\");\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addDiscussionLike(Integer cid, Boolean toLike, Integer sourceId, String sourceType) {\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        QueryWrapper<CommentLike> commentLikeQueryWrapper = new QueryWrapper<>();\n        commentLikeQueryWrapper.eq(\"cid\", cid).eq(\"uid\", userRolesVO.getUid());\n\n        CommentLike commentLike = commentLikeEntityService.getOne(commentLikeQueryWrapper, false);\n        // 添加点赞\n        if (toLike) {\n            // 如果不存在就添加\n            if (commentLike == null) {\n                boolean isSave = commentLikeEntityService\n                        .saveOrUpdate(new CommentLike().setUid(userRolesVO.getUid()).setCid(cid));\n                if (!isSave) {\n                    throw new StatusFailException(\"点赞失败，请重试尝试！\");\n                }\n            }\n            // 点赞+1\n            Comment comment = commentEntityService.getById(cid);\n            if (comment != null) {\n                comment.setLikeNum(comment.getLikeNum() + 1);\n                commentEntityService.updateById(comment);\n                commentEntityService.updateCommentLikeMsg(comment.getFromUid(), userRolesVO.getUid(), sourceId,\n                        sourceType);\n            }\n        }\n        // 取消点赞\n        else {\n            // 如果存在就删除\n            if (commentLike != null) {\n                boolean isDelete = commentLikeEntityService.removeById(commentLike.getId());\n                if (!isDelete) {\n                    throw new StatusFailException(\"取消点赞失败，请重试尝试！\");\n                }\n            }\n            // 点赞-1\n            UpdateWrapper<Comment> commentUpdateWrapper = new UpdateWrapper<>();\n            commentUpdateWrapper.setSql(\"like_num=like_num-1\").eq(\"id\", cid);\n            commentEntityService.update(commentUpdateWrapper);\n        }\n\n    }\n\n    @Override\n    public List<Reply> getAllReply(Integer commentId, Long cid) {\n\n        // 如果有登录，则获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        boolean isRoot = UserSessionUtil.isRoot();\n\n        return commentEntityService.getAllReplyByCommentId(cid, userRolesVO != null ? userRolesVO.getUid() : null,\n                isRoot, commentId);\n    }\n\n    @Override\n    public void addReply(ReplyDTO replyDTO) {\n\n        if (StrUtil.isEmpty(replyDTO.getReply().getContent().trim())) {\n            throw new StatusFailException(\"回复内容不能为空！\");\n        }\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        Reply reply = replyDTO.getReply();\n        reply.setFromAvatar(userRolesVO.getAvatar())\n                .setFromName(userRolesVO.getUsername())\n                .setFromUid(userRolesVO.getUid());\n\n        if (UserSessionUtil.isRoot()) {\n            reply.setFromRole(\"root\");\n        } else if (UserSessionUtil.isAdmin() || UserSessionUtil.isProblemAdmin()) {\n            reply.setFromRole(\"admin\");\n        } else {\n            reply.setFromRole(\"user\");\n        }\n        // 带有表情的字符串转换为编码\n        reply.setContent(EmojiUtil.toHtml(reply.getContent()));\n\n        boolean isOk = replyEntityService.saveOrUpdate(reply);\n\n        if (isOk) {\n            // 如果是讨论区的回复，发布成功需要增加统计该讨论的回复数\n            if (replyDTO.getDid() != null) {\n                UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();\n                discussionUpdateWrapper.eq(\"id\", replyDTO.getDid()).setSql(\"comment_num=comment_num+1\");\n                discussionEntityService.update(discussionUpdateWrapper);\n                // 更新消息\n                replyEntityService.updateReplyMsg(replyDTO.getDid(), \"Discussion\", reply.getContent(),\n                        replyDTO.getQuoteId(), replyDTO.getQuoteType(), reply.getToUid(), reply.getFromUid());\n            }\n        } else {\n            throw new StatusFailException(\"回复失败，请重新尝试！\");\n        }\n    }\n\n    @Override\n    public void deleteReply(ReplyDTO replyDTO) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        Reply reply = replyDTO.getReply();\n        // 如果不是评论本人 或者不是管理员 无权限删除该评论\n        if (reply.getFromUid().equals(userRolesVO.getUid()) || UserSessionUtil.isRoot()\n                || UserSessionUtil.isAdmin() || UserSessionUtil.isProblemAdmin()) {\n            // 删除该数据\n            boolean isOk = replyEntityService.removeById(reply.getId());\n            if (isOk) {\n                // 如果是讨论区的回复，删除成功需要减少统计该讨论的回复数\n                if (replyDTO.getDid() != null) {\n                    UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();\n                    discussionUpdateWrapper.eq(\"id\", replyDTO.getDid()).setSql(\"comment_num=comment_num-1\");\n                    discussionEntityService.update(discussionUpdateWrapper);\n                }\n            } else {\n                throw new StatusFailException(\"删除失败，请重新尝试\");\n            }\n        } else {\n            throw new StatusForbiddenException(\"无权删除该回复\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/CommonServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport cn.hutool.core.util.IdUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.problem.*;\nimport com.simplefanc.voj.backend.dao.training.TrainingCategoryEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.CaptchaVO;\nimport com.simplefanc.voj.backend.pojo.vo.ProblemTagVO;\nimport com.simplefanc.voj.backend.service.oj.CommonService;\nimport com.simplefanc.voj.common.constants.Constant;\nimport com.simplefanc.voj.common.pojo.entity.problem.*;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingCategory;\nimport com.wf.captcha.SpecCaptcha;\nimport com.wf.captcha.base.Captcha;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 16:28\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class CommonServiceImpl implements CommonService {\n\n    private final TagEntityService tagEntityService;\n\n    private final TagClassificationEntityService tagClassificationEntityService;\n\n    private final ProblemTagEntityService problemTagEntityService;\n\n    private final LanguageEntityService languageEntityService;\n\n    private final ProblemLanguageEntityService problemLanguageEntityService;\n\n    private final RedisUtil redisUtil;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final CodeTemplateEntityService codeTemplateEntityService;\n\n    private final TrainingCategoryEntityService trainingCategoryEntityService;\n\n    @Override\n    public CaptchaVO getCaptcha() {\n        SpecCaptcha specCaptcha = new SpecCaptcha(90, 30, 4);\n        specCaptcha.setCharType(Captcha.TYPE_DEFAULT);\n        String verCode = specCaptcha.text().toLowerCase();\n        String key = IdUtil.simpleUUID();\n        // 存入redis并设置过期时间为30分钟\n        redisUtil.set(key, verCode, 1800);\n        // 将key和base64返回给前端\n        CaptchaVO captchaVO = new CaptchaVO();\n        captchaVO.setImg(specCaptcha.toBase64());\n        captchaVO.setCaptchaKey(key);\n        return captchaVO;\n    }\n\n    @Override\n    public List<TrainingCategory> getTrainingCategory() {\n        return trainingCategoryEntityService.list();\n    }\n\n    @Override\n    public List<Tag> getAllProblemTagsList(String oj) {\n        List<Tag> tagList;\n        oj = oj.toUpperCase();\n        if (\"ALL\".equals(oj)) {\n            tagList = tagEntityService.list();\n        } else {\n            QueryWrapper<Tag> tagQueryWrapper = new QueryWrapper<>();\n            tagQueryWrapper.eq(\"oj\", oj);\n            tagList = tagEntityService.list(tagQueryWrapper);\n        }\n        return tagList;\n    }\n\n    @Override\n    public List<ProblemTagVO> getProblemTagsAndClassification(String oj) {\n        oj = oj.toUpperCase();\n        List<ProblemTagVO> problemTagVOList = new ArrayList<>();\n        List<TagClassification> classificationList = null;\n        List<Tag> tagList = null;\n        if (\"ALL\".equals(oj)) {\n            classificationList = tagClassificationEntityService.list();\n            QueryWrapper<Tag> tagQueryWrapper = new QueryWrapper<>();\n            tagList = tagEntityService.list(tagQueryWrapper);\n        } else {\n            QueryWrapper<TagClassification> tagClassificationQueryWrapper = new QueryWrapper<>();\n            tagClassificationQueryWrapper.eq(\"oj\", oj)\n                    .orderByAsc(\"`rank`\");\n            classificationList = tagClassificationEntityService.list(tagClassificationQueryWrapper);\n\n            QueryWrapper<Tag> tagQueryWrapper = new QueryWrapper<>();\n            tagQueryWrapper.eq(\"oj\", oj);\n            tagList = tagEntityService.list(tagQueryWrapper);\n        }\n        if (CollectionUtils.isEmpty(classificationList)) {\n            ProblemTagVO problemTagVO = new ProblemTagVO();\n            problemTagVO.setTagList(tagList);\n            problemTagVOList.add(problemTagVO);\n        } else {\n            for (TagClassification classification : classificationList) {\n                ProblemTagVO problemTagVO = new ProblemTagVO();\n                problemTagVO.setClassification(classification);\n                List<Tag> tags = new ArrayList<>();\n                if (!CollectionUtils.isEmpty(tagList)) {\n                    Iterator<Tag> it = tagList.iterator();\n                    while (it.hasNext()) {\n                        Tag tag = it.next();\n                        if (classification.getId().equals(tag.getTcid())) {\n                            tags.add(tag);\n                            it.remove();\n                        }\n                    }\n                }\n                problemTagVO.setTagList(tags);\n                problemTagVOList.add(problemTagVO);\n            }\n            if (tagList.size() > 0) {\n                ProblemTagVO problemTagVO = new ProblemTagVO();\n                problemTagVO.setTagList(tagList);\n                problemTagVOList.add(problemTagVO);\n            }\n        }\n\n        if (\"ALL\".equals(oj)) {\n            Collections.sort(problemTagVOList, problemTagVOComparator);\n        }\n        return problemTagVOList;\n    }\n\n    @Override\n    public Collection<Tag> getProblemTags(Long pid) {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"pid\", pid);\n        List<Long> tidList = problemTagEntityService.listByMap(map).stream().map(ProblemTag::getTid)\n                .collect(Collectors.toList());\n        return tagEntityService.listByIds(tidList);\n    }\n\n    @Override\n    public List<Language> getLanguages(Long pid, Boolean all) {\n        String oj = Constant.LOCAL;\n        if (pid != null) {\n            Problem problem = problemEntityService.getById(pid);\n            if (problem.getIsRemote()) {\n                oj = problem.getProblemId().split(\"-\")[0];\n            }\n        }\n\n        QueryWrapper<Language> queryWrapper = new QueryWrapper<>();\n        // 获取对应OJ支持的语言列表\n        queryWrapper.eq(all != null && !all, \"oj\", oj);\n        return languageEntityService.list(queryWrapper);\n    }\n\n    @Override\n    public Collection<Language> getProblemLanguages(Long pid) {\n        QueryWrapper<ProblemLanguage> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"pid\", pid).select(\"lid\");\n        List<Long> idList = problemLanguageEntityService.list(queryWrapper).stream().map(ProblemLanguage::getLid)\n                .collect(Collectors.toList());\n        return languageEntityService.listByIds(idList);\n\n    }\n\n    @Override\n    public List<CodeTemplate> getProblemCodeTemplate(Long pid) {\n        QueryWrapper<CodeTemplate> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"pid\", pid);\n        return codeTemplateEntityService.list(queryWrapper);\n    }\n\n    private Comparator<ProblemTagVO> problemTagVOComparator = (p1, p2) -> {\n        if (p1 == null) {\n            return 1;\n        }\n        if (p2 == null) {\n            return 1;\n        }\n        if (p1.getClassification() == null) {\n            return 1;\n        }\n        if (p2.getClassification() == null) {\n            return -1;\n        }\n        TagClassification p1Classification = p1.getClassification();\n        TagClassification p2Classification = p2.getClassification();\n        if (Objects.equals(p1Classification.getOj(), p2Classification.getOj())) {\n            return p1Classification.getRank().compareTo(p2Classification.getRank());\n        } else {\n            if (Constant.LOCAL.equals(p1Classification.getOj())) {\n                return -1;\n            } else if (Constant.LOCAL.equals(p2Classification.getOj())) {\n                return 1;\n            } else {\n                return p1Classification.getOj().compareTo(p2Classification.getOj());\n            }\n        }\n    };\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/ContestAdminServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestPrintEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestRecordEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.CheckAcDTO;\nimport com.simplefanc.voj.backend.service.oj.ContestAdminService;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestPrint;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 19:40\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class ContestAdminServiceImpl implements ContestAdminService {\n\n    private final ContestEntityService contestEntityService;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    private final ContestPrintEntityService contestPrintEntityService;\n\n    private final ContestValidator contestValidator;\n\n    @Override\n    public IPage<ContestRecord> getContestACInfo(Long cid, Integer currentPage, Integer limit) {\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(cid);\n\n        if (!contestValidator.isContestAdmin(contest)) {\n            throw new StatusForbiddenException(\"对不起，你无权查看！\");\n        }\n\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 30;\n        }\n\n        // 获取当前比赛的，状态为ac，未被校验的排在前面\n        return contestRecordEntityService.getACInfo(currentPage, limit, ContestEnum.RECORD_AC.getCode(), cid,\n                contest.getUid());\n\n    }\n\n    @Override\n    public void checkContestAcInfo(CheckAcDTO checkAcDTO) {\n\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(checkAcDTO.getCid());\n\n        if (!contestValidator.isContestAdmin(contest)) {\n            throw new StatusForbiddenException(\"对不起，你无权操作！\");\n        }\n\n        boolean isOk = contestRecordEntityService\n                .updateById(new ContestRecord().setChecked(checkAcDTO.getChecked()).setId(checkAcDTO.getId()));\n\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败！\");\n        }\n\n    }\n\n    @Override\n    public IPage<ContestPrint> getContestPrint(Long cid, Integer currentPage, Integer limit) {\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(cid);\n\n        if (!contestValidator.isContestAdmin(contest)) {\n            throw new StatusForbiddenException(\"对不起，你无权查看！\");\n        }\n\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 30;\n        }\n\n        // 获取当前比赛的，未被确定的排在签名\n\n        IPage<ContestPrint> contestPrintIPage = new Page<>(currentPage, limit);\n\n        QueryWrapper<ContestPrint> contestPrintQueryWrapper = new QueryWrapper<>();\n        contestPrintQueryWrapper.select(\"id\", \"cid\", \"username\", \"realname\", \"status\", \"gmt_create\").eq(\"cid\", cid)\n                .orderByAsc(\"status\").orderByDesc(\"gmt_create\");\n\n        return contestPrintEntityService.page(contestPrintIPage, contestPrintQueryWrapper);\n    }\n\n    @Override\n    public void checkContestPrintStatus(Long id, Long cid) {\n        Contest contest = contestEntityService.getById(cid);\n\n        if (!contestValidator.isContestAdmin(contest)) {\n            throw new StatusForbiddenException(\"对不起，你无权操作！\");\n        }\n\n        boolean isOk = contestPrintEntityService.updateById(new ContestPrint().setId(id).setStatus(1));\n\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败！\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/ContestScoreboardServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.exception.StatusNotFoundException;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestProblemEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.ContestRankDTO;\nimport com.simplefanc.voj.backend.pojo.vo.ContestOutsideInfo;\nimport com.simplefanc.voj.backend.pojo.vo.ContestVO;\nimport com.simplefanc.voj.backend.service.oj.ContestACMRankService;\nimport com.simplefanc.voj.backend.service.oj.ContestOIRankService;\nimport com.simplefanc.voj.backend.service.oj.ContestScoreboardService;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestProblem;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 20:02\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class ContestScoreboardServiceImpl implements ContestScoreboardService {\n\n    private final ContestEntityService contestEntityService;\n\n    private final ContestProblemEntityService contestProblemEntityService;\n\n    private final ContestValidator contestValidator;\n\n    private final ContestACMRankService contestACMRankService;\n\n    private final ContestOIRankService contestOIRankService;\n\n    @Override\n    public ContestOutsideInfo getContestOutsideInfo(Long cid) {\n\n        ContestVO contestInfo = contestEntityService.getContestInfoById(cid);\n\n        if (contestInfo == null) {\n            throw new StatusNotFoundException(\"访问错误：该比赛不存在！\");\n        }\n\n        if (!contestInfo.getOpenRank()) {\n            throw new StatusForbiddenException(\"本场比赛未开启外榜，禁止访问外榜！\");\n        }\n\n        // 获取本场比赛的状态\n        if (contestInfo.getStatus().equals(ContestEnum.STATUS_SCHEDULED.getCode())) {\n            throw new StatusForbiddenException(\"本场比赛正在筹备中，禁止访问外榜！\");\n        }\n\n        contestInfo.setNow(new Date());\n        ContestOutsideInfo contestOutsideInfo = new ContestOutsideInfo();\n        contestOutsideInfo.setContest(contestInfo);\n\n        QueryWrapper<ContestProblem> contestProblemQueryWrapper = new QueryWrapper<>();\n        contestProblemQueryWrapper.eq(\"cid\", cid).orderByAsc(\"display_id\");\n        List<ContestProblem> contestProblemList = contestProblemEntityService.list(contestProblemQueryWrapper);\n        contestOutsideInfo.setProblemList(contestProblemList);\n\n        return contestOutsideInfo;\n    }\n\n    @Override\n    public IPage getContestOutsideScoreboard(ContestRankDTO contestRankDTO) {\n        Long cid = contestRankDTO.getCid();\n        List<String> concernedList = contestRankDTO.getConcernedList();\n        Boolean removeStar = contestRankDTO.getRemoveStar();\n        Boolean forceRefresh = contestRankDTO.getForceRefresh();\n        Integer currentPage = contestRankDTO.getCurrentPage();\n        Integer limit = contestRankDTO.getLimit();\n\n        if (cid == null) {\n            throw new StatusFailException(\"错误：比赛id不能为空\");\n        }\n        if (removeStar == null) {\n            removeStar = false;\n        }\n        if (forceRefresh == null) {\n            forceRefresh = false;\n        }\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 30;\n        }\n\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(cid);\n\n        if (contest == null) {\n            throw new StatusFailException(\"访问错误：该比赛不存在！\");\n        }\n\n        if (!contest.getOpenRank()) {\n            throw new StatusForbiddenException(\"本场比赛未开启外榜，禁止访问外榜！\");\n        }\n\n        if (contest.getStatus().equals(ContestEnum.STATUS_SCHEDULED.getCode())) {\n            throw new StatusForbiddenException(\"本场比赛正在筹备中，禁止访问外榜！\");\n        }\n\n        // 不是比赛创建者或者超管无权限开启强制实时榜单\n        if (!contestValidator.isContestAdmin(contest)) {\n            forceRefresh = false;\n        }\n\n        // 校验该比赛是否开启了封榜模式，超级管理员和比赛创建者可以直接看到实际榜单\n        boolean isOpenSealRank = contestValidator.isOpenSealRank(contest, forceRefresh);\n        if (contest.getType().intValue() == ContestEnum.TYPE_ACM.getCode()) {\n            // 获取ACM比赛排行榜外榜\n            return contestACMRankService.getContestACMRankPage(contest, isOpenSealRank, removeStar, concernedList, contestRankDTO.getKeyword(),\n                    !forceRefresh,\n                    // 默认15s缓存\n                    15L, currentPage, limit);\n\n        } else {\n            // 获取OI比赛排行榜外榜\n            return contestOIRankService.getContestOIRankPage(contest, isOpenSealRank, removeStar, concernedList, contestRankDTO.getKeyword(),\n                    !forceRefresh,\n                    // 默认15s缓存\n                    15L, currentPage, limit);\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/ContestServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.exception.StatusNotFoundException;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.common.AnnouncementEntityService;\nimport com.simplefanc.voj.backend.dao.contest.*;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.problem.*;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.ContestPrintDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ContestRankDTO;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterContestDTO;\nimport com.simplefanc.voj.backend.pojo.dto.UserReadContestAnnouncementDTO;\nimport com.simplefanc.voj.backend.pojo.vo.*;\nimport com.simplefanc.voj.backend.service.oj.ContestACMRankService;\nimport com.simplefanc.voj.backend.service.oj.ContestOIRankService;\nimport com.simplefanc.voj.backend.service.oj.ContestService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.ProblemEnum;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.common.Announcement;\nimport com.simplefanc.voj.common.pojo.entity.contest.*;\nimport com.simplefanc.voj.common.pojo.entity.problem.*;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 22:26\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class ContestServiceImpl implements ContestService {\n\n    private final ContestEntityService contestEntityService;\n\n    private final ContestProblemEntityService contestProblemEntityService;\n\n    private final ContestAnnouncementEntityService contestAnnouncementEntityService;\n\n    private final AnnouncementEntityService announcementEntityService;\n\n    private final ContestRegisterEntityService contestRegisterEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final ProblemTagEntityService problemTagEntityService;\n\n    private final TagEntityService tagEntityService;\n\n    private final LanguageEntityService languageEntityService;\n\n    private final ProblemLanguageEntityService problemLanguageEntityService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final CodeTemplateEntityService codeTemplateEntityService;\n\n    private final ContestPrintEntityService contestPrintEntityService;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final RedisUtil redisUtil;\n\n    private final ContestValidator contestValidator;\n\n    private final ContestACMRankService contestACMRankService;\n\n    private final ContestOIRankService contestOIRankService;\n\n    @Override\n    public IPage<ContestVO> getContestList(Integer limit, Integer currentPage, Integer status, Integer type,\n                                           String keyword) {\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n        return contestEntityService.getContestList(limit, currentPage, type, status, keyword);\n    }\n\n    @Override\n    public ContestVO getContestInfo(Long cid) {\n\n        ContestVO contestInfo = contestEntityService.getContestInfoById(cid);\n        if (contestInfo == null) {\n            throw new StatusFailException(\"对不起，该比赛不存在!\");\n        }\n        // 设置当前服务器系统时间\n        contestInfo.setNow(new Date());\n\n        return contestInfo;\n    }\n\n    @Override\n    public void toRegisterContest(RegisterContestDTO registerContestDTO) {\n\n        Long cid = registerContestDTO.getCid();\n        String password = registerContestDTO.getPassword();\n        if (cid == null) {\n            throw new StatusFailException(\"cid不能为空！\");\n        }\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        Contest contest = contestEntityService.getById(cid);\n\n        if (!contestValidator.checkVisible(contest)) {\n            throw new StatusFailException(\"对不起，该比赛不存在!\");\n        }\n\n        // 密码不对\n        if (contest.getOpenPwdLimit() && !contest.getPwd().equals(password)) {\n            throw new StatusFailException(\"比赛密码错误，请重新输入！\");\n        }\n\n        // 需要校验当前比赛是否开启账号规则限制，如果有，需要对当前用户的用户名进行验证\n        if (contest.getOpenAccountLimit()\n                && !contestValidator.validateAccountRule(contest.getAccountLimitRule(), userRolesVO.getUsername())) {\n            throw new StatusFailException(\"对不起！本次比赛只允许特定账号规则的用户参赛！\");\n        }\n\n        QueryWrapper<ContestRegister> wrapper = new QueryWrapper<ContestRegister>().eq(\"cid\", cid).eq(\"uid\",\n                userRolesVO.getUid());\n        if (contestRegisterEntityService.getOne(wrapper, false) != null) {\n            throw new StatusFailException(\"您已注册过该比赛，请勿重复注册！\");\n        }\n\n        boolean isOk = contestRegisterEntityService\n                .saveOrUpdate(new ContestRegister().setCid(cid).setUid(userRolesVO.getUid()));\n\n        if (!isOk) {\n            throw new StatusFailException(\"校验比赛权限失败，请稍后再试\");\n        }\n    }\n\n    @Override\n    public AccessVO getContestAccess(Long cid) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        ContestRegister contestRegister = contestRegisterEntityService.getOne(\n                new QueryWrapper<ContestRegister>()\n                        .eq(\"cid\", cid)\n                        .eq(\"uid\", userRolesVO.getUid()), false);\n\n        Contest contest = contestEntityService.getById(cid);\n        if (!contestValidator.checkVisible(contest)) {\n            throw new StatusFailException(\"对不起，该比赛不存在!\");\n        }\n\n        boolean access = false;\n        if (contestRegister != null) {\n            access = true;\n            if (contest.getOpenAccountLimit()\n                    && !contestValidator.validateAccountRule(contest.getAccountLimitRule(), userRolesVO.getUsername())) {\n                access = false;\n                contestRegisterEntityService.removeById(contestRegister.getId());\n            }\n        } else {\n            if (contest.getOpenAccountLimit()\n                    && contestValidator.validateAccountRule(contest.getAccountLimitRule(), userRolesVO.getUsername())) {\n                access = true;\n\n                boolean isOk = contestRegisterEntityService\n                        .save(new ContestRegister().setCid(cid).setUid(userRolesVO.getUid()));\n\n                if (!isOk) {\n                    throw new StatusFailException(\"校验比赛权限失败，请稍后再试\");\n                }\n            }\n        }\n\n        AccessVO accessVO = new AccessVO();\n        accessVO.setAccess(access);\n        return accessVO;\n    }\n\n    @Override\n    public List<ContestProblemVO> getContestProblem(Long cid) {\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(cid);\n\n        // 需要对该比赛做判断，是否处于开始或结束状态才可以获取题目列表，同时若是私有赛需要判断是否已注册（比赛管理员包括超级管理员可以直接获取）\n        contestValidator.validateContestAuth(contest);\n\n        boolean isAdmin = contestValidator.isContestAdmin(contest);\n        // 如果比赛开启封榜\n        if (contestValidator.isOpenSealRank(contest, true)) {\n            return contestProblemEntityService.getContestProblemList(cid, contest.getStartTime(),\n                    contest.getEndTime(), contest.getSealRankTime(), isAdmin, contest.getAuthor())\n                    .stream().sorted().collect(Collectors.toList());\n        }\n        return contestProblemEntityService.getContestProblemList(cid, contest.getStartTime(),\n                contest.getEndTime(), null, isAdmin, contest.getAuthor())\n                .stream().sorted().collect(Collectors.toList());\n    }\n\n    @Override\n    // TODO 行数过多\n    public ProblemInfoVO getContestProblemDetails(Long cid, String displayId) {\n\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(cid);\n        contestValidator.validateContestAuth(contest);\n\n        // 根据cid和displayId获取pid\n        QueryWrapper<ContestProblem> contestProblemQueryWrapper = new QueryWrapper<>();\n        contestProblemQueryWrapper.eq(\"cid\", cid).eq(\"display_id\", displayId);\n        ContestProblem contestProblem = contestProblemEntityService.getOne(contestProblemQueryWrapper);\n\n        if (contestProblem == null) {\n            throw new StatusNotFoundException(\"该比赛题目不存在\");\n        }\n\n        // 查询题目详情，题目标签，题目语言，题目做题情况\n        Problem problem = problemEntityService.getById(contestProblem.getPid());\n\n        if (problem.getAuth().equals(ProblemEnum.AUTH_PRIVATE.getCode())) {\n            throw new StatusForbiddenException(\"该比赛题目当前不可访问！\");\n        }\n\n        // 设置比赛题目的标题为设置展示标题\n        problem.setTitle(contestProblem.getDisplayTitle());\n\n        List<Tag> tags = new LinkedList<>();\n\n        // 比赛结束后才开放标签和source、出题人、难度\n        if (contest.getStatus().intValue() != ContestEnum.STATUS_ENDED.getCode()) {\n            problem.setSource(null);\n            problem.setAuthor(null);\n            problem.setDifficulty(null);\n            QueryWrapper<ProblemTag> problemTagQueryWrapper = new QueryWrapper<>();\n            problemTagQueryWrapper.eq(\"pid\", contestProblem.getPid());\n            // 获取该题号对应的标签id\n            List<Long> tidList = new LinkedList<>();\n            problemTagEntityService.list(problemTagQueryWrapper).forEach(problemTag -> {\n                tidList.add(problemTag.getTid());\n            });\n            if (!tidList.isEmpty()) {\n                tags = (List<Tag>) tagEntityService.listByIds(tidList);\n            }\n        }\n        // 记录 languageId对应的name\n        HashMap<Long, String> tmpMap = new HashMap<>();\n\n        // 获取题目提交的代码支持的语言\n        List<String> languagesStr = new LinkedList<>();\n        QueryWrapper<ProblemLanguage> problemLanguageQueryWrapper = new QueryWrapper<>();\n        problemLanguageQueryWrapper.eq(\"pid\", contestProblem.getPid()).select(\"lid\");\n        List<Long> lidList = problemLanguageEntityService.list(problemLanguageQueryWrapper).stream()\n                .map(ProblemLanguage::getLid).collect(Collectors.toList());\n        languageEntityService.listByIds(lidList).forEach(language -> {\n            languagesStr.add(language.getName());\n            tmpMap.put(language.getId(), language.getName());\n        });\n\n        Date sealRankTime = null;\n        // 封榜时间除超级管理员和比赛管理员外 其它人不可看到最新数据\n        if (contestValidator.isOpenSealRank(contest, true)) {\n            sealRankTime = contest.getSealRankTime();\n        }\n\n        // 筛去 比赛管理员和超级管理员的提交\n        List<String> superAdminUidList = userInfoEntityService.getSuperAdminUidList();\n        superAdminUidList.add(contest.getUid());\n\n        // 获取题目的提交记录\n        ProblemCountVO problemCount = judgeEntityService.getContestProblemCount(contestProblem.getPid(),\n                contestProblem.getId(), contestProblem.getCid(), contest.getStartTime(), sealRankTime,\n                superAdminUidList);\n\n        // 获取题目的代码模板\n        QueryWrapper<CodeTemplate> codeTemplateQueryWrapper = new QueryWrapper<>();\n        codeTemplateQueryWrapper.eq(\"pid\", problem.getId()).eq(\"status\", true);\n        List<CodeTemplate> codeTemplates = codeTemplateEntityService.list(codeTemplateQueryWrapper);\n        HashMap<String, String> langNameAndCode = new HashMap<>();\n        if (!codeTemplates.isEmpty()) {\n            for (CodeTemplate codeTemplate : codeTemplates) {\n                langNameAndCode.put(tmpMap.get(codeTemplate.getLid()), codeTemplate.getCode());\n            }\n        }\n        // 将数据统一写入到一个VO返回数据实体类中\n        return new ProblemInfoVO(problem, tags, languagesStr, problemCount, langNameAndCode);\n    }\n\n    // TODO 参数过多\n    @Override\n    public IPage<JudgeVO> getContestSubmissionList(Integer limit, Integer currentPage, Boolean onlyMine,\n                                                   String displayId, Integer searchStatus, String searchUsername, Long searchCid, Boolean beforeContestSubmit,\n                                                   Boolean completeProblemId) {\n\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(searchCid);\n        contestValidator.validateContestAuth(contest);\n\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 30;\n        }\n\n        String uid = null;\n        // 只查看当前用户的提交\n        final String userId = UserSessionUtil.getUserInfo().getUid();\n        if (onlyMine) {\n            uid = userId;\n        }\n\n        String contestType;\n        if (contest.getType().intValue() == ContestEnum.TYPE_ACM.getCode()) {\n            contestType = ContestEnum.TYPE_ACM.getName();\n        } else {\n            contestType = ContestEnum.TYPE_OI.getName();\n        }\n\n        Date sealRankTime = null;\n        // 需要判断是否需要封榜\n        if (contestValidator.isOpenSealRank(contest, true)) {\n            sealRankTime = contest.getSealRankTime();\n        }\n\n        // OI比赛封榜期间不更新; ACM比赛封榜期间可看到自己的提交(sealTimeUid)，但是其它人的不可见\n        IPage<JudgeVO> contestJudgeList = judgeEntityService.getContestJudgeList(limit, currentPage, displayId,\n                searchCid, searchStatus, searchUsername, uid, beforeContestSubmit, contestType, contest.getStartTime(),\n                sealRankTime, userId, completeProblemId);\n\n        // 未查询到一条数据\n        if (contestJudgeList.getTotal() == 0) {\n            return contestJudgeList;\n        }\n        // 比赛还是进行阶段，同时不是超级管理员与比赛管理员，需要将除自己之外的提交的时间、空间、长度隐藏\n        if (contest.getStatus().intValue() == ContestEnum.STATUS_RUNNING.getCode() && !contestValidator.isContestAdmin(contest)) {\n            contestJudgeList.getRecords().forEach(judgeVO -> {\n                if (!judgeVO.getUid().equals(userId)) {\n                    judgeVO.setTime(null);\n                    judgeVO.setMemory(null);\n                    judgeVO.setLength(null);\n                }\n            });\n        }\n        return contestJudgeList;\n    }\n\n    @Override\n    public IPage getContestRank(ContestRankDTO contestRankDTO) {\n\n        Long cid = contestRankDTO.getCid();\n        List<String> concernedList = contestRankDTO.getConcernedList();\n        Integer currentPage = contestRankDTO.getCurrentPage();\n        Integer limit = contestRankDTO.getLimit();\n        Boolean removeStarUser = contestRankDTO.getRemoveStar();\n        Boolean forceRefresh = contestRankDTO.getForceRefresh();\n        final String keyword = contestRankDTO.getKeyword();\n\n        if (cid == null) {\n            throw new StatusFailException(\"错误：cid不能为空\");\n        }\n        if (removeStarUser == null) {\n            removeStarUser = false;\n        }\n        if (forceRefresh == null) {\n            forceRefresh = false;\n        }\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 30;\n        }\n\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(contestRankDTO.getCid());\n        contestValidator.validateContestAuth(contest);\n\n        // 校验该比赛是否开启了封榜模式，超级管理员和比赛创建者可以直接看到实际榜单\n        boolean isOpenSealRank = contestValidator.isOpenSealRank(contest, forceRefresh);\n\n        if (contest.getType().intValue() == ContestEnum.TYPE_ACM.getCode()) {\n            // ACM比赛\n            // 进行排行榜计算以及排名分页\n            return contestACMRankService.getContestACMRankPage(contest, isOpenSealRank, removeStarUser,\n                    concernedList, keyword, false, null, currentPage, limit);\n\n        } else {\n            // OI比赛\n            return contestOIRankService.getContestOIRankPage(contest, isOpenSealRank, removeStarUser,\n                    concernedList, keyword, false, null, currentPage, limit);\n        }\n    }\n\n    @Override\n    public Set<String> getContestAdminUidList(Contest contest) {\n        List<String> contestAdminUidList = userInfoEntityService.getSuperAdminUidList();\n        contestAdminUidList.add(contest.getUid());\n        return new HashSet<>(contestAdminUidList);\n    }\n\n    @Override\n    public IPage<AnnouncementVO> getContestAnnouncement(Long cid, Integer limit, Integer currentPage) {\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(cid);\n\n        // 需要对该比赛做判断，是否处于开始或结束状态才可以获取题目，同时若是私有赛需要判断是否已注册（比赛管理员包括超级管理员可以直接获取）\n        contestValidator.validateContestAuth(contest);\n\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n\n        return announcementEntityService.getContestAnnouncement(cid, true, limit, currentPage);\n    }\n\n    @Override\n    public List<Announcement> getContestUserNotReadAnnouncement(\n            UserReadContestAnnouncementDTO userReadContestAnnouncementDTO) {\n\n        Long cid = userReadContestAnnouncementDTO.getCid();\n        List<Long> readAnnouncementList = userReadContestAnnouncementDTO.getReadAnnouncementList();\n\n        QueryWrapper<ContestAnnouncement> contestAnnouncementQueryWrapper = new QueryWrapper<>();\n        contestAnnouncementQueryWrapper.eq(\"cid\", cid);\n        if (readAnnouncementList != null && readAnnouncementList.size() > 0) {\n            contestAnnouncementQueryWrapper.notIn(\"aid\", readAnnouncementList);\n        }\n        List<ContestAnnouncement> announcementList = contestAnnouncementEntityService\n                .list(contestAnnouncementQueryWrapper);\n\n        List<Long> aidList = announcementList.stream()\n                .map(ContestAnnouncement::getAid)\n                .collect(Collectors.toList());\n\n        if (aidList.size() > 0) {\n            QueryWrapper<Announcement> announcementQueryWrapper = new QueryWrapper<>();\n            announcementQueryWrapper.in(\"id\", aidList).orderByDesc(\"gmt_create\");\n            return announcementEntityService.list(announcementQueryWrapper);\n        } else {\n            return new ArrayList<>();\n        }\n\n    }\n\n    @Override\n    public void submitPrintText(ContestPrintDTO contestPrintDTO) {\n\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        // 获取本场比赛的状态\n        Contest contest = contestEntityService.getById(contestPrintDTO.getCid());\n        contestValidator.validateContestAuth(contest);\n\n        String lockKey = RedisConstant.CONTEST_ADD_PRINT_LOCK + userRolesVO.getUid();\n        if (redisUtil.hasKey(lockKey)) {\n            long expire = redisUtil.getExpire(lockKey);\n            throw new StatusForbiddenException(\"提交打印功能限制，请在\" + expire + \"秒后再进行提交！\");\n        } else {\n            redisUtil.set(lockKey, 1, 30);\n        }\n\n        boolean isOk = contestPrintEntityService.saveOrUpdate(\n                new ContestPrint().setCid(contestPrintDTO.getCid()).setContent(contestPrintDTO.getContent())\n                        .setUsername(userRolesVO.getUsername()).setRealname(userRolesVO.getRealname()));\n\n        if (!isOk) {\n            throw new StatusFailException(\"提交失败\");\n        }\n\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/DiscussionServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.exception.StatusNotFoundException;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionLikeEntityService;\nimport com.simplefanc.voj.backend.dao.discussion.DiscussionReportEntityService;\nimport com.simplefanc.voj.backend.dao.problem.CategoryEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserAcproblemEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.DiscussionVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.oj.DiscussionService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.discussion.Discussion;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionLike;\nimport com.simplefanc.voj.common.pojo.entity.discussion.DiscussionReport;\nimport com.simplefanc.voj.common.pojo.entity.problem.Category;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 15:21\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class DiscussionServiceImpl implements DiscussionService {\n\n    private final DiscussionEntityService discussionEntityService;\n\n    private final DiscussionLikeEntityService discussionLikeEntityService;\n\n    private final CategoryEntityService categoryEntityService;\n\n    private final DiscussionReportEntityService discussionReportEntityService;\n\n    private final RedisUtil redisUtil;\n\n    private final UserAcproblemEntityService userAcproblemEntityService;\n\n    @Override\n    public IPage<Discussion> getDiscussionList(Integer limit, Integer currentPage, Integer categoryId, String pid,\n                                               Boolean onlyMine, String keyword, Boolean admin) {\n\n        QueryWrapper<Discussion> discussionQueryWrapper = new QueryWrapper<>();\n\n        IPage<Discussion> iPage = new Page<>(currentPage, limit);\n\n        if (categoryId != null) {\n            discussionQueryWrapper.eq(\"category_id\", categoryId);\n        }\n\n        if (StrUtil.isNotEmpty(keyword)) {\n\n            final String key = keyword.trim();\n\n            discussionQueryWrapper.and(wrapper -> wrapper.like(\"title\", key).or().like(\"author\", key).or()\n                    .like(\"id\", key).or().like(\"description\", key));\n        }\n\n        if (StrUtil.isNotEmpty(pid)) {\n            discussionQueryWrapper.eq(\"pid\", pid);\n        }\n\n        boolean isAdmin = UserSessionUtil.isRoot()\n                || UserSessionUtil.isProblemAdmin() || UserSessionUtil.isAdmin();\n        discussionQueryWrapper.eq(!(admin && isAdmin), \"status\", 0).orderByDesc(\"top_priority\")\n                .orderByDesc(\"gmt_create\").orderByDesc(\"like_num\").orderByDesc(\"view_num\");\n\n        if (onlyMine) {\n            UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n            discussionQueryWrapper.eq(\"uid\", userRolesVO.getUid());\n        }\n\n        return discussionEntityService.page(iPage, discussionQueryWrapper);\n    }\n\n    @Override\n    public DiscussionVO getDiscussion(Integer did) {\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        String uid = null;\n\n        if (userRolesVO != null) {\n            uid = userRolesVO.getUid();\n        }\n\n        DiscussionVO discussion = discussionEntityService.getDiscussion(did, uid);\n\n        if (discussion == null) {\n            throw new StatusNotFoundException(\"对不起，该讨论不存在！\");\n        }\n\n        if (discussion.getStatus() == 1) {\n            throw new StatusForbiddenException(\"对不起，该讨论已被封禁！\");\n        }\n\n        // 浏览量+1\n        UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();\n        discussionUpdateWrapper.setSql(\"view_num=view_num+1\").eq(\"id\", discussion.getId());\n        discussionEntityService.update(discussionUpdateWrapper);\n        discussion.setViewNum(discussion.getViewNum() + 1);\n\n        return discussion;\n    }\n\n    @Override\n    public void addDiscussion(Discussion discussion) {\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        // 除管理员外 其它用户需要AC20道题目以上才可发帖，同时限制一天只能发帖5次\n        if (!UserSessionUtil.isRoot() && !UserSessionUtil.isAdmin()\n                && !UserSessionUtil.isProblemAdmin()) {\n\n            QueryWrapper<UserAcproblem> queryWrapper = new QueryWrapper<>();\n            queryWrapper.eq(\"uid\", userRolesVO.getUid()).select(\"distinct pid\");\n            int userAcProblemCount = userAcproblemEntityService.count(queryWrapper);\n\n            if (userAcProblemCount < 20) {\n                throw new StatusForbiddenException(\"对不起，您暂时无权限发帖！请先去提交题目通过20道以上!\");\n            }\n\n            String lockKey = RedisConstant.DISCUSSION_ADD_NUM_LOCK + userRolesVO.getUid();\n            Integer num = redisUtil.get(lockKey, Integer.class);\n            if (num == null) {\n                redisUtil.set(lockKey, 1, 3600 * 24);\n            } else if (num >= 5) {\n                throw new StatusForbiddenException(\"对不起，您今天发帖次数已超过5次，已被限制！\");\n            } else {\n                redisUtil.incr(lockKey, 1);\n            }\n        }\n\n        discussion.setAuthor(userRolesVO.getUsername()).setAvatar(userRolesVO.getAvatar()).setUid(userRolesVO.getUid());\n\n        if (UserSessionUtil.isRoot()) {\n            discussion.setRole(\"root\");\n        } else if (UserSessionUtil.isAdmin() || UserSessionUtil.isProblemAdmin()) {\n            discussion.setRole(\"admin\");\n        } else {\n            // 如果不是管理员角色，一律重置为不置顶\n            discussion.setTopPriority(false);\n        }\n\n        boolean isOk = discussionEntityService.saveOrUpdate(discussion);\n        if (!isOk) {\n            throw new StatusFailException(\"发布失败，请重新尝试！\");\n        }\n    }\n\n    @Override\n    public void updateDiscussion(Discussion discussion) {\n        boolean isOk = discussionEntityService.updateById(discussion);\n        if (!isOk) {\n            throw new StatusFailException(\"修改失败\");\n        }\n    }\n\n    @Override\n    public void removeDiscussion(Integer did) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<Discussion>().eq(\"id\", did);\n        // 如果不是是管理员,则需要附加当前用户的uid条件\n        if (!UserSessionUtil.isRoot() && !UserSessionUtil.isAdmin()\n                && !UserSessionUtil.isProblemAdmin()) {\n            discussionUpdateWrapper.eq(\"uid\", userRolesVO.getUid());\n        }\n        boolean isOk = discussionEntityService.remove(discussionUpdateWrapper);\n        if (!isOk) {\n            throw new StatusFailException(\"删除失败，无权限或者该讨论不存在\");\n        }\n\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addDiscussionLike(Integer did, Boolean toLike) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        QueryWrapper<DiscussionLike> discussionLikeQueryWrapper = new QueryWrapper<>();\n        discussionLikeQueryWrapper.eq(\"did\", did).eq(\"uid\", userRolesVO.getUid());\n\n        DiscussionLike discussionLike = discussionLikeEntityService.getOne(discussionLikeQueryWrapper, false);\n\n        // 添加点赞\n        if (toLike) {\n            // 如果不存在就添加\n            if (discussionLike == null) {\n                boolean isSave = discussionLikeEntityService\n                        .saveOrUpdate(new DiscussionLike().setUid(userRolesVO.getUid()).setDid(did));\n                if (!isSave) {\n                    throw new StatusFailException(\"点赞失败，请重试尝试！\");\n                }\n            }\n            // 点赞+1\n            Discussion discussion = discussionEntityService.getById(did);\n            if (discussion != null) {\n                discussion.setLikeNum(discussion.getLikeNum() + 1);\n                discussionEntityService.updateById(discussion);\n                // 更新点赞消息\n                discussionEntityService.updatePostLikeMsg(discussion.getUid(), userRolesVO.getUid(), did);\n            }\n        } else {\n            // 取消点赞\n            // 如果存在就删除\n            if (discussionLike != null) {\n                boolean isDelete = discussionLikeEntityService.removeById(discussionLike.getId());\n                if (!isDelete) {\n                    throw new StatusFailException(\"取消点赞失败，请重试尝试！\");\n                }\n            }\n            // 点赞-1\n            UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();\n            discussionUpdateWrapper.setSql(\"like_num=like_num-1\").eq(\"id\", did);\n            discussionEntityService.update(discussionUpdateWrapper);\n        }\n\n    }\n\n    @Override\n    public List<Category> getDiscussionCategory() {\n        return categoryEntityService.list();\n    }\n\n    @Override\n    public void addDiscussionReport(DiscussionReport discussionReport) {\n        boolean isOk = discussionReportEntityService.saveOrUpdate(discussionReport);\n        if (!isOk) {\n            throw new StatusFailException(\"举报失败，请重新尝试\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/HomeServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.common.constants.ScheduleConstant;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.common.AnnouncementEntityService;\nimport com.simplefanc.voj.backend.dao.common.FileEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.service.admin.user.UserRecordService;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.pojo.vo.ACMRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.AnnouncementVO;\nimport com.simplefanc.voj.backend.pojo.vo.ContestVO;\nimport com.simplefanc.voj.backend.service.oj.HomeService;\nimport com.simplefanc.voj.common.pojo.entity.common.File;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 21:00\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class HomeServiceImpl implements HomeService {\n\n    private final ContestEntityService contestEntityService;\n\n    private final AnnouncementEntityService announcementEntityService;\n\n    private final UserRecordService userRecordService;\n\n    private final RedisUtil redisUtil;\n\n    private final FileEntityService fileEntityService;\n\n    private final FilePathProperties filePathProps;\n\n    /**\n     * @MethodName getRecentContest\n     * @Params * @param null\n     * @Description 获取最近14天的比赛信息列表\n     * @Return CommonResult\n     * @Since 2021/12/29\n     */\n    @Override\n    public List<ContestVO> getRecentContest() {\n        return contestEntityService.getWithinNext14DaysContests();\n    }\n\n    /**\n     * @MethodName getHomeCarousel\n     * @Params\n     * @Description 获取主页轮播图\n     * @Return\n     * @Since 2021/9/4\n     */\n    @Override\n    public List<HashMap<String, Object>> getHomeCarousel() {\n        List<File> fileList = fileEntityService.queryCarouselFileList();\n        return fileList.stream().map(f -> {\n            // TODO\n            HashMap<String, Object> param = new HashMap<>(2);\n            param.put(\"id\", f.getId());\n            param.put(\"url\", filePathProps.getImgApi() + f.getName());\n            return param;\n        }).collect(Collectors.toList());\n    }\n\n    /**\n     * @MethodName getRecentSevenACRank\n     * @Params * @param null\n     * @Description 获取最近7天用户做题榜单\n     * @Return\n     * @Since 2021/1/15\n     */\n    @Override\n    public List<ACMRankVO> getRecentSevenACRank() {\n        return userRecordService.getRecent7ACRank();\n    }\n\n    /**\n     * @MethodName getRecentOtherContest\n     * @Params * @param null\n     * @Description 获取最近其他OJ的比赛信息列表\n     * @Return CommonResult\n     * @Since 2021/1/15\n     */\n    @Override\n    public List<HashMap<String, Object>> getRecentOtherContest() {\n        String redisKey = ScheduleConstant.RECENT_OTHER_CONTEST;\n        // 从redis获取比赛列表\n        return (ArrayList<HashMap<String, Object>>) redisUtil.get(redisKey);\n    }\n\n    /**\n     * @MethodName getCommonAnnouncement\n     * @Params * @param null\n     * @Description 获取主页公告列表\n     * @Return CommonResult\n     * @Since 2021/12/29\n     */\n    @Override\n    public IPage<AnnouncementVO> getCommonAnnouncement(Integer limit, Integer currentPage) {\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n        return announcementEntityService.getAnnouncementList(limit, currentPage, true);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/JudgeServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.simplefanc.voj.backend.common.exception.StatusAccessDeniedException;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.exception.StatusNotFoundException;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.contest.ContestRecordEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeCaseEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.problem.ProblemEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserAcproblemEntityService;\nimport com.simplefanc.voj.backend.judge.local.JudgeTaskDispatcher;\nimport com.simplefanc.voj.backend.judge.remote.RemoteJudgeTaskDispatcher;\nimport com.simplefanc.voj.backend.pojo.dto.SubmitIdListDTO;\nimport com.simplefanc.voj.backend.pojo.dto.ToJudgeDTO;\nimport com.simplefanc.voj.backend.config.ConfigVO;\nimport com.simplefanc.voj.backend.pojo.vo.JudgeVO;\nimport com.simplefanc.voj.backend.pojo.vo.SubmissionInfoVO;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.service.oj.BeforeDispatchInitService;\nimport com.simplefanc.voj.backend.service.oj.JudgeService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.backend.validator.JudgeValidator;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport com.simplefanc.voj.common.utils.IpUtil;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 11:12\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class JudgeServiceImpl implements JudgeService {\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final JudgeCaseEntityService judgeCaseEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final ContestEntityService contestEntityService;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    private final UserAcproblemEntityService userAcproblemEntityService;\n\n    private final JudgeTaskDispatcher judgeTaskDispatcher;\n\n    private final RemoteJudgeTaskDispatcher remoteJudgeTaskDispatcher;\n\n    private final RedisUtil redisUtil;\n\n    private final JudgeValidator judgeValidator;\n\n    private final ContestValidator contestValidator;\n\n    private final BeforeDispatchInitService beforeDispatchInitService;\n\n    private final ConfigVO configVO;\n\n    /**\n     * @MethodName submitProblemJudge\n     * @Description 核心方法\n     * @Since 2021/10/30\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Judge submitProblemJudge(ToJudgeDTO judgeDTO) {\n\n        judgeValidator.validateSubmissionInfo(judgeDTO);\n\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        boolean isContestSubmission = judgeDTO.getCid() != 0;\n\n        boolean isTrainingSubmission = judgeDTO.getTid() != null && judgeDTO.getTid() != 0;\n\n        // 非比赛提交有限制\n        if (!isContestSubmission && configVO.getDefaultSubmitInterval() > 0) {\n            String lockKey = RedisConstant.SUBMIT_NON_CONTEST_LOCK + userRolesVO.getUid();\n            long count = redisUtil.incr(lockKey, 1);\n            if (count > 1) {\n                throw new StatusForbiddenException(\"对不起，您的提交频率过快，请稍后再尝试！\");\n            }\n            redisUtil.expire(lockKey, configVO.getDefaultSubmitInterval());\n        }\n\n        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes()))\n                .getRequest();\n        // 将提交先写入数据库，准备调用判题服务器\n        Judge judge = new Judge();\n        // 默认设置代码为单独自己可见\n        judge.setShare(false).setCode(judgeDTO.getCode()).setCid(judgeDTO.getCid()).setLanguage(judgeDTO.getLanguage())\n                .setLength(judgeDTO.getCode().length()).setUid(userRolesVO.getUid())\n                .setUsername(userRolesVO.getUsername())\n                // 开始进入判题队列\n                .setStatus(JudgeStatus.STATUS_PENDING.getStatus()).setSubmitTime(new Date()).setVersion(0)\n                .setIp(IpUtil.getUserIpAddr(request));\n\n        // 如果比赛id不等于0，则说明为比赛提交\n        if (isContestSubmission) {\n            beforeDispatchInitService.initContestSubmission(judgeDTO.getCid(), judgeDTO.getPid(), judge);\n        } else if (isTrainingSubmission) {\n            beforeDispatchInitService.initTrainingSubmission(judgeDTO.getTid(), judgeDTO.getPid(), judge);\n        } else { // 如果不是比赛提交和训练提交\n            beforeDispatchInitService.initCommonSubmission(judgeDTO.getPid(), judge);\n        }\n\n        // 将提交加入任务队列\n        if (judgeDTO.getIsRemote()) {\n            // 如果是远程oj判题\n            final String remoteJudgeProblem = judge.getDisplayPid();\n            remoteJudgeTaskDispatcher.sendTask(judge, remoteJudgeProblem, isContestSubmission);\n        } else {\n            judgeTaskDispatcher.sendTask(judge, isContestSubmission);\n        }\n\n        return judge;\n    }\n\n    /**\n     * @MethodName resubmit\n     * @Description 调用判题服务器提交失败超过60s后，用户点击按钮重新提交判题进入的方法\n     * @Since 2021/2/12\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Judge resubmit(Long submitId) {\n\n        Judge judge = judgeEntityService.getById(submitId);\n        if (judge == null) {\n            throw new StatusNotFoundException(\"此提交数据不存在！\");\n        }\n\n        Problem problem = problemEntityService.getById(judge.getPid());\n\n        // 如果是非比赛题目\n        if (judge.getCid() == 0) {\n            // 重判前，需要将该题目对应记录表一并更新\n            // 如果该题已经是AC通过状态，更新该题目的用户ac做题表 user_acproblem\n            if (judge.getStatus().intValue() == JudgeStatus.STATUS_ACCEPTED.getStatus().intValue()) {\n                QueryWrapper<UserAcproblem> userAcproblemQueryWrapper = new QueryWrapper<>();\n                userAcproblemQueryWrapper.eq(\"submit_id\", judge.getSubmitId());\n                userAcproblemEntityService.remove(userAcproblemQueryWrapper);\n            }\n        } else {\n            if (problem.getIsRemote()) {\n                // 将对应比赛记录设置成默认值\n                UpdateWrapper<ContestRecord> updateWrapper = new UpdateWrapper<>();\n                updateWrapper.eq(\"submit_id\", submitId).setSql(\"status=null,score=null\");\n                contestRecordEntityService.update(updateWrapper);\n            } else {\n                throw new StatusNotFoundException(\"错误！非vJudge题目在比赛过程无权限重新提交\");\n            }\n        }\n\n        // 重新进入等待队列\n        judge.setStatus(JudgeStatus.STATUS_PENDING.getStatus());\n        judge.setVersion(judge.getVersion() + 1);\n        judge.setErrorMessage(null).setOiRankScore(null).setScore(null).setTime(null).setJudger(\"\").setMemory(null);\n        judgeEntityService.updateById(judge);\n\n        // 将提交加入任务队列\n        if (problem.getIsRemote()) {\n            // 如果是远程oj判题\n            remoteJudgeTaskDispatcher.sendTask(judge, problem.getProblemId(), judge.getCid() != 0);\n        } else {\n            judgeTaskDispatcher.sendTask(judge, judge.getCid() != 0);\n        }\n        return judge;\n    }\n\n    /**\n     * @MethodName getSubmission\n     * @Description 获取单个提交记录的详情\n     * @Since 2021/1/2\n     */\n    @Override\n    public SubmissionInfoVO getSubmission(Long submitId) {\n        Judge judge = judgeEntityService.getById(submitId);\n        if (judge == null) {\n            throw new StatusNotFoundException(\"此提交数据不存在！\");\n        }\n\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        if (userRolesVO == null) {\n            throw new StatusAccessDeniedException(\"请先登录！\");\n        }\n\n        // 是否为超级管理员\n        boolean isRoot = UserSessionUtil.isRoot();\n        // 是否为题目管理员\n        boolean problemAdmin = UserSessionUtil.isProblemAdmin();\n        // 限制：后台配置的时间 之前的代码 都不能查看\n        if (!isRoot && !problemAdmin && judge.getSubmitTime().getTime() < configVO.getCodeVisibleStartTime()) {\n            throw new StatusNotFoundException(\"此提交数据当前时间无法查看！\");\n        }\n        // 清空vj信息\n        judge.setVjudgeUsername(null);\n        judge.setVjudgeSubmitId(null);\n        judge.setVjudgePassword(null);\n\n        // 超级管理员与题目管理员有权限查看代码\n        // 如果不是本人或者并未分享代码，则不可查看\n        // 当此次提交代码不共享\n        // 比赛提交只有比赛创建者和root账号可看代码\n        if (judge.getCid() != 0) {\n            Contest contest = contestEntityService.getById(judge.getCid());\n            if (!contestValidator.isContestAdmin(contest)) {\n                // 不是本人的话不能查看代码\n                if (!userRolesVO.getUid().equals(judge.getUid())) {\n                    judge.setCode(null);\n                    // 如果还在比赛时间，不是本人不能查看时间，空间，长度，错误提示信息\n                    if (contest.getStatus().intValue() == ContestEnum.STATUS_RUNNING.getCode()) {\n                        judge.setTime(null);\n                        judge.setMemory(null);\n                        judge.setLength(null);\n                        judge.setErrorMessage(\"The contest is in progress. You are not allowed to view other people's error information.\");\n                    }\n                }\n            }\n        } else {\n            if (!judge.getShare() && !isRoot && !problemAdmin) {\n                // 需要判断是否为当前登陆用户自己的提交代码\n                if (!judge.getUid().equals(userRolesVO.getUid())) {\n                    judge.setCode(null);\n                }\n            }\n        }\n\n        // 只允许用户查看ce错误，sf错误，se错误信息提示\n        if (judge.getStatus().intValue() != JudgeStatus.STATUS_COMPILE_ERROR.getStatus()\n                && judge.getStatus().intValue() != JudgeStatus.STATUS_SYSTEM_ERROR.getStatus()\n                && judge.getStatus().intValue() != JudgeStatus.STATUS_SUBMITTED_FAILED.getStatus()) {\n            judge.setErrorMessage(\"The error message does not support viewing.\");\n        }\n\n        Problem problem = problemEntityService.getById(judge.getPid());\n        return new SubmissionInfoVO()\n                .setSubmission(judge)\n                .setCodeShare(problem.getCodeShare());\n    }\n\n    /**\n     * @MethodName updateSubmission\n     * @Description 修改单个提交详情的分享权限\n     * @Since 2021/1/2\n     */\n    @Override\n    public void updateSubmission(Judge judge) {\n        // 需要获取一下该token对应用户的数据\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        // 判断该提交是否为当前用户的\n        if (!userRolesVO.getUid().equals(judge.getUid())) {\n            throw new StatusForbiddenException(\"对不起，您不能修改他人的代码分享权限！\");\n        }\n        Judge judgeInfo = judgeEntityService.getById(judge.getSubmitId());\n        if (judgeInfo.getCid() != 0) {\n            // 如果是比赛提交，不可分享！\n            throw new StatusForbiddenException(\"对不起，您不能分享比赛题目的提交代码！\");\n        }\n        judgeInfo.setShare(judge.getShare());\n        boolean isOk = judgeEntityService.updateById(judgeInfo);\n        if (!isOk) {\n            throw new StatusFailException(\"修改代码权限失败！\");\n        }\n    }\n\n    /**\n     * @MethodName getJudgeList\n     * @Description 通用查询判题记录列表\n     * @Since 2021/10/29\n     */\n    @Override\n    public IPage<JudgeVO> getJudgeList(Integer limit, Integer currentPage, Boolean onlyMine, String searchPid,\n                                       Integer searchStatus, String searchUsername, Boolean completeProblemId) {\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 30;\n        }\n\n        String uid = null;\n        // 只查看当前用户的提交\n        if (onlyMine) {\n            UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n            if (userRolesVO == null) {\n                throw new StatusAccessDeniedException(\"当前用户数据为空，请您重新登陆！\");\n            }\n            uid = userRolesVO.getUid();\n        }\n        if (searchPid != null) {\n            searchPid = searchPid.trim();\n        }\n        if (searchUsername != null) {\n            searchUsername = searchUsername.trim();\n        }\n\n        return judgeEntityService.getCommonJudgeList(limit, currentPage, searchPid, searchStatus, searchUsername, uid,\n                completeProblemId);\n    }\n\n    /**\n     * @MethodName checkJudgeResult\n     * @Description 对提交列表状态为Pending和Judging的提交进行更新检查\n     * @Since 2021/1/3\n     */\n    @Override\n    public HashMap<Long, Object> checkCommonJudgeResult(SubmitIdListDTO submitIdListDTO) {\n\n        List<Long> submitIds = submitIdListDTO.getSubmitIds();\n\n        if (CollectionUtils.isEmpty(submitIds)) {\n            return new HashMap<>();\n        }\n\n        QueryWrapper<Judge> queryWrapper = new QueryWrapper<>();\n        // lambada表达式过滤掉code\n        queryWrapper.select(Judge.class, info -> !\"code\".equals(info.getColumn())).in(\"submit_id\", submitIds);\n        List<Judge> judgeList = judgeEntityService.list(queryWrapper);\n        HashMap<Long, Object> result = new HashMap<>();\n        for (Judge judge : judgeList) {\n            judge.setCode(null);\n            judge.setErrorMessage(null);\n            judge.setVjudgeUsername(null);\n            judge.setVjudgeSubmitId(null);\n            judge.setVjudgePassword(null);\n            result.put(judge.getSubmitId(), judge);\n        }\n        return result;\n    }\n\n    /**\n     * @MethodName checkContestJudgeResult\n     * @Description 需要检查是否为封榜，是否可以查询结果，避免有人恶意查询\n     * @Since 2021/6/11\n     */\n    @Override\n    public HashMap<Long, Object> checkContestJudgeResult(SubmitIdListDTO submitIdListDTO) {\n\n        if (submitIdListDTO.getCid() == null) {\n            throw new StatusNotFoundException(\"查询比赛id不能为空\");\n        }\n\n        if (CollectionUtils.isEmpty(submitIdListDTO.getSubmitIds())) {\n            return new HashMap<>();\n        }\n\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        Contest contest = contestEntityService.getById(submitIdListDTO.getCid());\n\n        boolean isSealRank = contestValidator.isOpenSealRank(contest, true);\n\n        QueryWrapper<Judge> queryWrapper = new QueryWrapper<>();\n        // lambada表达式过滤掉code\n        queryWrapper.select(Judge.class, info -> !\"code\".equals(info.getColumn()))\n                .in(\"submit_id\", submitIdListDTO.getSubmitIds()).eq(\"cid\", submitIdListDTO.getCid())\n                .between(isSealRank, \"submit_time\", contest.getStartTime(), contest.getSealRankTime());\n        List<Judge> judgeList = judgeEntityService.list(queryWrapper);\n        HashMap<Long, Object> result = new HashMap<>();\n        for (Judge judge : judgeList) {\n            judge.setCode(null);\n            judge.setDisplayPid(null);\n            judge.setErrorMessage(null);\n            judge.setVjudgeUsername(null);\n            judge.setVjudgeSubmitId(null);\n            judge.setVjudgePassword(null);\n            if (!judge.getUid().equals(userRolesVO.getUid()) && !contestValidator.isContestAdmin(contest)) {\n                judge.setTime(null);\n                judge.setMemory(null);\n                judge.setLength(null);\n            }\n            result.put(judge.getSubmitId(), judge);\n        }\n        return result;\n    }\n\n    /**\n     * @MethodName getJudgeCase\n     * @Description 获得指定提交id的测试样例结果，暂不支持查看测试数据，只可看测试点结果，时间，空间，或者IO得分\n     * @Since 2021/10/29\n     */\n    @Override\n    public List<JudgeCase> getAllCaseResult(Long submitId) {\n\n        Judge judge = judgeEntityService.getById(submitId);\n\n        if (judge == null) {\n            throw new StatusNotFoundException(\"此提交数据不存在！\");\n        }\n\n        Problem problem = problemEntityService.getById(judge.getPid());\n\n        // 如果该题不支持开放测试点结果查看\n        if (!problem.getOpenCaseResult()) {\n            return null;\n        }\n\n        // 是否为超级管理员\n        boolean isRoot = UserSessionUtil.isRoot();\n\n        if (judge.getCid() != 0) {\n            Contest contest = contestEntityService.getById(judge.getCid());\n            if (!contestValidator.isContestAdmin(contest)) {\n                // 当前是比赛期间 比赛封榜不能看\n                if (contest.getSealRank() && contest.getStatus().intValue() == ContestEnum.STATUS_RUNNING.getCode()\n                        && contest.getSealRankTime().before(new Date())) {\n                    throw new StatusForbiddenException(\"对不起，该题测试样例详情不能查看！\");\n                }\n\n                // 若是比赛题目，只支持OI查看测试点情况，ACM强制禁止查看,比赛管理员除外\n                if (problem.getType().intValue() == ContestEnum.TYPE_ACM.getCode()) {\n                    throw new StatusForbiddenException(\"对不起，该题测试样例详情不能查看！\");\n                }\n            }\n        }\n\n        QueryWrapper<JudgeCase> wrapper = new QueryWrapper<>();\n\n        if (!isRoot && !UserSessionUtil.isAdmin() && !UserSessionUtil.isProblemAdmin()) {\n            wrapper.select(\"time\", \"memory\", \"score\", \"status\", \"user_output\");\n        }\n        wrapper.eq(\"submit_id\", submitId).last(\"order by length(input_data) asc,input_data asc\");\n\n        // 当前所有测试点只支持 空间 时间 状态码 IO得分 和错误信息提示查看而已\n        return judgeCaseEntityService.list(wrapper);\n    }\n\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/ProblemServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.common.exception.StatusNotFoundException;\nimport com.simplefanc.voj.backend.dao.contest.ContestEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.problem.*;\nimport com.simplefanc.voj.backend.pojo.dto.PidListDTO;\nimport com.simplefanc.voj.backend.pojo.vo.*;\nimport com.simplefanc.voj.backend.service.oj.ProblemService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.backend.validator.ContestValidator;\nimport com.simplefanc.voj.common.constants.*;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.problem.*;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 10:37\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class ProblemServiceImpl implements ProblemService {\n\n    private final ProblemEntityService problemEntityService;\n\n    private final ProblemTagEntityService problemTagEntityService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final TagEntityService tagEntityService;\n\n    private final LanguageEntityService languageEntityService;\n\n    private final ContestEntityService contestEntityService;\n\n    private final ProblemLanguageEntityService problemLanguageEntityService;\n\n    private final CodeTemplateEntityService codeTemplateEntityService;\n\n    private final ContestValidator contestValidator;\n\n    /**\n     * @MethodName getProblemList\n     * @Params * @param null\n     * @Description 获取题目列表分页\n     * @Since 2021/10/27\n     */\n    @Override\n    public Page<ProblemVO> getProblemList(Integer limit, Integer currentPage, String keyword, List<Long> tagIds,\n                                          Integer difficulty, String oj, Boolean problemVisible) {\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 10;\n        }\n\n        // 关键词查询不为空\n        if (StrUtil.isNotEmpty(keyword)) {\n            keyword = keyword.trim();\n        }\n        if (oj != null && !RemoteOj.isRemoteOj(oj)) {\n            oj = Constant.LOCAL;\n        }\n        boolean allProblemVisible = problemVisible && (UserSessionUtil.isRoot() || UserSessionUtil.isProblemAdmin());\n        return problemEntityService.getProblemList(limit, currentPage, keyword, difficulty, tagIds, oj, allProblemVisible);\n    }\n\n    /**\n     * @MethodName getRandomProblem\n     * @Description 随机选取一道题目\n     * @Since 2021/10/27\n     */\n    @Override\n    public RandomProblemVO getRandomProblem() {\n        QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();\n        // 必须是公开题目\n        queryWrapper.select(\"problem_id\").eq(\"auth\", 1);\n        List<Problem> list = problemEntityService.list(queryWrapper);\n        if (list.size() == 0) {\n            throw new StatusFailException(\"获取随机题目失败，题库暂无公开题目！\");\n        }\n        Random random = new Random();\n        int index = random.nextInt(list.size());\n        RandomProblemVO randomProblemVO = new RandomProblemVO();\n        randomProblemVO.setProblemId(list.get(index).getProblemId());\n        return randomProblemVO;\n    }\n\n    /**\n     * @MethodName getUserProblemStatus\n     * @Description 获取用户对应该题目列表中各个题目的做题情况\n     * @Since 2021/12/29\n     */\n    // TODO 行数过多\n    @Override\n    public HashMap<Long, Object> getUserProblemStatus(PidListDTO pidListDTO) {\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        HashMap<Long, Object> result = new HashMap<>();\n        // 先查询判断该用户对于这些题是否已经通过，若已通过，则无论后续再提交结果如何，该题都标记为通过\n        QueryWrapper<Judge> queryWrapper = new QueryWrapper<>();\n        queryWrapper.select(\"distinct pid,status,submit_time,score\").in(\"pid\", pidListDTO.getPidList())\n                .eq(\"uid\", userRolesVO.getUid()).orderByDesc(\"submit_time\");\n\n        if (pidListDTO.getIsContestProblemList()) {\n            // 如果是比赛的提交记录需要判断cid\n            queryWrapper.eq(\"cid\", pidListDTO.getCid());\n        } else {\n            queryWrapper.eq(\"cid\", 0);\n        }\n\n        List<Judge> judges = judgeEntityService.list(queryWrapper);\n\n        boolean isACMContest = true;\n        Contest contest = null;\n        if (pidListDTO.getIsContestProblemList()) {\n            contest = contestEntityService.getById(pidListDTO.getCid());\n            if (contest == null) {\n                throw new StatusNotFoundException(\"错误：该比赛不存在！\");\n            }\n            isACMContest = contest.getType().intValue() == ContestEnum.TYPE_ACM.getCode();\n        }\n\n        for (Judge judge : judges) {\n            // 如果是比赛的题目列表状态\n            HashMap<String, Object> temp = new HashMap<>();\n            if (pidListDTO.getIsContestProblemList()) {\n                processContestJudge(result, isACMContest, contest, judge, temp);\n            } else { // 不是比赛题目\n                // 如果该题目已通过，则强制写为通过（0）\n                if (judge.getStatus().intValue() == JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n                    temp.put(\"status\", JudgeStatus.STATUS_ACCEPTED.getStatus());\n                    result.put(judge.getPid(), temp);\n                } else if (!result.containsKey(judge.getPid())) {\n                    // 还未写入，则使用最新一次提交的结果\n                    temp.put(\"status\", judge.getStatus());\n                    result.put(judge.getPid(), temp);\n                }\n            }\n        }\n\n        // 再次检查，应该可能从未提交过该题，则状态写为-10\n        for (Long pid : pidListDTO.getPidList()) {\n            // 如果是比赛的题目列表状态\n            if (pidListDTO.getIsContestProblemList()) {\n                if (!result.containsKey(pid)) {\n                    HashMap<String, Object> temp = new HashMap<>();\n                    temp.put(\"score\", null);\n                    temp.put(\"status\", JudgeStatus.STATUS_NOT_SUBMITTED.getStatus());\n                    result.put(pid, temp);\n                }\n            } else {\n                if (!result.containsKey(pid)) {\n                    HashMap<String, Object> temp = new HashMap<>();\n                    temp.put(\"status\", JudgeStatus.STATUS_NOT_SUBMITTED.getStatus());\n                    result.put(pid, temp);\n                }\n            }\n        }\n        return result;\n\n    }\n\n    private void processContestJudge(HashMap<Long, Object> result, boolean isACMContest, Contest contest, Judge judge, HashMap<String, Object> temp) {\n        // IO比赛的，如果还未写入，则使用最新一次提交的结果\n//        if (!isACMContest) {\n//            if (!result.containsKey(judge.getPid())) {\n//                // 判断该提交是否为封榜之后的提交,OI赛制封榜后的提交看不到提交结果，\n//                // 只有比赛结束可以看到,比赛管理员与超级管理员的提交除外\n//                if (contestValidator.isOpenSealRank(contest, true)) {\n//                    temp.put(\"status\", JudgeStatus.STATUS_SUBMITTED_UNKNOWN_RESULT.getStatus());\n//                    temp.put(\"score\", null);\n//                } else {\n//                    temp.put(\"status\", judge.getStatus());\n//                    temp.put(\"score\", judge.getScore());\n//                }\n//                result.put(judge.getPid(), temp);\n//            }\n//        } else {\n        // 如果该题目已通过，且同时是为不封榜前提交的，则强制写为通过（0）\n        if (judge.getStatus().intValue() == JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n            temp.put(\"status\", JudgeStatus.STATUS_ACCEPTED.getStatus());\n            temp.put(\"score\", judge.getScore());\n            result.put(judge.getPid(), temp);\n        } else if (!result.containsKey(judge.getPid())) {\n            // 还未写入，则使用最新一次提交的结果\n            temp.put(\"status\", judge.getStatus());\n            temp.put(\"score\", judge.getScore());\n            result.put(judge.getPid(), temp);\n        }\n//        }\n    }\n\n    /**\n     * @MethodName getProblemInfo\n     * @Description 获取指定题目的详情信息，标签，所支持语言，做题情况（只能查询公开题目 也就是auth为1）\n     * @Since 2021/10/27\n     */\n    @Override\n    public ProblemInfoVO getProblemInfo(String problemId) {\n\n        QueryWrapper<Problem> wrapper = new QueryWrapper<Problem>().eq(\"problem_id\", problemId);\n        // 查询题目详情，题目标签，题目语言，题目做题情况\n        Problem problem = problemEntityService.getOne(wrapper, false);\n        if (problem == null) {\n            throw new StatusNotFoundException(\"该题号对应的题目不存在\");\n        }\n        boolean isAdmin = UserSessionUtil.isRoot() || UserSessionUtil.isProblemAdmin();\n        if (!isAdmin && !problem.getAuth().equals(ProblemEnum.AUTH_PUBLIC.getCode())) {\n            throw new StatusForbiddenException(\"该题号对应题目并非公开题目，不支持访问！\");\n        }\n\n        QueryWrapper<ProblemTag> problemTagQueryWrapper = new QueryWrapper<>();\n        problemTagQueryWrapper.eq(\"pid\", problem.getId());\n        // 获取该题号对应的标签id\n        List<Long> tidList = new LinkedList<>();\n        problemTagEntityService.list(problemTagQueryWrapper).forEach(problemTag -> {\n            tidList.add(problemTag.getTid());\n        });\n\n        List<Tag> tags = (List<Tag>) tagEntityService.listByIds(tidList);\n\n        // 记录 languageId对应的name\n        HashMap<Long, String> tmpMap = new HashMap<>();\n\n        // 获取题目提交的代码支持的语言\n        List<String> languagesStr = new LinkedList<>();\n        QueryWrapper<ProblemLanguage> problemLanguageQueryWrapper = new QueryWrapper<>();\n        problemLanguageQueryWrapper.eq(\"pid\", problem.getId()).select(\"lid\");\n        List<Long> lidList = problemLanguageEntityService.list(problemLanguageQueryWrapper).stream()\n                .map(ProblemLanguage::getLid).collect(Collectors.toList());\n        languageEntityService.listByIds(lidList).forEach(language -> {\n            languagesStr.add(language.getName());\n            tmpMap.put(language.getId(), language.getName());\n        });\n\n        // 获取题目的提交记录\n        ProblemCountVO problemCount = judgeEntityService.getProblemCount(problem.getId());\n\n        // 获取题目的代码模板\n        QueryWrapper<CodeTemplate> codeTemplateQueryWrapper = new QueryWrapper<>();\n        codeTemplateQueryWrapper.eq(\"pid\", problem.getId()).eq(\"status\", true);\n        List<CodeTemplate> codeTemplates = codeTemplateEntityService.list(codeTemplateQueryWrapper);\n        HashMap<String, String> langNameAndCode = new HashMap<>();\n        if (codeTemplates.size() > 0) {\n            for (CodeTemplate codeTemplate : codeTemplates) {\n                langNameAndCode.put(tmpMap.get(codeTemplate.getLid()), codeTemplate.getCode());\n            }\n        }\n        // 屏蔽一些题目参数\n        problem.setJudgeExtraFile(null).setSpjCode(null).setSpjLanguage(null);\n\n        // 将数据统一写入到一个VO返回数据实体类中\n        return new ProblemInfoVO(problem, tags, languagesStr, problemCount, langNameAndCode);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/RankServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.service.admin.user.UserRecordService;\nimport com.simplefanc.voj.backend.pojo.vo.ACMRankVO;\nimport com.simplefanc.voj.backend.pojo.vo.OIRankVO;\nimport com.simplefanc.voj.backend.service.oj.RankService;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.RedisConstant;\nimport com.simplefanc.voj.common.pojo.entity.user.UserInfo;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 20:47\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class RankServiceImpl implements RankService {\n\n    // 排行榜缓存时间 60s\n    private static final long CACHE_RANK_SECOND = 60;\n\n    private final UserRecordService userRecordService;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final RedisUtil redisUtil;\n\n    /**\n     * @MethodName get-rank-list\n     * @Params * @param null\n     * @Description 获取排行榜数据\n     * @Return CommonResult\n     * @Since 2021/10/27\n     */\n    @Override\n    public IPage getRankList(Integer limit, Integer currentPage, String searchUser, Integer type) {\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 30;\n        }\n\n        List<String> uidList = null;\n        if (StrUtil.isNotEmpty(searchUser)) {\n            QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();\n            userInfoQueryWrapper.and(wrapper -> wrapper.like(\"username\", searchUser)\n                    .or().like(\"nickname\", searchUser)\n                    .or().like(\"realname\", searchUser));\n\n            userInfoQueryWrapper.eq(\"status\", 0);\n\n            uidList = userInfoEntityService.list(userInfoQueryWrapper).stream().map(UserInfo::getUuid)\n                    .collect(Collectors.toList());\n        }\n\n        IPage rankList = null;\n        // 根据type查询不同类型的排行榜\n        if (type.intValue() == ContestEnum.TYPE_ACM.getCode()) {\n            rankList = getACMRankList(limit, currentPage, uidList);\n        } else if (type.intValue() == ContestEnum.TYPE_OI.getCode()) {\n            rankList = getOIRankList(limit, currentPage, uidList);\n        } else {\n            throw new StatusFailException(\"比赛类型代码不正确！\");\n        }\n        return rankList;\n    }\n\n    @Cacheable(value = RedisConstant.ACM_RANK_CACHE, key = \"#limit+'-'+#currentPage\", condition=\"#uidList == null\")\n    public IPage<ACMRankVO> getACMRankList(int limit, int currentPage, List<String> uidList) {\n        IPage<ACMRankVO> data = null;\n        if (uidList != null) {\n            Page<ACMRankVO> page = new Page<>(currentPage, limit);\n            if (!uidList.isEmpty()) {\n                data = userRecordService.getACMRankList(page, uidList);\n            } else {\n                data = page;\n            }\n        } else {\n//            String key = AccountConstant.ACM_RANK_CACHE + \"_\" + limit + \"_\" + currentPage;\n//            data = (IPage<ACMRankVO>) redisUtil.get(key);\n//            if (data == null) {\n                Page<ACMRankVO> page = new Page<>(currentPage, limit);\n                data = userRecordService.getACMRankList(page, null);\n//                redisUtil.set(key, data, cacheRankSecond);\n//            }\n        }\n\n        return data;\n    }\n\n    @Cacheable(value = RedisConstant.OI_RANK_CACHE, key = \"#limit+'-'+#currentPage\", condition=\"#uidList == null\")\n    public IPage<OIRankVO> getOIRankList(int limit, int currentPage, List<String> uidList) {\n        IPage<OIRankVO> data = null;\n        if (uidList != null) {\n            Page<OIRankVO> page = new Page<>(currentPage, limit);\n            if (!uidList.isEmpty()) {\n                data = userRecordService.getOIRankList(page, uidList);\n            } else {\n                data = page;\n            }\n        } else {\n//            String key = AccountConstant.OI_RANK_CACHE + \"_\" + limit + \"_\" + currentPage;\n//            data = (IPage<OIRankVO>) redisUtil.get(key);\n//            if (data == null) {\n                Page<OIRankVO> page = new Page<>(currentPage, limit);\n                data = userRecordService.getOIRankList(page, null);\n//                redisUtil.set(key, data, cacheRankSecond);\n//            }\n        }\n        return data;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/oj/impl/TrainingServiceImpl.java",
    "content": "package com.simplefanc.voj.backend.service.oj.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.dao.training.*;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.pojo.dto.RegisterTrainingDTO;\nimport com.simplefanc.voj.backend.pojo.vo.*;\nimport com.simplefanc.voj.backend.service.admin.training.AdminTrainingRecordService;\nimport com.simplefanc.voj.backend.service.oj.TrainingService;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.backend.validator.TrainingValidator;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.training.*;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/10 17:12\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class TrainingServiceImpl implements TrainingService {\n\n    private final TrainingEntityService trainingEntityService;\n\n    private final TrainingRegisterEntityService trainingRegisterEntityService;\n\n    private final TrainingCategoryEntityService trainingCategoryEntityService;\n\n    private final TrainingProblemEntityService trainingProblemEntityService;\n\n    private final TrainingRecordEntityService trainingRecordEntityService;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final AdminTrainingRecordService adminTrainingRecordService;\n\n    private final TrainingValidator trainingValidator;\n\n    /**\n     * @param limit\n     * @param currentPage\n     * @param keyword\n     * @param categoryId\n     * @param auth\n     * @MethodName getTrainingList\n     * @Description 获取训练题单列表，可根据关键词、类别、权限、类型过滤\n     * @Return\n     * @Since 2021/11/20\n     */\n    @Override\n    public IPage<TrainingVO> getTrainingList(Integer limit, Integer currentPage, String keyword, Long categoryId,\n                                             String auth) {\n\n        // 页数，每页题数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 30;\n        }\n        return trainingEntityService.getTrainingList(limit, currentPage, categoryId, auth, keyword);\n    }\n\n    /**\n     * @param tid\n     * @MethodName getTraining\n     * @Description 根据tid获取指定训练详情\n     * @Return\n     * @Since 2021/11/20\n     */\n    @Override\n    public TrainingVO getTraining(Long tid) {\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        boolean isRoot = UserSessionUtil.isRoot();\n\n        Training training = trainingEntityService.getById(tid);\n        if (training == null || !training.getStatus()) {\n            throw new StatusFailException(\"该训练不存在或不允许显示！\");\n        }\n\n        TrainingVO trainingVO = BeanUtil.copyProperties(training, TrainingVO.class);\n        TrainingCategory trainingCategory = trainingCategoryEntityService\n                .getTrainingCategoryByTrainingId(training.getId());\n        trainingVO.setCategoryName(trainingCategory.getName());\n        trainingVO.setCategoryColor(trainingCategory.getColor());\n        List<Long> trainingProblemIdList = trainingProblemEntityService.getTrainingProblemIdList(training.getId());\n        trainingVO.setProblemCount(trainingProblemIdList.size());\n\n        if (userRolesVO != null && trainingValidator.isInTrainingOrAdmin(training, userRolesVO)) {\n            Integer userTrainingACProblemCount = trainingProblemEntityService\n                    .getUserTrainingACProblemCount(userRolesVO.getUid(), trainingProblemIdList);\n            trainingVO.setAcCount(userTrainingACProblemCount);\n        } else {\n            trainingVO.setAcCount(0);\n        }\n\n        return trainingVO;\n    }\n\n    /**\n     * @param tid\n     * @MethodName getTrainingProblemList\n     * @Description 根据tid获取指定训练的题单题目列表\n     * @Return\n     * @Since 2021/11/20\n     */\n    @Override\n    public List<ProblemVO> getTrainingProblemList(Long tid) {\n\n        Training training = trainingEntityService.getById(tid);\n        if (training == null || !training.getStatus()) {\n            throw new StatusFailException(\"该训练不存在或不允许显示！\");\n        }\n\n        trainingValidator.validateTrainingAuth(training);\n\n        return trainingProblemEntityService.getTrainingProblemList(tid);\n\n    }\n\n    /**\n     * @param registerTrainingDTO\n     * @MethodName toRegisterTraining\n     * @Description 注册校验私有权限的训练\n     * @Return\n     * @Since 2021/11/20\n     */\n    @Override\n    public void toRegisterTraining(RegisterTrainingDTO registerTrainingDTO) {\n\n        Long tid = registerTrainingDTO.getTid();\n        String password = registerTrainingDTO.getPassword();\n\n        if (tid == null || StrUtil.isEmpty(password)) {\n            throw new StatusFailException(\"请求参数不能为空！\");\n        }\n\n        Training training = trainingEntityService.getById(tid);\n\n        if (training == null || !training.getStatus()) {\n            throw new StatusFailException(\"对不起，该训练不存在或不允许显示!\");\n        }\n\n        if (!training.getPrivatePwd().equals(password)) {\n            throw new StatusFailException(\"训练密码错误，请重新输入！\");\n        }\n\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        QueryWrapper<TrainingRegister> registerQueryWrapper = new QueryWrapper<>();\n        registerQueryWrapper.eq(\"tid\", tid).eq(\"uid\", userRolesVO.getUid());\n        if (trainingRegisterEntityService.count(registerQueryWrapper) > 0) {\n            throw new StatusFailException(\"您已注册过该训练，请勿重复注册！\");\n        }\n\n        boolean isOk = trainingRegisterEntityService\n                .save(new TrainingRegister().setTid(tid).setUid(userRolesVO.getUid()));\n\n        if (!isOk) {\n            throw new StatusFailException(\"校验训练密码失败，请稍后再试\");\n        } else {\n            adminTrainingRecordService.syncUserSubmissionToRecordByTid(tid, userRolesVO.getUid());\n        }\n    }\n\n    /**\n     * @param tid\n     * @MethodName getTrainingAccess\n     * @Description 私有权限的训练需要获取当前用户是否有进入训练的权限\n     * @Return\n     * @Since 2021/11/20\n     */\n    @Override\n    public AccessVO getTrainingAccess(Long tid) {\n        // 获取当前登录的用户\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        QueryWrapper<TrainingRegister> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"tid\", tid).eq(\"uid\", userRolesVO.getUid());\n        TrainingRegister trainingRegister = trainingRegisterEntityService.getOne(queryWrapper, false);\n        boolean access = false;\n        if (trainingRegister != null) {\n            access = true;\n            Training training = trainingEntityService.getById(tid);\n            if (training == null || !training.getStatus()) {\n                throw new StatusFailException(\"对不起，该训练不存在!\");\n            }\n        }\n\n        AccessVO accessVO = new AccessVO();\n        accessVO.setAccess(access);\n\n        return accessVO;\n    }\n\n    /**\n     * @param tid\n     * @param limit\n     * @param currentPage\n     * @MethodName getTrainingRnk\n     * @Description 获取训练的排行榜分页\n     * @Return\n     * @Since 2021/11/22\n     */\n    @Override\n    public IPage<TrainingRankVO> getTrainingRank(Long tid, Integer limit, Integer currentPage) {\n\n        Training training = trainingEntityService.getById(tid);\n        if (training == null || !training.getStatus()) {\n            throw new StatusFailException(\"该训练不存在或不允许显示！\");\n        }\n\n        trainingValidator.validateTrainingAuth(training);\n\n        // 页数，每页数若为空，设置默认值\n        if (currentPage == null || currentPage < 1) {\n            currentPage = 1;\n        }\n        if (limit == null || limit < 1) {\n            limit = 30;\n        }\n\n        return getTrainingRank(tid, training.getAuthor(), currentPage, limit);\n    }\n\n    // TODO 行数过多\n    private IPage<TrainingRankVO> getTrainingRank(Long tid, String username, int currentPage, int limit) {\n\n        Map<Long, String> tpIdMapDisplayId = getTPIdMapDisplayId(tid);\n        List<TrainingRecordVO> trainingRecordVOList = trainingRecordEntityService.getTrainingRecord(tid);\n\n        List<String> superAdminUidList = userInfoEntityService.getSuperAdminUidList();\n\n        List<TrainingRankVO> result = new ArrayList<>();\n\n        HashMap<String, Integer> uidMapIndex = new HashMap<>();\n        int pos = 0;\n        for (TrainingRecordVO trainingRecordVO : trainingRecordVOList) {\n            // 超级管理员和训练创建者的提交不入排行榜\n            if (username.equals(trainingRecordVO.getUsername())\n                    || superAdminUidList.contains(trainingRecordVO.getUid())) {\n                continue;\n            }\n\n            TrainingRankVO trainingRankVO;\n            Integer index = uidMapIndex.get(trainingRecordVO.getUid());\n            if (index == null) {\n                trainingRankVO = new TrainingRankVO();\n                trainingRankVO.setRealname(trainingRecordVO.getRealname()).setAvatar(trainingRecordVO.getAvatar())\n                        .setSchool(trainingRecordVO.getSchool()).setGender(trainingRecordVO.getGender())\n                        .setUid(trainingRecordVO.getUid()).setUsername(trainingRecordVO.getUsername())\n                        .setNickname(trainingRecordVO.getNickname()).setAc(0).setTotalRunTime(0);\n                HashMap<String, HashMap<String, Object>> submissionInfo = new HashMap<>();\n                trainingRankVO.setSubmissionInfo(submissionInfo);\n\n                result.add(trainingRankVO);\n                uidMapIndex.put(trainingRecordVO.getUid(), pos);\n                pos++;\n            } else {\n                trainingRankVO = result.get(index);\n            }\n            String displayId = tpIdMapDisplayId.get(trainingRecordVO.getTpid());\n            // TODO\n            HashMap<String, Object> problemSubmissionInfo = trainingRankVO.getSubmissionInfo().getOrDefault(displayId,\n                    new HashMap<>());\n\n            // 如果该题目已经AC过了，只比较运行时间取最小\n            if ((Boolean) problemSubmissionInfo.getOrDefault(\"isAC\", false)) {\n                if (trainingRecordVO.getStatus().intValue() == JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n                    int runTime = (int) problemSubmissionInfo.getOrDefault(\"runTime\", 0);\n                    if (runTime > trainingRecordVO.getUseTime()) {\n                        trainingRankVO.setTotalRunTime(\n                                trainingRankVO.getTotalRunTime() - runTime + trainingRecordVO.getUseTime());\n                        problemSubmissionInfo.put(\"runTime\", trainingRecordVO.getUseTime());\n                    }\n                }\n                continue;\n            }\n\n            problemSubmissionInfo.put(\"status\", trainingRecordVO.getStatus());\n            problemSubmissionInfo.put(\"score\", trainingRecordVO.getScore());\n\n            // 通过的话\n            if (trainingRecordVO.getStatus().intValue() == JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n                // 总解决题目次数ac+1\n                trainingRankVO.setAc(trainingRankVO.getAc() + 1);\n                problemSubmissionInfo.put(\"isAC\", true);\n                problemSubmissionInfo.put(\"runTime\", trainingRecordVO.getUseTime());\n                trainingRankVO.setTotalRunTime(trainingRankVO.getTotalRunTime() + trainingRecordVO.getUseTime());\n            }\n\n            trainingRankVO.getSubmissionInfo().put(displayId, problemSubmissionInfo);\n        }\n\n        List<TrainingRankVO> orderResultList = result.stream()\n                // 先以总ac数降序\n                .sorted(Comparator.comparing(TrainingRankVO::getAc, Comparator.reverseOrder())\n                        .thenComparing(TrainingRankVO::getTotalRunTime))\n                .collect(Collectors.toList());\n\n        // 计算好排行榜，然后进行分页\n        Page<TrainingRankVO> page = new Page<>(currentPage, limit);\n        int count = orderResultList.size();\n        List<TrainingRankVO> pageList = new ArrayList<>();\n        // 计算当前页第一条数据的下标\n        int currId = currentPage > 1 ? (currentPage - 1) * limit : 0;\n        for (int i = 0; i < limit && i < count - currId; i++) {\n            pageList.add(orderResultList.get(currId + i));\n        }\n        page.setSize(limit);\n        page.setCurrent(currentPage);\n        page.setTotal(count);\n        page.setRecords(pageList);\n        return page;\n    }\n\n    private Map<Long, String> getTPIdMapDisplayId(Long tid) {\n        QueryWrapper<TrainingProblem> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"tid\", tid);\n        List<TrainingProblem> trainingProblemList = trainingProblemEntityService.list(queryWrapper);\n        return trainingProblemList.stream()\n                .collect(Collectors.toMap(TrainingProblem::getId, TrainingProblem::getDisplayId));\n    }\n\n    /**\n     * 未启用，该操作会导致公开训练也记录，但其实并不需要，会造成数据量无效增加\n     */\n    @Override\n    @Async\n    public void checkAndSyncTrainingRecord(Long pid, Long submitId, String uid) {\n        QueryWrapper<TrainingProblem> trainingProblemQueryWrapper = new QueryWrapper<>();\n        trainingProblemQueryWrapper.eq(\"pid\", pid);\n\n        List<TrainingProblem> trainingProblemList = trainingProblemEntityService.list(trainingProblemQueryWrapper);\n        List<TrainingRecord> trainingRecordList = new ArrayList<>();\n        for (TrainingProblem trainingProblem : trainingProblemList) {\n            TrainingRecord trainingRecord = new TrainingRecord();\n            trainingRecord.setPid(pid)\n                    .setTid(trainingProblem.getTid())\n                    .setTpid(trainingProblem.getId())\n                    .setSubmitId(submitId).setUid(uid);\n            trainingRecordList.add(trainingRecord);\n        }\n        if (trainingRecordList.size() > 0) {\n            trainingRecordEntityService.saveBatch(trainingRecordList);\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/service/schedule/ScheduleService.java",
    "content": "package com.simplefanc.voj.backend.service.schedule;\n\nimport cn.hutool.core.date.DatePattern;\nimport cn.hutool.core.date.DateTime;\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.io.FileUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.backend.dao.common.FileEntityService;\nimport com.simplefanc.voj.backend.dao.judge.JudgeEntityService;\nimport com.simplefanc.voj.backend.dao.msg.AdminSysNoticeEntityService;\nimport com.simplefanc.voj.backend.dao.msg.UserSysNoticeEntityService;\nimport com.simplefanc.voj.backend.dao.user.SessionEntityService;\nimport com.simplefanc.voj.backend.dao.user.UserInfoEntityService;\nimport com.simplefanc.voj.backend.service.admin.user.UserRecordService;\nimport com.simplefanc.voj.backend.config.property.FilePathProperties;\nimport com.simplefanc.voj.backend.service.admin.rejudge.RejudgeService;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.common.File;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.msg.AdminSysNotice;\nimport com.simplefanc.voj.common.pojo.entity.msg.UserSysNotice;\nimport com.simplefanc.voj.common.pojo.entity.user.Session;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * 一个cron表达式有至少6个（也可能7个）有空格分隔的时间元素。按顺序依次为：\n * <p>\n * 字段 允许值 允许的特殊字符 秒 0~59 , - * / 分 0~59 , - * / 小时 0~23 , - * / 日期 1-31 , - * ? / L W C 月份\n * 1~12或者JAN~DEC , - * / 星期 1~7或者SUN~SAT , - * ? / L C # 年（可选） 留空，1970~2099 , - * /\n * <p>\n * “*” 字符代表所有可能的值 “-” 字符代表数字范围 例如1-5 “/” 字符用来指定数值的增量 “？” 字符仅被用于天（月）和天（星期）两个子表达式，表示不指定值。\n * 当2个子表达式其中之一被指定了值以后，为了避免冲突，需要将另一个子表达式的值设为“？” “L” 字符仅被用于天（月）和天（星期）两个子表达式，它是单词“last”的缩写\n * 如果在“L”前有具体的内容，它就具有其他的含义了。 “W” 字符代表着平日(Mon-Fri)，并且仅能用于日域中。它用来指定离指定日的最近的一个平日。\n * 大部分的商业处理都是基于工作周的，所以 W 字符可能是非常重要的。 \"C\"\n * 代表“Calendar”的意思。它的意思是计划所关联的日期，如果日期没有被关联，则相当于日历中所有日期。\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class ScheduleService {\n\n    private final FileEntityService fileEntityService;\n\n    private final RedisUtil redisUtil;\n\n    private final UserInfoEntityService userInfoEntityService;\n\n    private final UserRecordService userRecordService;\n\n    private final SessionEntityService sessionEntityService;\n\n    private final AdminSysNoticeEntityService adminSysNoticeEntityService;\n\n    private final UserSysNoticeEntityService userSysNoticeEntityService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final RejudgeService rejudgeService;\n\n    private final FilePathProperties filePathProps;\n\n    /**\n     * @MethodName deleteAvatar\n     * @Params * @param null\n     * @Description 每天3点定时查询数据库字段并删除未引用的头像\n     * @Return\n     * @Since 2021/1/13\n     */\n    @Scheduled(cron = \"0 0 3 * * *\")\n    public void deleteAvatar() {\n        List<File> files = fileEntityService.queryDeleteAvatarList();\n        // 如果查不到，直接结束\n        if (files.isEmpty()) {\n            return;\n        }\n        List<Long> idLists = new LinkedList<>();\n        for (File file : files) {\n            if (file.getDelete()) {\n                boolean delSuccess = FileUtil.del(file.getFilePath());\n                if (delSuccess) {\n                    idLists.add(file.getId());\n                }\n            }\n        }\n\n        boolean isSuccess = fileEntityService.removeByIds(idLists);\n        if (!isSuccess) {\n            log.error(\"数据库file表删除头像数据失败----------------->sql语句执行失败\");\n        }\n    }\n\n    /**\n     * @MethodName deleteTestCase\n     * @Params * @param null\n     * @Description 每天3点定时删除指定文件夹的上传测试数据\n     * @Return\n     * @Since 2021/2/7\n     */\n    @Scheduled(cron = \"0 0 3 * * *\")\n    public void deleteTestCase() {\n        boolean result = FileUtil.del(filePathProps.getTestcaseTmpFolder());\n        if (!result) {\n            log.error(\"每日定时任务异常------------------------>{}\", \"清除本地的题目测试数据失败!\");\n        }\n    }\n\n    /**\n     * @MethodName deleteContestPrintText\n     * @Params * @param null\n     * @Description 每天4点定时删除本地的比赛打印数据\n     * @Return\n     * @Since 2021/9/19\n     */\n    @Scheduled(cron = \"0 0 4 * * *\")\n    public void deleteContestPrintText() {\n        boolean result = FileUtil.del(filePathProps.getContestTextPrintFolder());\n        if (!result) {\n            log.error(\"每日定时任务异常------------------------>{}\", \"清除本地的比赛打印数据失败!\");\n        }\n    }\n\n    /**\n     * @MethodName deleteUserSession\n     * @Params * @param null\n     * @Description 每天3点定时删除用户半年的session表记录\n     * @Return\n     * @Since 2021/9/6\n     */\n    @Scheduled(cron = \"0 0 3 * * *\")\n    public void deleteUserSession() {\n        QueryWrapper<Session> sessionQueryWrapper = new QueryWrapper<>();\n        DateTime dateTime = DateUtil.offsetMonth(new Date(), -6);\n        String strTime = dateTime.toString(DatePattern.NORM_DATETIME_FORMAT);\n        sessionQueryWrapper.select(\"distinct uid\");\n        sessionQueryWrapper.apply(\"UNIX_TIMESTAMP(gmt_create) >= UNIX_TIMESTAMP('\" + strTime + \"')\");\n        List<Session> sessionList = sessionEntityService.list(sessionQueryWrapper);\n        if (sessionList.size() > 0) {\n            List<String> uidList = sessionList.stream().map(Session::getUid).collect(Collectors.toList());\n            QueryWrapper<Session> queryWrapper = new QueryWrapper<>();\n            queryWrapper.in(\"uid\", uidList).apply(\"UNIX_TIMESTAMP('\" + strTime + \"') > UNIX_TIMESTAMP(gmt_create)\");\n            List<Session> needDeletedSessionList = sessionEntityService.list(queryWrapper);\n            if (needDeletedSessionList.size() > 0) {\n                List<Long> needDeletedIdList = needDeletedSessionList.stream().map(Session::getId)\n                        .collect(Collectors.toList());\n                boolean isOk = sessionEntityService.removeByIds(needDeletedIdList);\n                if (!isOk) {\n                    log.error(\"=============数据库session表定时删除用户6个月前的记录失败===============\");\n                }\n            }\n        }\n    }\n\n    /**\n     * @MethodName syncNoticeToUser\n     * @Description 每一小时拉取系统通知表admin_sys_notice到表user_sys_notice(只推送给半年内有登录过的用户)\n     * @Return\n     * @Since 2021/10/3\n     */\n    @Scheduled(cron = \"0 0 0/1 * * *\")\n    public void syncNoticeToRecentHalfYearUser() {\n        QueryWrapper<AdminSysNotice> adminSysNoticeQueryWrapper = new QueryWrapper<>();\n        adminSysNoticeQueryWrapper.eq(\"state\", false);\n        List<AdminSysNotice> adminSysNotices = adminSysNoticeEntityService.list(adminSysNoticeQueryWrapper);\n        if (adminSysNotices.size() == 0) {\n            return;\n        }\n\n        QueryWrapper<Session> sessionQueryWrapper = new QueryWrapper<>();\n        sessionQueryWrapper.select(\"DISTINCT uid\");\n        List<Session> sessionList = sessionEntityService.list(sessionQueryWrapper);\n        List<String> userIds = sessionList.stream().map(Session::getUid).collect(Collectors.toList());\n\n        for (AdminSysNotice adminSysNotice : adminSysNotices) {\n            switch (adminSysNotice.getType()) {\n                // TODO 魔法\n                case \"All\":\n                    List<UserSysNotice> userSysNoticeList = new ArrayList<>();\n                    for (String uid : userIds) {\n                        UserSysNotice userSysNotice = new UserSysNotice();\n                        userSysNotice.setRecipientId(uid).setType(\"Sys\").setSysNoticeId(adminSysNotice.getId());\n                        userSysNoticeList.add(userSysNotice);\n                    }\n                    boolean isOk1 = userSysNoticeEntityService.saveOrUpdateBatch(userSysNoticeList);\n                    if (isOk1) {\n                        adminSysNotice.setState(true);\n                    }\n                    break;\n                case \"Single\":\n                    UserSysNotice userSysNotice = new UserSysNotice();\n                    userSysNotice.setRecipientId(adminSysNotice.getRecipientId()).setType(\"Mine\")\n                            .setSysNoticeId(adminSysNotice.getId());\n                    boolean isOk2 = userSysNoticeEntityService.saveOrUpdate(userSysNotice);\n                    if (isOk2) {\n                        adminSysNotice.setState(true);\n                    }\n                    break;\n                case \"Admin\":\n                    break;\n            }\n\n        }\n\n        boolean isUpdateNoticeOk = adminSysNoticeEntityService.saveOrUpdateBatch(adminSysNotices);\n        if (!isUpdateNoticeOk) {\n            log.error(\"=============推送系统通知更新状态失败===============\");\n        }\n\n    }\n\n    @Scheduled(cron = \"0 0/20 * * * ?\")\n    public void check20MinutesPendingSubmission() {\n        DateTime dateTime = DateUtil.offsetMinute(new Date(), -15);\n        String strTime = dateTime.toString(DatePattern.NORM_DATETIME_FORMAT);\n\n        QueryWrapper<Judge> judgeQueryWrapper = new QueryWrapper<>();\n        judgeQueryWrapper.select(\"distinct submit_id\");\n        judgeQueryWrapper.eq(\"status\", JudgeStatus.STATUS_PENDING.getStatus());\n        judgeQueryWrapper.apply(\"UNIX_TIMESTAMP('\" + strTime + \"') > UNIX_TIMESTAMP(gmt_modified)\");\n        List<Judge> judgeList = judgeEntityService.list(judgeQueryWrapper);\n        if (!CollectionUtils.isEmpty(judgeList)) {\n            log.info(\"Half An Hour Check Pending Submission to Rejudge:\" + Arrays.toString(judgeList.toArray()));\n            for (Judge judge : judgeList) {\n                rejudgeService.rejudge(judge.getSubmitId());\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/shiro/AccountProfile.java",
    "content": "package com.simplefanc.voj.backend.shiro;\n\nimport com.simplefanc.voj.common.pojo.entity.user.Role;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/19 22:59\n * @Description:\n */\n@Data\npublic class AccountProfile implements Serializable {\n\n    private String uid;\n\n    private String username;\n\n    private String password;\n\n    private String nickname;\n\n    private String school;\n\n    private String course;\n\n    private String number;\n\n    private String gender;\n\n    private String realname;\n\n    private String cfUsername;\n\n    private String email;\n\n    private String avatar;\n\n    private String signature;\n\n    private int status;\n\n    private Date gmtCreate;\n\n    private Date gmtModified;\n\n    private List<Role> roles;\n\n    /**\n     * shiro登录用户实体默认主键获取方法要为getId\n     *\n     * @return\n     */\n    public String getId() {\n        return uid;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/shiro/AccountRealm.java",
    "content": "package com.simplefanc.voj.backend.shiro;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport com.simplefanc.voj.backend.common.utils.JwtUtil;\nimport com.simplefanc.voj.backend.mapper.RoleAuthMapper;\nimport com.simplefanc.voj.backend.mapper.UserRoleMapper;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.common.pojo.entity.user.Auth;\nimport com.simplefanc.voj.common.pojo.entity.user.Role;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.shiro.authc.*;\nimport org.apache.shiro.authz.AuthorizationInfo;\nimport org.apache.shiro.authz.SimpleAuthorizationInfo;\nimport org.apache.shiro.realm.AuthorizingRealm;\nimport org.apache.shiro.subject.PrincipalCollection;\nimport org.springframework.stereotype.Component;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/19 22:57\n * @Description: 定义认证与授权的实现\n */\n@Slf4j(topic = \"voj\")\n@Component\n@RequiredArgsConstructor\npublic class AccountRealm extends AuthorizingRealm {\n\n    private final JwtUtil jwtUtil;\n\n    private final UserRoleMapper userRoleMapper;\n\n    private final RoleAuthMapper roleAuthMapper;\n\n    @Override\n    public boolean supports(AuthenticationToken token) {\n        return token instanceof JwtToken;\n    }\n\n    @Override\n    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {\n        AccountProfile user = (AccountProfile) principals.getPrimaryPrincipal();\n        // 角色权限列表\n        List<String> permissionsNameList = new LinkedList<>();\n        // 用户角色列表\n        List<String> roleNameList = new LinkedList<>();\n        // 获取该用户角色所有的权限\n        List<Role> roles = userRoleMapper.getRolesByUid(user.getUid());\n        // 角色变动，同时需要修改会话里面的数据\n        UserRolesVO userInfo = UserSessionUtil.getUserInfo();\n        userInfo.setRoles(roles);\n        UserSessionUtil.setUserInfo(userInfo);\n        for (Role role : roles) {\n            roleNameList.add(role.getRole());\n            for (Auth auth : roleAuthMapper.getRoleAuths(role.getId()).getAuths()) {\n                permissionsNameList.add(auth.getPermission());\n            }\n        }\n        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();\n\n        authorizationInfo.addRoles(roleNameList);\n        // 添加权限\n        authorizationInfo.addStringPermissions(permissionsNameList);\n        return authorizationInfo;\n    }\n\n    @Override\n    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {\n        JwtToken jwt = (JwtToken) token;\n        String userId = jwtUtil.getClaimByToken((String) jwt.getPrincipal());\n        UserRolesVO userRoles = userRoleMapper.getUserRoles(userId, null);\n        if (userRoles == null) {\n            throw new UnknownAccountException(\"账户不存在！\");\n        }\n        if (userRoles.getStatus() == 1) {\n            throw new LockedAccountException(\"该账户暂未开放，请联系管理员进行处理！\");\n        }\n        AccountProfile profile = new AccountProfile();\n        BeanUtil.copyProperties(userRoles, profile);\n        // 写入会话，后续不必重复查询\n        UserSessionUtil.setUserInfo(userRoles);\n        return new SimpleAuthenticationInfo(profile, jwt.getCredentials(), getName());\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/shiro/JwtFilter.java",
    "content": "package com.simplefanc.voj.backend.shiro;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.backend.common.utils.JwtUtil;\nimport com.simplefanc.voj.backend.common.utils.RedisUtil;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport com.simplefanc.voj.common.result.ResultStatus;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.shiro.authc.AuthenticationException;\nimport org.apache.shiro.authc.AuthenticationToken;\nimport org.apache.shiro.web.filter.authc.AuthenticatingFilter;\nimport org.apache.shiro.web.util.WebUtils;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/19 23:16\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class JwtFilter extends AuthenticatingFilter {\n\n    private final static String TOKEN_LOCK = \"token-lock:\";\n\n    private final JwtUtil jwtUtil;\n\n    private final RedisUtil redisUtil;\n\n    /**\n     * 拦截请求之后，用于把令牌字符串封装成令牌对象\n     *\n     * @param servletRequest\n     * @param servletResponse\n     * @return\n     */\n    @Override\n    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {\n        // 获取 token\n        HttpServletRequest request = (HttpServletRequest) servletRequest;\n        String jwt = request.getHeader(\"Authorization\");\n        if (StrUtil.isEmpty(jwt)) {\n            return null;\n        }\n        return new JwtToken(jwt);\n    }\n\n    /**\n     * 该方法用于处理所有应该被Shiro处理的请求\n     *\n     * @param servletRequest\n     * @param servletResponse\n     * @return\n     * @throws Exception\n     */\n    @Override\n    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {\n        HttpServletRequest request = (HttpServletRequest) servletRequest;\n        String token = request.getHeader(\"Authorization\");\n        if (StrUtil.isEmpty(token)) {\n            return true;\n        }\n        if (!jwtUtil.verifyToken(token)) {\n            return true;\n        }\n        String userId = jwtUtil.getClaimByToken(token);\n        if (!redisUtil.hasKey(JwtUtil.TOKEN_REFRESH + userId) && redisUtil.hasKey(JwtUtil.TOKEN_KEY + userId)) {\n            // 过了需更新token时间，但是还未过期，则进行token刷新\n            HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;\n            HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;\n            this.refreshToken(httpRequest, httpResponse, userId);\n        }\n        // 执行自动登录\n        return executeLogin(servletRequest, servletResponse);\n    }\n\n    /**\n     * 刷新Token，并更新token到前端\n     *\n     * @param request\n     * @param userId\n     * @param response\n     * @return\n     */\n    private void refreshToken(HttpServletRequest request, HttpServletResponse response, String userId) {\n        // 获取锁20s\n        boolean lock = redisUtil.getLock(TOKEN_LOCK + userId, 20);\n        if (lock) {\n            String newToken = jwtUtil.generateToken(userId);\n            response.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n            // 放到信息头部\n            response.setHeader(\"Authorization\", newToken);\n            // 让前端可用访问\n            response.setHeader(\"Access-Control-Expose-Headers\", \"Refresh-Token,Authorization,Url-Type\");\n            // 为了前端能区别请求来源\n            response.setHeader(\"Url-Type\", request.getHeader(\"Url-Type\"));\n            // 告知前端需要刷新token\n            response.setHeader(\"Refresh-Token\", \"true\");\n        }\n        redisUtil.releaseLock(TOKEN_LOCK + userId);\n    }\n\n    @Override\n    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,\n                                     ServletResponse response) {\n        HttpServletResponse httpResponse = (HttpServletResponse) response;\n        HttpServletRequest httpRequest = (HttpServletRequest) request;\n        try {\n            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);\n            httpResponse.setContentType(\"application/json;charset=utf-8\");\n            // 让前端可用访问\n            httpResponse.setHeader(\"Access-Control-Expose-Headers\", \"Refresh-Token,Authorization,Url-Type\");\n            httpResponse.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n            // 为了前端能区别请求来源\n            httpResponse.setHeader(\"Url-Type\", httpRequest.getHeader(\"Url-Type\"));\n\n            // 处理登录失败的异常\n            Throwable throwable = e.getCause() == null ? e : e.getCause();\n            CommonResult<Void> result = CommonResult.errorResponse(throwable.getMessage(), ResultStatus.ACCESS_DENIED);\n            httpResponse.getWriter().print(JSONUtil.toJsonStr(result));\n        } catch (IOException exception) {\n        }\n        return false;\n    }\n\n    /**\n     * 对跨域提供支持\n     */\n    @Override\n    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {\n        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);\n        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);\n        httpServletResponse.setHeader(\"Access-control-Allow-Origin\", httpServletRequest.getHeader(\"Origin\"));\n        httpServletResponse.setHeader(\"Access-Control-Allow-Methods\", \"GET,POST,OPTIONS,PUT,DELETE\");\n        httpServletResponse.setHeader(\"Access-Control-Allow-Headers\",\n                httpServletRequest.getHeader(\"Access-Control-Request-Headers\"));\n        // 让前端可用访问\n        httpServletResponse.setHeader(\"Access-Control-Expose-Headers\",\n                \"Refresh-Token,Authorization,Url-Type,Content-disposition,Content-Type\");\n        // 跨域时会首先发送一个OPTIONS请求，这里我们给OPTIONS请求直接返回正常状态\n        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {\n            httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());\n            return false;\n        }\n        return super.preHandle(request, response);\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/shiro/JwtToken.java",
    "content": "package com.simplefanc.voj.backend.shiro;\n\nimport org.apache.shiro.authc.AuthenticationToken;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/19 22:58\n * @Description:\n */\npublic class JwtToken implements AuthenticationToken {\n\n    private String token;\n\n    public JwtToken(String token) {\n        this.token = token;\n    }\n\n    @Override\n    public Object getPrincipal() {\n        return token;\n    }\n\n    @Override\n    public Object getCredentials() {\n        return token;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/shiro/UserSessionUtil.java",
    "content": "package com.simplefanc.voj.backend.shiro;\n\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport org.apache.shiro.SecurityUtils;\nimport org.apache.shiro.session.Session;\n\n/**\n * @author chenfan\n * @date 2022/4/13 11:29\n **/\npublic class UserSessionUtil {\n\n    public static Boolean isProblemAdmin() {\n        return SecurityUtils.getSubject().hasRole(\"problem_admin\");\n    }\n\n    public static Boolean isRoot() {\n        return SecurityUtils.getSubject().hasRole(\"root\");\n    }\n\n    public static Boolean isAdmin() {\n        return SecurityUtils.getSubject().hasRole(\"admin\");\n    }\n\n    public static UserRolesVO getUserInfo() {\n        return (UserRolesVO) getSessionAttribute(\"userInfo\");\n    }\n\n    public static void logout() {\n        SecurityUtils.getSubject().logout();\n    }\n\n    public static void setUserInfo(UserRolesVO userInfo) {\n        setSessionAttribute(\"userInfo\", userInfo);\n    }\n\n    private static Session getSession() {\n        return SecurityUtils.getSubject().getSession();\n    }\n\n    private static void setSessionAttribute(Object key, Object value) {\n        getSession().setAttribute(key, value);\n    }\n\n    private static Object getSessionAttribute(Object key) {\n        return getSession().getAttribute(key);\n    }\n\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/validator/AccessInterceptor.java",
    "content": "package com.simplefanc.voj.backend.validator;\n\nimport com.simplefanc.voj.backend.common.annotation.Access;\nimport com.simplefanc.voj.backend.common.constants.AccessEnum;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.core.annotation.AnnotationUtils;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.HandlerInterceptor;\nimport org.springframework.web.servlet.ModelAndView;\nimport org.springframework.web.servlet.resource.ResourceHttpRequestHandler;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Method;\n\n/**\n * @Author chenfan\n * @Date 2022/5/9\n */\n@Component\n@RequiredArgsConstructor\npublic class AccessInterceptor implements HandlerInterceptor {\n\n    private final AccessValidator accessValidator;\n\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        if(handler instanceof HandlerMethod) {\n            HandlerMethod handlerMethod = HandlerMethod.class.cast(handler);\n            Access access = this.getAnnotation(handlerMethod.getMethod(), handlerMethod.getBeanType(), Access.class);\n            if (access == null || access.value().length == 0) {\n                return true;\n            }\n            for (AccessEnum value : access.value()) {\n                accessValidator.validateAccess(value);\n            }\n            return true;\n        } else if (handler instanceof ResourceHttpRequestHandler) {\n            // 静态资源的请求不处理\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {\n        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);\n    }\n\n    @Override\n    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {\n        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);\n    }\n\n    /**\n     * 先从method上获取注解，获取不到再从class上获取\n     *\n     * @param method\n     * @param clazz\n     * @param annotationClass\n     * @param <T>\n     * @return 注解对象\n     */\n    public <T extends Annotation> T getAnnotation(Method method, Class<?> clazz, Class<T> annotationClass) {\n        T annotation = AnnotationUtils.getAnnotation(method, annotationClass);\n        if (annotation == null) {\n            annotation = AnnotationUtils.findAnnotation(clazz, annotationClass);\n        }\n        return annotation;\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/validator/AccessValidator.java",
    "content": "package com.simplefanc.voj.backend.validator;\n\nimport com.simplefanc.voj.backend.common.constants.AccessEnum;\nimport com.simplefanc.voj.backend.common.exception.StatusAccessDeniedException;\nimport com.simplefanc.voj.backend.config.ConfigVO;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\n/**\n * @Author chenfan\n * @Date 2022/9/25\n */\n@Component\n@RequiredArgsConstructor\npublic class AccessValidator {\n\n    private final ConfigVO configVO;\n\n    public void validateAccess(AccessEnum accessEnum) throws StatusAccessDeniedException {\n        switch (accessEnum) {\n//            case PUBLIC_DISCUSSION:\n//                if (!configVO.getOpenPublicDiscussion()) {\n//                    throw new StatusAccessDeniedException(\"网站当前未开启公开讨论区的功能，不可访问！\");\n//                }\n//                break;\n//            case CONTEST_COMMENT:\n//                if (!configVO.getOpenContestComment()) {\n//                    throw new StatusAccessDeniedException(\"网站当前未开启比赛评论区的功能，不可访问！\");\n//                }\n//                break;\n            case PUBLIC_JUDGE:\n                if (!configVO.getOpenPublicJudge()) {\n                    throw new StatusAccessDeniedException(\"网站当前未开启题目评测的功能，禁止提交或调试！\");\n                }\n                break;\n            case CONTEST_JUDGE:\n                if (!configVO.getOpenContestJudge()) {\n                    throw new StatusAccessDeniedException(\"网站当前未开启比赛题目评测的功能，禁止提交或调试！\");\n                }\n                break;\n            default:\n        }\n    }\n}\n"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/validator/ContestValidator.java",
    "content": "package com.simplefanc.voj.backend.validator;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.dao.contest.ContestRegisterEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRegister;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 20:06\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class ContestValidator {\n\n    private final ContestRegisterEntityService contestRegisterEntityService;\n\n    public boolean isContestAdmin(Contest contest) {\n        // 超级管理员或比赛拥有者\n        return UserSessionUtil.isRoot() || isContestOwner(contest.getUid());\n    }\n\n    public boolean isContestOwner(String uid) {\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n        if(userRolesVO == null) {\n            return false;\n        }\n        return uid.equals(userRolesVO.getUid());\n    }\n\n    public boolean isOpenSealRank(Contest contest, Boolean forceRefresh) {\n        // 如果是管理员同时选择强制刷新榜单，则封榜无效\n        if (forceRefresh && isContestAdmin(contest)) {\n            return false;\n        } else if (contest.getSealRank() && contest.getSealRankTime() != null) {\n            // 该比赛开启封榜模式\n            Date now = new Date();\n            // 如果现在时间处于封榜开始到比赛结束之间\n            if (now.after(contest.getSealRankTime()) && now.before(contest.getEndTime())) {\n                return true;\n            }\n            // 或者没有开启赛后自动解除封榜，不可刷新榜单\n            return !contest.getAutoRealRank() && now.after(contest.getEndTime());\n        }\n        return false;\n    }\n\n    public boolean checkVisible(Contest contest) {\n        if (contest == null) {\n            return false;\n        }\n        if (contest.getContestAdminVisible() && isContestAdmin(contest)) {\n            return true;\n        }\n        return contest.getVisible();\n    }\n\n    /**\n     * @param contest\n     * @MethodName validateContestAuth\n     * @Description 需要对该比赛做判断，是否处于开始或结束状态才可以获取，同时若是私有赛需要判断是否已注册（比赛管理员包括超级管理员可以直接获取）\n     * @Since 2021/1/17\n     */\n    public void validateContestAuth(Contest contest) {\n        if (!checkVisible(contest)) {\n            throw new StatusFailException(\"对不起，该比赛不存在！\");\n        }\n        if (isContestAdmin(contest)) {\n            return;\n        }\n        // 若不是比赛管理者\n        // 判断一下比赛的状态，还未开始不能访问\n        if (contest.getStatus().intValue() != ContestEnum.STATUS_RUNNING.getCode()\n                && contest.getStatus().intValue() != ContestEnum.STATUS_ENDED.getCode()) {\n            throw new StatusForbiddenException(\"比赛还未开始，您无权访问该比赛！\");\n        }\n\n        // 如果是处于比赛正在进行阶段，需要判断该场比赛是否为私有赛，私有赛需要判断该用户是否已注册\n        if (contest.getAuth().intValue() == ContestEnum.AUTH_PRIVATE.getCode()) {\n            QueryWrapper<ContestRegister> registerQueryWrapper = new QueryWrapper<>();\n            registerQueryWrapper.eq(\"cid\", contest.getId()).eq(\"uid\", UserSessionUtil.getUserInfo().getUid());\n            ContestRegister register = contestRegisterEntityService.getOne(registerQueryWrapper);\n            // 如果数据为空，表示未注册私有赛，不可访问\n            if (register == null) {\n                throw new StatusForbiddenException(\"对不起，请先到比赛首页输入比赛密码进行注册！\");\n            }\n\n            if (contest.getOpenAccountLimit()\n                    && !validateAccountRule(contest.getAccountLimitRule(), UserSessionUtil.getUserInfo().getUsername())) {\n                throw new StatusForbiddenException(\"对不起！本次比赛只允许特定账号规则的用户参赛！\");\n            }\n        }\n    }\n\n    public void validateJudgeAuth(Contest contest) {\n        String uid = UserSessionUtil.getUserInfo().getUid();\n        if (contest.getAuth().intValue() == ContestEnum.AUTH_PRIVATE.getCode()\n                || contest.getAuth().intValue() == ContestEnum.AUTH_PROTECT.getCode()) {\n            QueryWrapper<ContestRegister> queryWrapper = new QueryWrapper<>();\n            queryWrapper.eq(\"cid\", contest.getId()).eq(\"uid\", uid);\n            ContestRegister register = contestRegisterEntityService.getOne(queryWrapper, false);\n            // 如果还没注册\n            if (register == null) {\n                throw new StatusForbiddenException(\"对不起，请你先注册该比赛，提交代码失败！\");\n            }\n        }\n    }\n\n    public boolean validateAccountRule(String accountRule, String username) {\n\n        String prefix = ReUtil.get(\"<prefix>([\\\\s\\\\S]*?)</prefix>\", accountRule, 1);\n        String suffix = ReUtil.get(\"<suffix>([\\\\s\\\\S]*?)</suffix>\", accountRule, 1);\n        String start = ReUtil.get(\"<start>([\\\\s\\\\S]*?)</start>\", accountRule, 1);\n        String end = ReUtil.get(\"<end>([\\\\s\\\\S]*?)</end>\", accountRule, 1);\n        String extra = ReUtil.get(\"<extra>([\\\\s\\\\S]*?)</extra>\", accountRule, 1);\n\n        int startNum = Integer.parseInt(start);\n        int endNum = Integer.parseInt(end);\n\n        String formatString = \"%0\" + String.valueOf(endNum).length() + \"d\";\n        for (int i = startNum; i <= endNum; i++) {\n            String paddedNum = String.format(formatString, i);\n            if (username.equals(prefix + paddedNum + suffix)) {\n                return true;\n            }\n        }\n        // 额外账号列表\n        if (StrUtil.isNotEmpty(extra)) {\n            String[] accountList = extra.trim().split(\"\\n\");\n            for (String account : accountList) {\n                if (username.equals(account)) {\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/validator/JudgeValidator.java",
    "content": "package com.simplefanc.voj.backend.validator;\n\nimport com.simplefanc.voj.backend.common.constants.AccessEnum;\nimport com.simplefanc.voj.backend.common.exception.StatusFailException;\nimport com.simplefanc.voj.backend.pojo.dto.ToJudgeDTO;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/11 11:20\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class JudgeValidator {\n\n    private final AccessValidator accessValidator;\n\n    private final static List<String> VOJ_LANGUAGE_LIST = Arrays.asList(\"C++\", \"C++ With O2\", \"C\", \"C With O2\",\n            \"Python3\", \"Python2\", \"Java\", \"Golang\", \"C#\", \"PHP\", \"PyPy2\", \"PyPy3\", \"JavaScript Node\", \"JavaScript V8\");\n\n    public void validateSubmissionInfo(ToJudgeDTO toJudgeDTO) {\n        if (toJudgeDTO.getCid() != null && toJudgeDTO.getCid() != 0) {\n            accessValidator.validateAccess(AccessEnum.CONTEST_JUDGE);\n        } else {\n            accessValidator.validateAccess(AccessEnum.PUBLIC_JUDGE);\n        }\n\n        if (!toJudgeDTO.getIsRemote() && !VOJ_LANGUAGE_LIST.contains(toJudgeDTO.getLanguage())) {\n            throw new StatusFailException(\"提交的代码的语言错误！请使用\" + VOJ_LANGUAGE_LIST + \"中之一的语言！\");\n        }\n\n        if (toJudgeDTO.getCode().length() < 50 && !toJudgeDTO.getLanguage().contains(\"Py\")\n                && !toJudgeDTO.getLanguage().contains(\"PHP\") && !toJudgeDTO.getLanguage().contains(\"JavaScript\")) {\n            throw new StatusFailException(\"提交的代码是无效的，代码字符长度请不要低于50！\");\n        }\n\n        if (toJudgeDTO.getCode().length() > 65535) {\n            throw new StatusFailException(\"提交的代码是无效的，代码字符长度请不要超过65535！\");\n        }\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/java/com/simplefanc/voj/backend/validator/TrainingValidator.java",
    "content": "package com.simplefanc.voj.backend.validator;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.backend.common.constants.TrainingEnum;\nimport com.simplefanc.voj.backend.common.exception.StatusAccessDeniedException;\nimport com.simplefanc.voj.backend.common.exception.StatusForbiddenException;\nimport com.simplefanc.voj.backend.dao.training.TrainingRegisterEntityService;\nimport com.simplefanc.voj.backend.pojo.vo.UserRolesVO;\nimport com.simplefanc.voj.backend.shiro.UserSessionUtil;\nimport com.simplefanc.voj.common.pojo.entity.training.Training;\nimport com.simplefanc.voj.common.pojo.entity.training.TrainingRegister;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/21 20:55\n * @Description:\n */\n@Component\n@RequiredArgsConstructor\npublic class TrainingValidator {\n\n    private final TrainingRegisterEntityService trainingRegisterEntityService;\n\n    public void validateTrainingAuth(Training training) {\n\n        // 是否为超级管理员\n        boolean isRoot = UserSessionUtil.isRoot();\n        UserRolesVO userRolesVO = UserSessionUtil.getUserInfo();\n\n        if (TrainingEnum.AUTH_PRIVATE.getValue().equals(training.getAuth())) {\n\n            if (userRolesVO == null) {\n                throw new StatusAccessDeniedException(\"该训练属于私有题单，请先登录以校验权限！\");\n            }\n            // 是否为该私有训练的创建者\n            boolean isAuthor = training.getAuthor().equals(userRolesVO.getUsername());\n\n            if (isRoot || isAuthor) {\n                return;\n            }\n\n            // 如果三者都不是，需要做注册权限校验\n            checkTrainingRegister(training.getId(), userRolesVO.getUid());\n        }\n    }\n\n    private void checkTrainingRegister(Long tid, String uid) {\n        QueryWrapper<TrainingRegister> trainingRegisterQueryWrapper = new QueryWrapper<>();\n        trainingRegisterQueryWrapper.eq(\"tid\", tid);\n        trainingRegisterQueryWrapper.eq(\"uid\", uid);\n        TrainingRegister trainingRegister = trainingRegisterEntityService.getOne(trainingRegisterQueryWrapper, false);\n\n        if (trainingRegister == null) {\n            throw new StatusAccessDeniedException(\"该训练属于私有，请先使用专属密码注册！\");\n        }\n\n        if (!trainingRegister.getStatus()) {\n            throw new StatusForbiddenException(\"错误：你已被禁止参加该训练！\");\n        }\n    }\n\n    public boolean isInTrainingOrAdmin(Training training, UserRolesVO userRolesVO) {\n        if (TrainingEnum.AUTH_PRIVATE.getValue().equals(training.getAuth())) {\n            if (userRolesVO == null) {\n                throw new StatusAccessDeniedException(\"该训练属于私有题单，请先登录以校验权限！\");\n            }\n            // 是否为超级管理员\n            boolean isRoot = UserSessionUtil.isRoot();\n            // 是否为该私有训练的创建者\n            boolean isAuthor = training.getAuthor().equals(userRolesVO.getUsername());\n\n            if (isRoot || isAuthor) {\n                return true;\n            }\n\n            // 如果三者都不是，需要做注册权限校验\n            QueryWrapper<TrainingRegister> trainingRegisterQueryWrapper = new QueryWrapper<>();\n            trainingRegisterQueryWrapper.eq(\"tid\", training.getId());\n            trainingRegisterQueryWrapper.eq(\"uid\", userRolesVO.getUid());\n            TrainingRegister trainingRegister = trainingRegisterEntityService.getOne(trainingRegisterQueryWrapper,\n                    false);\n\n            return trainingRegister != null && trainingRegister.getStatus();\n\n        }\n        return true;\n    }\n\n}"
  },
  {
    "path": "voj-backend/src/main/resources/application.yml",
    "content": "voj-backend:\n  version: 2023/10/14\n  ip: ${BACKEND_SERVER_IP:127.0.0.1}\n  port: ${BACKEND_SERVER_PORT:6688}\nserver:\n  port: ${voj-backend.port}\n  servlet:\n    encoding:\n      force: true\n\nspring:\n  application:\n    name: @artifactId@\n  cloud:\n    nacos:\n      discovery:\n        server-addr: ${NACOS_URL:127.0.0.1:8848}\n        username: ${NACOS_USERNAME:nacos}\n        password: ${NACOS_PASSWORD:nacos}\n      config:\n        group: DEFAULT_GROUP\n        server-addr: ${spring.cloud.nacos.discovery.server-addr}\n        username: ${NACOS_USERNAME:nacos}\n        password: ${NACOS_PASSWORD:nacos}\n  config:\n    import:\n      - nacos:voj-${spring.profiles.active}.yml\n      - nacos:voj-remote.yml\n  profiles:\n    active: @profiles.active@\n\n  # 配置文件上传限制\n  servlet:\n    multipart:\n      max-file-size: 256MB\n      max-request-size: 256MB\n  redis:\n    host: ${voj.redis.host}\n    port: ${voj.redis.port}\n    password:\n    # 连接超时时间（毫秒）\n    timeout: 3000\n    pool:\n      # 连接池最大连接数（使用负值表示没有限制）\n      max-active: 200\n      # 连接池最大阻塞等待时间（使用负值表示没有限制）\n      max-wait: -1\n      # 连接池中的最大空闲连接\n      max-idle: 50\n      # 连接池中的最小空闲连接\n      min-idle: 10\n  datasource:\n    username: ${voj.db.username}\n    password: ${voj.db.password}\n    url: jdbc:mysql://${voj.db.host}:${voj.db.port}/${voj.db.name}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true\n    driver-class-name: com.mysql.cj.jdbc.Driver\n    type: com.alibaba.druid.pool.DruidDataSource\n    initial-size: 10 # 初始化时建立物理连接的个数。初始化发生在显示调用init方法，或者第一次getConnection时\n    min-idle: 20 # 最小连接池数量\n    maxActive: 200 # 最大连接池数量\n    maxWait: 60000 # 获取连接时最大等待时间，单位毫秒。配置了maxWait之后，缺省启用公平锁，并发效率会有所下降，如果需要可以通过配置\n    timeBetweenEvictionRunsMillis: 60000 # 关闭空闲连接的检测时间间隔.Destroy线程会检测连接的间隔时间，如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。\n    minEvictableIdleTimeMillis: 300000 # 连接的最小生存时间.连接保持空闲而不被驱逐的最小时间\n    validationQuery: SELECT 1 FROM DUAL # 验证数据库服务可用性的sql.用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL\n    testWhileIdle: true # 申请连接时检测空闲时间，根据空闲时间再检测连接是否有效.建议配置为true，不影响性能，并且保证安全性。申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRun\n    testOnBorrow: false # 申请连接时直接检测连接是否有效.申请连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。\n    testOnReturn: false # 归还连接时检测连接是否有效.归还连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。\n    poolPreparedStatements: true # 开启PSCache\n    maxPoolPreparedStatementPerConnectionSize: 20 #设置PSCache值\n    connectionErrorRetryAttempts: 3 # 连接出错后再尝试连接三次\n    breakAfterAcquireFailure: true # 数据库服务宕机自动重连机制\n    timeBetweenConnectErrorMillis: 300000 # 连接出错后重试时间间隔\n    asyncInit: true # 异步初始化策略\n    remove-abandoned: true # 是否自动回收超时连接\n    remove-abandoned-timeout: 1800 # 超时时间(以秒数为单位)\n    transaction-query-timeout: 6000 # 事务超时时间\n    filters: stat,wall,log4j #数据库日志\n    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500\n  thymeleaf:\n    encoding: UTF-8\n\nshiro-redis:\n  enabled: true\n  redis-manager:\n    host: ${voj.redis.host}:${voj.redis.port}\n    password:\n\nmybatis-plus:\n  mapper-locations: classpath*:com/simplefanc/voj/backend/mapper/xml/**Mapper.xml\n  type-aliases-package: com.simplefanc.voj.common.pojo.entity\n  configuration:\n    cache-enabled: false\n  # 关闭打印 mybatis-plus 的 LOGO\n  global-config:\n    banner: false\n\nlogging:\n  level:\n    com.alibaba.nacos: error\n    root: info\n  #  config: classpath:logback-spring.xml\n  file:\n    path: ./log\n\n# 消费者将要去访问的微服务名称（注册成功进入nacos的微服务提供者）\nservice-url:\n  name: voj-judger # 服务名\n\nfilepath:\n  testcase-base-folder: \"/voj/testcase\"\n  user-avatar-folder: \"/voj/file/avatar\"\n  home-carousel-folder: \"/voj/file/carousel\"\n  markdown-file-folder: \"/voj/file/md\"\n  problem-file-folder: \"/voj/file/problem\"\n  contest-text-print-folder: \"/voj/file/contest_print\"\n  testcase-tmp-folder: \"/voj/file/zip\"\n  file-download-tmp-folder: \"/voj/file/zip/download\"\n  contest-ac-submission-tmp-folder: \"/voj/file/zip/contest_ac\"\n  img-api: \"/api/public/img/\"\n  file-api: \"/api/public/file/\"\n\nvoj:\n  cache:\n    allowNull: true\n    initialCapacity: 128\n    maximumSize: 1024\n    # Caffeine过期时间\n    expireAfterWrite: 30\n    # Redis缓存过期时间\n    redisExpire: 60\n\n---\nspring:\n  config:\n    activate:\n      on-profile: dev\nfilepath:\n  testcase-base-folder: \"~/voj/testcase\"\n  user-avatar-folder: \"~/voj/file/avatar\"\n  home-carousel-folder: \"~/voj/file/carousel\"\n  markdown-file-folder: \"~/voj/file/md\"\n  problem-file-folder: \"~/voj/file/problem\"\n  contest-text-print-folder: \"~/voj/file/contest_print\"\n  testcase-tmp-folder: \"~/voj/file/zip\"\n  file-download-tmp-folder: \"~/voj/file/zip/download\"\n  contest-ac-submission-tmp-folder: \"~/voj/file/zip/contest_ac\"\n  img-api: \"~/api/public/img/\"\n  file-api: \"~/api/public/file/\""
  },
  {
    "path": "voj-backend/src/main/resources/banner.txt",
    "content": "${AnsiColor.BRIGHT_YELLOW}\n\n ___      ___ ________        ___\n|\\  \\    /  /|\\   __  \\      |\\  \\\n\\ \\  \\  /  / | \\  \\|\\  \\     \\ \\  \\\n \\ \\  \\/  / / \\ \\  \\\\\\  \\  __ \\ \\  \\\n  \\ \\    / /   \\ \\  \\\\\\  \\|\\  \\\\_\\  \\\n   \\ \\__/ /     \\ \\_______\\ \\________\\\n    \\|__|/       \\|_______|\\|________|\n    Virtual Online Judge(VOJ) - Backend\n           @Author chenfan\n           @Latest Update ${voj-backend.version}"
  },
  {
    "path": "voj-backend/src/main/resources/email-rule.yml",
    "content": "# 邮箱地址黑名单\nvoj:\n  blacklist:\n    - \"@ccmail.uk\"\n    - \"@exdonuts.com\"\n    - \"@hamham.uk\"\n    - \"@digdig.org\"\n    - \"@owleyes.ch\"\n    - \"@stayhome.li\"\n    - \"@fanclub.pm\"\n    - \"@hotsoup.be\"\n    - \"@simaenaga.com\"\n    - \"@tapi.re\"\n    - \"@fuwari.be\"\n    - \"@magim.be\"\n    - \"@mirai.re\"\n    - \"@moimoi.re\"\n    - \"@heisei.be\"\n    - \"@honeys.be\"\n    - \"@mbox.re\"\n    - \"@uma3.be\"\n    - \"@fuwa.li\"\n    - \"@kpost.be\"\n    - \"@risu.be\"\n    - \"@fuwa.be\"\n    - \"@usako.net\"\n    - \"@eay.jp\"\n    - \"@via.tokyo.jp\"\n    - \"@ichigo.me\"\n    - \"@choco.la\"\n    - \"@cream.pink\"\n    - \"@merry.pink\"\n    - \"@neko2.net\"\n    - \"@fuwamofu.com\"\n    - \"@ruru.be\"\n    - \"@macr2.com\"\n    - \"@f5.si\"\n    - \"@ahk.jp\"\n    - \"@svk.jp\""
  },
  {
    "path": "voj-backend/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<configuration>\n    <!-- 属性文件:在properties文件中找到对应的配置项 -->\n    <springProperty scope=\"context\" name=\"logging.file.path\" source=\"logging.file.path\"/>\n    <springProperty scope=\"context\" name=\"spring.application.name\" source=\"spring.application.name\"/>\n\n    <!--控制台打印-->\n    <appender name=\"consoleLog\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder class=\"ch.qos.logback.classic.encoder.PatternLayoutEncoder\">\n            <!--格式化输出（配色）：%d表示日期，%thread表示线程名，%-5level：级别从左显示5个字符宽度%msg：日志消息，%n是换行符-->\n            <pattern>\n                %yellow(%d{yyyy-MM-dd HH:mm:ss}) %red([%thread]) %highlight(%-5level) %cyan(%logger{50}) - %magenta(%msg) %n\n            </pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n    </appender>\n\n    <!--根据日志级别分离日志，分别输出到不同的文件-->\n    <appender name=\"fileInfoLog\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>error</level>\n            <onMatch>DENY</onMatch>\n            <onMismatch>ACCEPT</onMismatch>\n        </filter>\n        <encoder class=\"ch.qos.logback.classic.encoder.PatternLayoutEncoder\">\n            <pattern>\n                %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n\n            </pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n        <!--滚动策略-->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <!--按时间保存日志 修改格式可以按小时、按天、月来保存-->\n            <fileNamePattern>${logging.file.path}/${spring.application.name}.info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <!--保存时长-->\n            <maxHistory>7</maxHistory>\n            <!-- 单个文件最大-->\n            <maxFileSize>200MB</maxFileSize>\n            <!--总大小-->\n            <totalSizeCap>1GB</totalSizeCap>\n        </rollingPolicy>\n    </appender>\n\n    <appender name=\"fileErrorLog\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>error</level>\n        </filter>\n        <encoder>\n            <pattern>\n                %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n\n            </pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n        <!--滚动策略-->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <!--路径-->\n            <fileNamePattern>${logging.file.path}/${spring.application.name}.error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <!--保存时长-->\n            <maxHistory>7</maxHistory>\n            <!-- 单个文件最大-->\n            <maxFileSize>200MB</maxFileSize>\n            <!--总大小-->\n            <totalSizeCap>1GB</totalSizeCap>\n        </rollingPolicy>\n    </appender>\n\n    <root level=\"info\">\n        <appender-ref ref=\"consoleLog\"/>\n        <appender-ref ref=\"fileInfoLog\"/>\n        <appender-ref ref=\"fileErrorLog\"/>\n    </root>\n\n    <root level=\"error\">\n        <appender-ref ref=\"consoleLog\"/>\n        <appender-ref ref=\"fileErrorLog\"/>\n    </root>\n\n</configuration>"
  },
  {
    "path": "voj-backend/src/main/resources/templates/emailTemplate_registerCode.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh\" xmlns:th=http://www.thymeleaf.org>\n<head>\n    <meta charset=\"UTF-8\">\n    <title>[[${OJ_SHORT_NAME}]]用户注册验证码</title>\n</head>\n<body>\n<div style=\"background: white;\n               width: 100%;      \n               max-width: 800px;      \n               margin: auto auto;      \n               border-radius: 5px;      \n               border:#1bc3fb 1px solid;      \n               overflow: hidden;      \n               -webkit-box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.12);      \n               box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.18);\">\n    <!--            <header style=\"overflow: hidden;\">-->\n    <!--                <center>-->\n    <!--                    <img style=\"width:100%;z-index: 666;\"-->\n    <!--                         th:src=\"${EMAIL_BACKGROUND_IMG}\">-->\n    <!--                </center>-->\n    <!--            </header>-->\n    <div style=\"padding: 5px 20px; \">\n        <p style=\" position: relative;\n                 color: white;      \n                 float: left;      \n                 z-index: 999;      \n                 background: #1bc3fb;      \n                 padding: 5px 30px;      \n                 margin: 0;\n                 box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.30) \">\n            Dear New [[${OJ_SHORT_NAME}]]er\n        </p>\n        <br>\n        <center>\n            <h3>\n                来自 <span style=\" text-decoration: none;color: #FF779A;\" th:text=\"${OJ_SHORT_NAME}\"></span> 邮件提醒\n            </h3>\n            <p style=\"text-indent:2em;\">\n                您收到这封电子邮件是因为您 (也可能是某人冒充您的名义) 在<a rel=\"noopener\"\n                                                 style=\" text-decoration: none;color: #1bc3fb \" target=\"_blank\"\n                                                 th:href=\"${OJ_URL}\">&nbsp;[[${OJ_SHORT_NAME}]]&nbsp;</a>上进行注册。假如这不是您本人所申请,\n                请不用理会这封电子邮件, 但是如果您持续收到这类的信件骚扰, 请您尽快联络管理员。\n            </p>\n            <div style=\"background: #fafafa repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);margin:20px 0px;padding:15px;border-radius:5px;font-size:14px;color:#555555;text-align: center; \">\n                请使用以下验证码完成后续注册流程：<br>\n                <span style=\" color: #FF779A;font-weight: bolder;font-size: 25px;\" th:text=\"${CODE}\"></span><br>\n                注意:请您在收到邮件10分钟内([[${EXPIRE_TIME}]]前)使用，否则该验证码将会失效。\n            </div>\n            &nbsp; &nbsp;\n\n            <br>\n            <div style=\" text-align: center; \">\n                <a rel=\"noopener\"\n                   style=\"text-transform: uppercase;\n                               text-decoration: none;\n                               font-size: 14px;\n                               background: #FF779A;\n                               color: #FFFFFF;\n                               padding: 10px;\n                               display: inline-block;\n                               border-radius: 5px;\n                               margin: 10px auto 0;\" target=\"_blank\" th:href=\"${OJ_URL}\"\n                   th:text=\"${OJ_SHORT_NAME}+'｜传送门🚪'\"></a>\n            </div>\n            <p style=\"font-size: 12px;text-align: center;color: #999; \">\n                欢迎常来访问！<br>\n                © 2021 <a rel=\"noopener\" style=\"text-decoration:none; color:#1bc3fb \" target=\"_blank\"\n                          th:href=\"${OJ_URL}\" th:text=\"${OJ_NAME}\"></a>\n            </p>\n            <p></p>\n        </center>\n    </div>\n</div>\n</body>\n</html>"
  },
  {
    "path": "voj-backend/src/main/resources/templates/emailTemplate_resetPassword.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh\" xmlns:th=http://www.thymeleaf.org>\n<head>\n    <meta charset=\"UTF-8\">\n    <title>[[${OJ_SHORT_NAME}]]的重置密码邮件</title>\n</head>\n<body>\n<div style=\"background: white;\n               width: 100%;\n               max-width: 800px;\n               margin: auto auto;\n               border-radius: 5px;\n               border:#1bc3fb 1px solid;\n               overflow: hidden;\n               -webkit-box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.12);\n               box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.18);\">\n    <!--            <header style=\"overflow: hidden;\">-->\n    <!--                <center>-->\n    <!--                    <img style=\"width:100%;z-index: 666;\"-->\n    <!--                         th:src=\"${EMAIL_BACKGROUND_IMG}\">-->\n    <!--                </center>-->\n    <!--            </header>-->\n    <div style=\"padding: 5px 20px; \">\n        <p style=\" position: relative;\n                 color: white;\n                 float: left;\n                 z-index: 999;\n                 background: #1bc3fb;\n                 padding: 5px 30px;\n                 margin: 0;\n                 box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.30) \" th:text=\"'Dear '+${USERNAME}\">\n        </p>\n        <br>\n        <center>\n            <h3>\n                来自 <span style=\" text-decoration: none;color: #FF779A;\" th:text=\"${OJ_SHORT_NAME}\"></span> 邮件提醒\n            </h3>\n            <p style=\"text-indent:2em;\">\n                您收到这封电子邮件是因为您 (也可能是某人冒充您的名义) 在<a rel=\"noopener\"\n                                                 style=\" text-decoration: none;color: #1bc3fb \" target=\"_blank\"\n                                                 th:href=\"${OJ_URL}\">&nbsp;[[${OJ_SHORT_NAME}]]&nbsp;</a>上进行密码重置操作。假如这不是您本人所申请,\n                请不用理会这封电子邮件, 但是如果您持续收到这类的信件骚扰, 请您尽快联络管理员。\n            </p>\n            <div style=\"background: #fafafa repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);margin:20px 0px;padding:15px;border-radius:5px;font-size:14px;color:#555555;text-align: center; \">\n                请点击下面的链接完成后续重置密码的流程：<br>\n                <a style=\"color: #FF779A;font-weight: bolder;font-size: 25px;text-decoration: none;\"\n                   th:href=\"${RESET_URL}\">CLICK HERE</a><br>\n                注意:请您在收到邮件10分钟内([[${EXPIRE_TIME}]]前)使用，否则该重置密码链接将会失效。\n            </div>\n            &nbsp; &nbsp;\n\n            <br>\n            <div style=\" text-align: center; \">\n                <a rel=\"noopener\"\n                   style=\"text-transform: uppercase;\n                               text-decoration: none;\n                               font-size: 14px;\n                               background: #FF779A;\n                               color: #FFFFFF;\n                               padding: 10px;\n                               display: inline-block;\n                               border-radius: 5px;\n                               margin: 10px auto 0;\" target=\"_blank\" th:href=\"${OJ_URL}\"\n                   th:text=\"${OJ_SHORT_NAME}+'｜传送门🚪'\"></a>\n            </div>\n            <p style=\"font-size: 12px;text-align: center;color: #999; \">\n                欢迎常来访问！<br>\n                © 2021 <a rel=\"noopener\" style=\"text-decoration:none; color:#1bc3fb \" target=\"_blank\"\n                          th:href=\"${OJ_URL}\" th:text=\"${OJ_NAME}\"></a>\n            </p>\n            <p></p>\n        </center>\n    </div>\n</div>\n</body>\n</html>"
  },
  {
    "path": "voj-backend/src/main/resources/templates/emailTemplate_testEmail.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh\" xmlns:th=http://www.thymeleaf.org>\n<head>\n    <meta charset=\"UTF-8\">\n    <title>[[${OJ_SHORT_NAME}]]超级管理员测试邮箱可用性邮件</title>\n</head>\n<body>\n<div style=\"background: white;\n               width: 100%;\n               max-width: 800px;\n               margin: auto auto;\n               border-radius: 5px;\n               border:#1bc3fb 1px solid;\n               overflow: hidden;\n               -webkit-box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.12);\n               box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.18);\">\n    <!--            <header style=\"overflow: hidden;\">-->\n    <!--                <center>-->\n    <!--                    <img style=\"width:100%;z-index: 666;\"-->\n    <!--                         th:src=\"${EMAIL_BACKGROUND_IMG}\">-->\n    <!--                </center>-->\n    <!--            </header>-->\n    <div style=\"padding: 5px 20px; \">\n        <p style=\" position: relative;\n                 color: white;\n                 float: left;\n                 z-index: 999;\n                 background: #1bc3fb;\n                 padding: 5px 30px;\n                 margin: 0;\n                 box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.30) \">\n            Dear Super Admin\n        </p>\n        <br>\n        <center>\n            <h3>\n                来自 <span style=\" text-decoration: none;color: #FF779A;\" th:text=\"${OJ_SHORT_NAME}\"></span> 邮件提醒\n            </h3>\n            <p style=\"text-indent:2em;\">\n                您收到这封电子邮件是因为您在<a rel=\"noopener\"\n                                 style=\" text-decoration: none;color: #1bc3fb \" target=\"_blank\" th:href=\"${OJ_URL}\">&nbsp;[[${OJ_SHORT_NAME}]]&nbsp;</a>上进行邮箱配置更新，然后进行邮箱可行性的测试。\n            </p>\n            <div style=\"background: #fafafa repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);margin:20px 0px;padding:15px;border-radius:5px;font-size:14px;color:#555555;text-align: center; \">\n                经过本邮件的接收，可证实：<br>\n                <a style=\"color: #FF779A;font-weight: bolder;font-size: 25px;text-decoration: none;\">Test\n                    Success</a><br>\n            </div>\n            &nbsp; &nbsp;\n\n            <br>\n            <div style=\" text-align: center; \">\n                <a rel=\"noopener\"\n                   style=\"text-transform: uppercase;\n                               text-decoration: none;\n                               font-size: 14px;\n                               background: #FF779A;\n                               color: #FFFFFF;\n                               padding: 10px;\n                               display: inline-block;\n                               border-radius: 5px;\n                               margin: 10px auto 0;\" target=\"_blank\" th:href=\"${OJ_URL}\"\n                   th:text=\"${OJ_SHORT_NAME}+'｜传送门🚪'\"></a>\n            </div>\n            <p style=\"font-size: 12px;text-align: center;color: #999; \">\n                欢迎常来访问！<br>\n                © 2021 <a rel=\"noopener\" style=\"text-decoration:none; color:#1bc3fb \" target=\"_blank\"\n                          th:href=\"${OJ_URL}\" th:text=\"${OJ_NAME}\"></a>\n            </p>\n            <p></p>\n        </center>\n    </div>\n</div>\n</body>\n</html>"
  },
  {
    "path": "voj-backend/src/test/java/com/simplefanc/AppTest.java",
    "content": "package com.simplefanc;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Unit test for simple App.\n */\npublic class AppTest {\n\n    /**\n     * Rigorous Test :-)\n     */\n    @Test\n    public void shouldAnswerWithTrue() {\n        assertTrue(true);\n    }\n\n}\n"
  },
  {
    "path": "voj-common/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>voj</artifactId>\n        <groupId>com.simplefanc</groupId>\n        <version>1.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <name>voj-common</name>\n    <artifactId>voj-common</artifactId>\n    <version>1.0</version>\n\n    <dependencies>\n        <!--导入swagger-->\n        <dependency>\n            <groupId>io.springfox</groupId>\n            <artifactId>springfox-swagger2</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.springfox</groupId>\n            <artifactId>springfox-swagger-ui</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.aspectj</groupId>\n            <artifactId>aspectjweaver</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>javax.servlet</groupId>\n            <artifactId>javax.servlet-api</artifactId>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/Constant.java",
    "content": "package com.simplefanc.voj.common.constants;\n\n/**\n * @author chenfan\n * @date 2022/5/7 21:28\n **/\npublic interface Constant {\n    String LOCAL = \"LOCAL\";\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/ContestConstant.java",
    "content": "package com.simplefanc.voj.common.constants;\n\n/**\n * @author chenfan\n * @date 2022/4/18 18:11\n **/\npublic interface ContestConstant {\n\n    String OI_RANK_RECENT_SCORE = \"Recent\";\n\n    String OI_RANK_HIGHEST_SCORE = \"Highest\";\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/ContestEnum.java",
    "content": "package com.simplefanc.voj.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @Description 比赛相关的常量\n * @Since 2021/1/7\n */\n@Getter\n@AllArgsConstructor\npublic enum ContestEnum {\n    TYPE_ACM(0, \"ACM\"),\n    TYPE_OI(1, \"OI\"),\n\n    STATUS_SCHEDULED(-1, \"Scheduled\"),\n    STATUS_RUNNING(0, \"Running\"),\n    STATUS_ENDED(1, \"Ended\"),\n\n    AUTH_PUBLIC(0, \"Public\"),\n    AUTH_PRIVATE(1, \"Private\"),\n    AUTH_PROTECT(2, \"Protect\"),\n\n    RECORD_NOT_AC_PENALTY(-1, \"未AC通过算罚时\"),\n    RECORD_NOT_AC_NOT_PENALTY(0, \"未AC通过不罚时\"),\n    RECORD_AC(1, \"AC通过\");\n\n    private final Integer code;\n\n    private final String name;\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/JudgeCaseMode.java",
    "content": "package com.simplefanc.voj.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author chenfan\n * @date 2022/9/21\n **/\n@Getter\n@AllArgsConstructor\npublic enum JudgeCaseMode {\n\n    DEFAULT(\"default\"),\n\n    ITERATE_UNTIL_WRONG(\"iterate_until_wrong\");\n\n    private final String mode;\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/JudgeMode.java",
    "content": "package com.simplefanc.voj.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author chenfan\n * @date 2022/4/18 16:24\n **/\n@Getter\n@AllArgsConstructor\npublic enum JudgeMode {\n\n    DEFAULT(\"default\"),\n\n    SPJ(\"spj\"),\n\n    INTERACTIVE(\"interactive\");\n\n    private final String mode;\n\n    public static JudgeMode getJudgeMode(String mode) {\n        for (JudgeMode judgeMode : JudgeMode.values()) {\n            if (judgeMode.getMode().equals(mode)) {\n                return judgeMode;\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/JudgeStatus.java",
    "content": "package com.simplefanc.voj.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @Description 提交评测结果的状态码\n * @Since 2021/1/1\n */\n@Getter\n@AllArgsConstructor\npublic enum JudgeStatus {\n    STATUS_NOT_SUBMITTED(-10, \"Not Submitted\", null),\n    STATUS_SUBMITTED_UNKNOWN_RESULT(-5, \"Submitted Unknown Result\", null),\n    STATUS_PRESENTATION_ERROR(-3, \"Presentation Error\", \"pe\"),\n    STATUS_COMPILE_ERROR(-2, \"Compile Error\", \"ce\"),\n    STATUS_WRONG_ANSWER(-1, \"Wrong Answer\", \"wa\"),\n    STATUS_ACCEPTED(0, \"Accepted\", \"ac\"),\n    STATUS_TIME_LIMIT_EXCEEDED(1, \"Time Limit Exceeded\", \"tle\"),\n    STATUS_MEMORY_LIMIT_EXCEEDED(2, \"Memory Limit Exceeded\", \"mle\"),\n    STATUS_RUNTIME_ERROR(3, \"Runtime Error\", \"re\"),\n    STATUS_SYSTEM_ERROR(4, \"System Error\", \"se\"),\n    STATUS_PENDING(5, \"Pending\", null),\n    STATUS_COMPILING(6, \"Compiling\", null),\n    STATUS_JUDGING(7, \"Judging\", null),\n    STATUS_PARTIAL_ACCEPTED(8, \"Partial Accepted\", \"pa\"),\n    STATUS_SUBMITTING(9, \"Submitting\", null),\n    STATUS_SUBMITTED_FAILED(10, \"Submitted Failed\", null),\n    STATUS_OUTPUT_LIMIT_EXCEEDED(11, \"Output Limit Exceeded\", null),\n    STATUS_NULL(15, \"No Status\", null),\n    JUDGE_SERVER_SUBMIT_PREFIX(-1002, \"Judge SubmitId-ServerId:\", null);\n\n    private final Integer status;\n\n    private final String name;\n\n    private final String columnName;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/ProblemEnum.java",
    "content": "package com.simplefanc.voj.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n@Getter\n@AllArgsConstructor\npublic enum ProblemEnum {\n    AUTH_PUBLIC(1, \"Public\"),\n    AUTH_PRIVATE(2, \"Private\"),\n    AUTH_CONTEST(3, \"Contest\");\n\n    private final Integer code;\n    private final String name;\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/ProblemLevelEnum.java",
    "content": "package com.simplefanc.voj.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n@Getter\n@AllArgsConstructor\npublic enum ProblemLevelEnum {\n    PROBLEM_LEVEL_EASY(0, \"Easy\"),\n    PROBLEM_LEVEL_MID(1, \"Mid\"),\n    PROBLEM_LEVEL_HARD(2, \"Hard\");\n\n    private final Integer code;\n    private final String name;\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/RedisConstant.java",
    "content": "package com.simplefanc.voj.common.constants;\n\npublic interface RedisConstant {\n    String OI_CONTEST_RANK_CACHE = \"oi_contest_rank_cache\";\n\n    String CONTEST_RANK_CAL_RESULT_CACHE = \"contest_rank_cal_result_cache\";\n\n    String ACM_RANK_CACHE = \"acm_rank_cache\";\n\n    String OI_RANK_CACHE = \"oi_rank_cache\";\n\n    String SUPER_ADMIN_UID_LIST_CACHE = \"super_admin_uid_list_cache\";\n\n    String CODE_CHANGE_PASSWORD_FAIL = \"change-password-fail:\";\n\n    String CODE_CHANGE_PASSWORD_LOCK = \"change-password-lock:\";\n\n    String CODE_ACCOUNT_LOCK = \"account-lock:\";\n\n    String CODE_CHANGE_EMAIL_FAIL = \"change-email-fail:\";\n\n    String CODE_CHANGE_EMAIL_LOCK = \"change-email-lock:\";\n\n    String TRY_LOGIN_NUM = \"try-login-num:\";\n\n    String SUBMIT_NON_CONTEST_LOCK = \"submit_non_contest_lock:\";\n\n    String SUBMIT_CONTEST_LOCK = \"submit_contest_lock:\";\n\n    String DISCUSSION_ADD_NUM_LOCK = \"discussion_add_num_lock:\";\n\n    String CONTEST_ADD_PRINT_LOCK = \"contest_add_print_lock:\";\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/constants/RemoteOj.java",
    "content": "package com.simplefanc.voj.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n@Getter\n@AllArgsConstructor\npublic enum RemoteOj {\n\n    HDU(\"HDU\"),\n\n    CF(\"CF\"),\n\n    GYM(\"GYM\"),\n\n    AtCoder(\"AC\"),\n\n    POJ(\"POJ\"),\n\n    JSK(\"JSK\"),\n\n    MXT(\"MXT\"),\n\n    TKOJ(\"TKOJ\"),\n\n    EOJ(\"EOJ\"),\n\n    LOJ(\"LOJ\"),\n    ;\n\n    private final String name;\n\n    public static RemoteOj getTypeByName(String judgeName) {\n        if (judgeName == null) {\n            return null;\n        }\n        for (RemoteOj remoteJudge : RemoteOj.values()) {\n            if (remoteJudge.getName().equals(judgeName)) {\n                return remoteJudge;\n            }\n        }\n        return null;\n    }\n\n    public static Boolean isRemoteOj(String name) {\n        for (RemoteOj remoteOj : RemoteOj.values()) {\n            if (remoteOj.getName().equals(name)) {\n                return true;\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/dto/CompileDTO.java",
    "content": "package com.simplefanc.voj.common.pojo.dto;\n\nimport lombok.Data;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2021/2/6 14:42\n * @Description:\n */\n@Data\npublic class CompileDTO {\n\n    /**\n     * 编译的源代码\n     */\n    private String code;\n\n    /**\n     * 编译的源代码相关的题目id\n     */\n    private Long pid;\n\n    /**\n     * 编译的源代码所选语言\n     */\n    private String language;\n\n    /**\n     * 调用判题机的凭证\n     */\n    private String token;\n\n    /**\n     * 编译所需的额外文件，key:文件名,value:文件内容\n     */\n    private HashMap<String, String> extraFiles;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/dto/JudgeDTO.java",
    "content": "package com.simplefanc.voj.common.pojo.dto;\n\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\n\n/**\n * @Author: chenfan\n * @Date: 2021/2/4 22:29\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"com.simplefanc.voj.common.pojo.dto.JudgeDTO\", description = \"后台服务与判题服务之间的数据交互格式\")\npublic class JudgeDTO implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @ApiModelProperty(\"判题数据\")\n    private Judge judge;\n\n    @ApiModelProperty(\"验证的token\")\n    private String token;\n\n    @ApiModelProperty(\"远程判题不为空，voj判题为null，例如HDU-1000\")\n    private String remoteJudgeProblem;\n\n    @ApiModelProperty(\"远程判题所用账号\")\n    private String username;\n\n    @ApiModelProperty(\"远程判题所用密码\")\n    private String password;\n\n    @ApiModelProperty(\"调用判题机的ip\")\n    private String judgeServerIp;\n\n    @ApiModelProperty(\"调用判题机的port\")\n    private Integer judgeServerPort;\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/common/Announcement.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.common;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/10 19:47\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Announcement对象\", description = \"\")\npublic class Announcement {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"通知标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"通知内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"发布者id（必须为比赛创建者或者超级管理员才能）\")\n    private String uid;\n\n    @ApiModelProperty(value = \"0可见，1不可见\")\n    private int status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/common/File.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.common;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/11 13:58\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"File对象\", description = \"\")\n@TableName(\"`file`\")\n// TODO 名字\npublic class File {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"文件所属类型，例如avatar\")\n    @TableField(\"`type`\")\n    private String type;\n\n    @ApiModelProperty(value = \"文件名\")\n    private String name;\n\n    @ApiModelProperty(value = \"文件后缀格式\")\n    private String suffix;\n\n    @ApiModelProperty(value = \"文件所在文件夹的路径\")\n    private String folderPath;\n\n    @ApiModelProperty(value = \"文件绝对路径\")\n    private String filePath;\n\n    @ApiModelProperty(value = \"是否删除\")\n    @TableField(\"`delete`\")\n    private Boolean delete;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @ApiModelProperty(value = \"修改时间\")\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/contest/Contest.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.contest;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Contest对象\", description = \"\")\npublic class Contest implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    @ApiModelProperty(value = \"比赛id\")\n    private Long id;\n\n    @ApiModelProperty(value = \"比赛创建者id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"比赛创建者的用户名\")\n    private String author;\n\n    @ApiModelProperty(value = \"比赛标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"0为acm赛制，1为比分赛制\")\n    private Integer type;\n\n    @ApiModelProperty(value = \"比赛说明\")\n    private String description;\n\n    @ApiModelProperty(value = \"比赛来源，原创为0，克隆赛为比赛id\")\n    private Integer source;\n\n    @ApiModelProperty(value = \"0为公开赛，1为私有赛（访问有密码），2为保护赛（提交有密码）\")\n    private Integer auth;\n\n    @ApiModelProperty(value = \"是否打开密码限制\")\n    private Boolean openPwdLimit;\n\n    @ApiModelProperty(value = \"比赛密码\")\n    private String pwd;\n\n    @ApiModelProperty(value = \"开始时间\")\n    private Date startTime;\n\n    @ApiModelProperty(value = \"结束时间\")\n    private Date endTime;\n\n    @ApiModelProperty(value = \"比赛时长（s）\")\n    private Long duration;\n\n    @ApiModelProperty(value = \"是否开启封榜\")\n    private Boolean sealRank;\n\n    @ApiModelProperty(value = \"封榜起始时间，一直到比赛结束，不刷新榜单\")\n    private Date sealRankTime;\n\n    @ApiModelProperty(value = \"比赛结束是否自动解除封榜,自动转换成真实榜单\")\n    private Boolean autoRealRank;\n\n    @ApiModelProperty(value = \"比赛管理员是否参与排名\")\n    private Boolean contestAdminRank;\n\n    @ApiModelProperty(value = \"-1为未开始，0为进行中，1为已结束\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"是否可见\")\n    private Boolean visible;\n\n    @ApiModelProperty(value = \"是否仅对比赛管理员可见\")\n    private Boolean contestAdminVisible;\n\n    @ApiModelProperty(value = \"是否打开打印功能\")\n    private Boolean openPrint;\n\n    @ApiModelProperty(value = \"是否打开账号限制\")\n    private Boolean openAccountLimit;\n\n    @ApiModelProperty(\n            value = \"账号限制规则 <prefix>**</prefix><suffix>**</suffix><start>**</start><end>**</end><extra>**</extra>\")\n    private String accountLimitRule;\n\n    @ApiModelProperty(value = \"排行榜显示（username、nickname、realname）\")\n    private String rankShowName;\n\n    @ApiModelProperty(value = \"打星用户列表 {\\\"star_account\\\":['a','b']}\")\n    private String starAccount;\n\n    @ApiModelProperty(value = \"是否开放比赛榜单\")\n    private Boolean openRank;\n\n    @ApiModelProperty(value = \"oi排行榜得分方式，Recent、Highest（最近一次提交、最高得分提交）\")\n    private String oiRankScoreType;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/contest/ContestAnnouncement.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.contest;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"ContestAnnouncement对象\", description = \"\")\npublic class ContestAnnouncement implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"公告id\")\n    private Long aid;\n\n    @ApiModelProperty(value = \"比赛id\")\n    private Long cid;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/contest/ContestPrint.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.contest;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/9/19 21:00\n * @Description:\n */\n\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"ContestPrint\", description = \"\")\npublic class ContestPrint {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    private Long cid;\n\n    @ApiModelProperty(value = \"提交打印文本的用户\")\n    private String username;\n\n    @ApiModelProperty(value = \"真实姓名\")\n    private String realname;\n\n    @ApiModelProperty(value = \"内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"状态\")\n    private Integer status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/contest/ContestProblem.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.contest;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"ContestProblem对象\", description = \"\")\npublic class ContestProblem implements Serializable, Comparable<ContestProblem> {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"该题目在比赛中的顺序id\")\n    private String displayId;\n\n    @ApiModelProperty(value = \"比赛id\")\n    private Long cid;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"该题目在比赛中的标题，默认为原名字\")\n    private String displayTitle;\n\n    @ApiModelProperty(value = \"气球的颜色\")\n    private String color;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n    @Override\n    public int compareTo(ContestProblem cp) {\n        if (this.displayId.length() == cp.displayId.length()) {\n            return this.displayId.compareTo(cp.getDisplayId());\n        }\n        return Integer.compare(this.displayId.length(), cp.displayId.length());\n    }\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/contest/ContestRecord.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.contest;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"ContestRecord对象\", description = \"\")\npublic class ContestRecord implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"比赛id\")\n    private Long cid;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"比赛中的题目id\")\n    private Long cpid;\n\n    @ApiModelProperty(value = \"比赛中展示的id\")\n    private String displayId;\n\n    @ApiModelProperty(value = \"提交id，用于可重判\")\n    private Long submitId;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"真实姓名（废弃）\")\n    private String realname;\n\n    @ApiModelProperty(value = \"提交结果，0表示未AC通过不罚时，1表示AC通过，-1为未AC通过算罚时\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"具体提交时间\")\n    private Date submitTime;\n\n    @ApiModelProperty(value = \"提交时间，为提交时间减去比赛时间\")\n    private Long time;\n\n    @ApiModelProperty(value = \"OI比赛的得分\")\n    private Integer score;\n\n    @ApiModelProperty(value = \"提交的程序运行耗时\")\n    private Integer useTime;\n\n    @ApiModelProperty(value = \"是否为一血AC（废弃）\")\n    private Boolean firstBlood;\n\n    @ApiModelProperty(value = \"AC是否已校验\")\n    private Boolean checked;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/contest/ContestRegister.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.contest;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"ContestRegister对象\", description = \"\")\npublic class ContestRegister implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"比赛id\")\n    private Long cid;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"默认为0表示正常，1为失效。\")\n    private Integer status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/discussion/Comment.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.discussion;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Comment对象\", description = \"\")\npublic class Comment implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Integer id;\n\n    @ApiModelProperty(value = \"NULL表示无引用比赛\")\n    private Long cid;\n\n    @ApiModelProperty(value = \"NULL表示无引用讨论\")\n    private Integer did;\n\n    @ApiModelProperty(value = \"评论内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"评论者id\")\n    private String fromUid;\n\n    @ApiModelProperty(value = \"评论者用户名\")\n    private String fromName;\n\n    @ApiModelProperty(value = \"评论组头像地址\")\n    private String fromAvatar;\n\n    @ApiModelProperty(value = \"评论者角色\")\n    private String fromRole;\n\n    @ApiModelProperty(value = \"点赞数量\")\n    private Integer likeNum;\n\n    @ApiModelProperty(value = \"是否封禁或删除 0正常，1封禁\")\n    @TableLogic\n    private Integer status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/discussion/CommentLike.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.discussion;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/5 11:36\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"CommentLike对象\", description = \"\")\npublic class CommentLike {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Integer id;\n\n    @ApiModelProperty(value = \"评论id\")\n    private Integer cid;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/discussion/Discussion.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.discussion;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/4 22:11\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Discussion对象\", description = \"\")\npublic class Discussion {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Integer id;\n\n    @ApiModelProperty(value = \"分类id\")\n    private Integer categoryId;\n\n    @ApiModelProperty(value = \"讨论标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"讨论简介\")\n    private String description;\n\n    @ApiModelProperty(value = \"讨论内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"题目关联 默认为null则不关联题目\")\n    private String pid;\n\n    @ApiModelProperty(value = \"发表者id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"发表者用户名\")\n    private String author;\n\n    @ApiModelProperty(value = \"发表者头像地址\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"发表者角色\")\n    private String role;\n\n    @ApiModelProperty(value = \"浏览数量\")\n    private Integer viewNum;\n\n    @ApiModelProperty(value = \"点赞数量\")\n    private Integer likeNum;\n\n    @ApiModelProperty(value = \"评论数量,包括其评论及其回复数\")\n    private Integer commentNum;\n\n    @ApiModelProperty(value = \"优先级，是否置顶\")\n    private Boolean topPriority;\n\n    @ApiModelProperty(value = \"是否封禁或删除 0正常，1封禁\")\n    private Integer status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/discussion/DiscussionLike.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.discussion;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/5 11:36\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"DiscussionLike对象\", description = \"\")\npublic class DiscussionLike {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Integer id;\n\n    @ApiModelProperty(value = \"讨论id\")\n    private Integer did;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/discussion/DiscussionReport.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.discussion;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/11 21:43\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"DiscussionReport对象\", description = \"\")\npublic class DiscussionReport {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"讨论id\")\n    private Integer did;\n\n    @ApiModelProperty(value = \"举报者的用户名\")\n    private String reporter;\n\n    @ApiModelProperty(value = \"举报内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"是否已读\")\n    private Boolean status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/discussion/Reply.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.discussion;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/5 19:03\n * @Description:\n */\n\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Reply对象\", description = \"\")\npublic class Reply {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Integer id;\n\n    @ApiModelProperty(value = \"评论id\")\n    private Integer commentId;\n\n    @ApiModelProperty(value = \"回复评论者id\")\n    private String fromUid;\n\n    @ApiModelProperty(value = \"回复评论者用户名\")\n    private String fromName;\n\n    @ApiModelProperty(value = \"回复评论者头像地址\")\n    private String fromAvatar;\n\n    @ApiModelProperty(value = \"回复评论者角色\")\n    private String fromRole;\n\n    @ApiModelProperty(value = \"被回复的用户id\")\n    private String toUid;\n\n    @ApiModelProperty(value = \"被回复的用户名\")\n    private String toName;\n\n    @ApiModelProperty(value = \"被回复的用户头像地址\")\n    private String toAvatar;\n\n    @ApiModelProperty(value = \"回复的内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"是否封禁或删除 0正常，1封禁\")\n    @TableLogic\n    private Integer status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/judge/Judge.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.judge;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Judge对象\", description = \"\")\npublic class Judge implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"submit_id\", type = IdType.AUTO)\n    private Long submitId;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"题目展示id\")\n    private String displayPid;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"提交的时间\")\n    private Date submitTime;\n\n    @ApiModelProperty(value = \"结果码具体参考文档\")\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    private Integer status;\n\n    @ApiModelProperty(value = \"0为仅自己可见，1为全部人可见。\")\n    private Boolean share;\n\n    @ApiModelProperty(value = \"错误提醒（编译错误，或者vj提醒）\")\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    private String errorMessage;\n\n    @ApiModelProperty(value = \"运行时间(ms)\")\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    private Integer time;\n\n    @ApiModelProperty(value = \"运行内存(kb)\")\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    private Integer memory;\n\n    @ApiModelProperty(value = \"IO判题不为空\")\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    private Integer score;\n\n    @ApiModelProperty(value = \"代码长度\")\n    private Integer length;\n\n    @ApiModelProperty(value = \"代码\")\n    private String code;\n\n    @ApiModelProperty(value = \"代码语言\")\n    private String language;\n\n    @ApiModelProperty(value = \"比赛id，非比赛提交默认为0\")\n    private Long cid;\n\n    @ApiModelProperty(value = \"比赛中题目排序id，非比赛提交默认为0\")\n    private Long cpid;\n\n    @ApiModelProperty(value = \"判题机名称\")\n    private String judger;\n\n    @ApiModelProperty(value = \"提交者所在ip\")\n    private String ip;\n\n    @ApiModelProperty(value = \"废弃\")\n    private Integer version;\n\n    @ApiModelProperty(value = \"该题在OI排行榜的分数\")\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    private Integer oiRankScore;\n\n    @ApiModelProperty(value = \"vjudge判题在其它oj的提交id\")\n    private String vjudgeSubmitId;\n\n    @ApiModelProperty(value = \"vjudge判题在其它oj的提交用户名\")\n    private String vjudgeUsername;\n\n    @ApiModelProperty(value = \"vjudge判题在其它oj的提交账号密码\")\n    private String vjudgePassword;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/judge/JudgeCase.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.judge;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"JudgeCase对象\", description = \"\")\npublic class JudgeCase implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"判题id\")\n    private Long submitId;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"测试样例id\")\n    private Long caseId;\n\n    @ApiModelProperty(value = \"测试该样例所用时间ms\")\n    private Integer time;\n\n    @ApiModelProperty(value = \"测试该样例所用空间KB\")\n    private Integer memory;\n\n    @ApiModelProperty(value = \"OI得分\")\n    private Integer score;\n\n    @ApiModelProperty(value = \"测试该样例结果状态码\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"样例输入，输入文件名\")\n    private String inputData;\n\n    @ApiModelProperty(value = \"样例输出，输出文件名\")\n    private String outputData;\n\n    @ApiModelProperty(value = \"用户样例输出，暂不使用，当前用于记录spj对单个测试点的输出\")\n    private String userOutput;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/judge/JudgeServer.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.judge;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/15 11:08\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"JudgeServer对象\", description = \"判题服务器配置\")\npublic class JudgeServer {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Integer id;\n\n    @ApiModelProperty(value = \"判题服务名字\")\n    private String name;\n\n    @ApiModelProperty(value = \"判题机ip\")\n    private String ip;\n\n    @ApiModelProperty(value = \"判题机端口号\")\n    private Integer port;\n\n    @ApiModelProperty(value = \"ip:port\")\n    private String url;\n\n    @ApiModelProperty(value = \"判题机所在服务器cpu核心数\")\n    private Integer cpuCore;\n\n    @ApiModelProperty(value = \"当前判题数\")\n    private Integer taskNumber;\n\n    @ApiModelProperty(value = \"判题并发最大数\")\n    private Integer maxTaskNumber;\n\n    @ApiModelProperty(value = \"0可用，1不可用\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"是否为远程判题vj\")\n    private Boolean isRemote;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/judge/RemoteJudgeAccount.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.judge;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/18 17:41\n * @Description:\n */\n\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"RemoteJudgeAccount对象\", description = \"远程判题服务的账号\")\npublic class RemoteJudgeAccount {\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Integer id;\n\n    @ApiModelProperty(value = \"远程oj名字\")\n    private String oj;\n\n    @ApiModelProperty(value = \"账号用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"账号密码\")\n    private String password;\n\n    @ApiModelProperty(value = \"是否可用\")\n    private Boolean status;\n\n    @ApiModelProperty(value = \"废弃\")\n    private Long version;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/msg/AdminSysNotice.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.msg;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:11\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"AdminSysNotice\", description = \"\")\npublic class AdminSysNotice {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"通知标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"通知内容\")\n    private String content;\n\n    @ApiModelProperty(value = \"发给哪些用户类型,例如全部用户All，指定单个用户Single，管理员Admin\")\n    private String type;\n\n    @ApiModelProperty(value = \"是否已被拉取过，如果已经拉取过，就无需再次拉取\")\n    private Boolean state;\n\n    @ApiModelProperty(value = \"接受通知的用户的id，如果type为single，那么recipient 为该用户的id;否则recipient为null\")\n    private String recipientId;\n\n    @ApiModelProperty(value = \"发布通知的管理员id\")\n    private String adminId;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/msg/MsgRemind.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.msg;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:21\n * @Description:\n */\n\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"MsgRemind\", description = \"\")\npublic class MsgRemind {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"动作类型，如点赞讨论帖Like_Post、点赞评论Like_Discuss、评论Discuss、回复Reply等\")\n    private String action;\n\n    @ApiModelProperty(value = \"消息来源id，讨论id或比赛id\")\n    private Integer sourceId;\n\n    @ApiModelProperty(value = \"事件源类型：'Discussion'、'Contest'等\")\n    private String sourceType;\n\n    @ApiModelProperty(value = \"事件源的内容，比如回复的内容，回复的评论等等,不超过250字符，超过使用...\")\n    private String sourceContent;\n\n    @ApiModelProperty(value = \"事件引用上一级评论或回复id\")\n    private Integer quoteId;\n\n    @ApiModelProperty(value = \"事件引用上一级的类型：Comment、Reply\")\n    private String quoteType;\n\n    @ApiModelProperty(value = \"事件所发生的地点链接 url\")\n    private String url;\n\n    @ApiModelProperty(value = \"接受通知的用户的id\")\n    private String recipientId;\n\n    @ApiModelProperty(value = \"动作执行者的id\")\n    private String senderId;\n\n    @ApiModelProperty(value = \"是否已读\")\n    private Boolean state;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/msg/UserSysNotice.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.msg;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/1 20:18\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"UserSysNotice\", description = \"\")\npublic class UserSysNotice {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"系统通知的id\")\n    private Long sysNoticeId;\n\n    @ApiModelProperty(value = \"接受通知的用户的id\")\n    private String recipientId;\n\n    @ApiModelProperty(value = \"消息类型，系统通知Sys、我的信息Mine\")\n    private String type;\n\n    @ApiModelProperty(value = \"是否已读\")\n    private Boolean state;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/problem/Category.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.problem;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/4 22:09\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Category对象\", description = \"\")\npublic class Category {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"分类名字\")\n    private String name;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/problem/CodeTemplate.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.problem;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/23 18:31\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"CodeTemplate\", description = \"\")\npublic class CodeTemplate {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Integer id;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"语言id\")\n    private Long lid;\n\n    @ApiModelProperty(value = \"代码\")\n    private String code;\n\n    @ApiModelProperty(value = \"是否启用\")\n    private Boolean status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/problem/Language.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.problem;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/12 23:14\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Language对象\", description = \"\")\npublic class Language {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"语言类型\")\n    private String contentType;\n\n    @ApiModelProperty(value = \"语言描述\")\n    private String description;\n\n    @ApiModelProperty(value = \"语言名字\")\n    private String name;\n\n    @ApiModelProperty(value = \"编译指令\")\n    private String compileCommand;\n\n    @ApiModelProperty(value = \"A+B模板\")\n    private String template;\n\n    @ApiModelProperty(value = \"语言默认代码模板\")\n    private String codeTemplate;\n\n    @ApiModelProperty(value = \"是否可作为特殊判题的一种语言\")\n    private Boolean isSpj;\n\n    @ApiModelProperty(value = \"该语言属于哪个oj，自身oj用ME\")\n    private String oj;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/problem/Problem.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.problem;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.*;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Problem对象\", description = \"\")\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class Problem implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"题目的自定义ID 例如（VOJ-1000）\")\n    private String problemId;\n\n    @ApiModelProperty(value = \"题目的自定义信息 例如JSK除题面外的真实id\")\n    private String info;\n\n    @ApiModelProperty(value = \"题目\")\n    private String title;\n\n    @ApiModelProperty(value = \"作者\")\n    private String author;\n\n    @ApiModelProperty(value = \"0为ACM,1为OI\")\n    private Integer type;\n\n    @ApiModelProperty(value = \"default,spj,interactive\")\n    private String judgeMode;\n\n    @ApiModelProperty(value = \"default,iterate_until_wrong\")\n    private String judgeCaseMode;\n\n    @ApiModelProperty(value = \"单位ms\")\n    private Integer timeLimit;\n\n    @ApiModelProperty(value = \"单位mb\")\n    private Integer memoryLimit;\n\n    @ApiModelProperty(value = \"单位mb\")\n    private Integer stackLimit;\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n\n    @ApiModelProperty(value = \"输入描述\")\n    private String input;\n\n    @ApiModelProperty(value = \"输出描述\")\n    private String output;\n\n    @ApiModelProperty(value = \"题面样例\")\n    private String examples;\n\n    @ApiModelProperty(value = \"是否为vj判题\")\n    private Boolean isRemote;\n\n    @ApiModelProperty(value = \"题目来源（vj判题时例如HDU-1000的链接）\")\n    private String source;\n\n    @ApiModelProperty(value = \"题目难度\")\n    private Integer difficulty;\n\n    @ApiModelProperty(value = \"备注,提醒\")\n    private String hint;\n\n    @ApiModelProperty(value = \"默认为1公开，2为私有，3为比赛中\")\n    private Integer auth;\n\n    @ApiModelProperty(value = \"该题目对应的相关提交代码，用户是否可用分享\")\n    private Boolean codeShare;\n\n    @ApiModelProperty(value = \"特判程序或交互程序的代码\")\n    @TableField(value = \"spj_code\", updateStrategy = FieldStrategy.IGNORED)\n    private String spjCode;\n\n    @ApiModelProperty(value = \"特判程序或交互程序的语言\")\n    @TableField(value = \"spj_language\", updateStrategy = FieldStrategy.IGNORED)\n    private String spjLanguage;\n\n    @ApiModelProperty(value = \"特判程序或交互程序的额外文件 json key:name value:content\")\n    @TableField(value = \"user_extra_file\", updateStrategy = FieldStrategy.IGNORED)\n    private String userExtraFile;\n\n    @ApiModelProperty(value = \"特判程序或交互程序的额外文件 json key:name value:content\")\n    @TableField(value = \"judge_extra_file\", updateStrategy = FieldStrategy.IGNORED)\n    private String judgeExtraFile;\n\n    @ApiModelProperty(value = \"是否默认去除用户代码的每行末尾空白符\")\n    private Boolean isRemoveEndBlank;\n\n    @ApiModelProperty(value = \"是否默认开启该题目的测试样例结果查看\")\n    private Boolean openCaseResult;\n\n    @ApiModelProperty(value = \"题目测试数据是否是上传的\")\n    private Boolean isUploadCase;\n\n    @ApiModelProperty(value = \"题目测试数据的版本号\")\n    private String caseVersion;\n\n    @ApiModelProperty(value = \"修改题目的管理员用户名\")\n    private String modifiedUser;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/problem/ProblemCase.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.problem;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/28 13:37\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Case对象\", description = \"题目测试样例\")\npublic class ProblemCase {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"测试样例的输入\")\n    private String input;\n\n    @ApiModelProperty(value = \"测试样例的输出\")\n    private String output;\n\n    @ApiModelProperty(value = \"该测试样例的IO得分\")\n    private Integer score;\n\n    @ApiModelProperty(value = \"0可用，1不可用\")\n    private Integer status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/problem/ProblemLanguage.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.problem;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/13 00:00\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"ProblemLanguage对象\", description = \"\")\npublic class ProblemLanguage {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    private Long pid;\n\n    private Long lid;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/problem/ProblemTag.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.problem;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/8 16:03\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"ProblemTag对象\", description = \"\")\npublic class ProblemTag {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    private Long pid;\n\n    private Long tid;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/problem/Tag.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.problem;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Tag对象\", description = \"\")\n// TODO 命名 problem_tag\npublic class Tag implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"标签名字\")\n    private String name;\n\n    @ApiModelProperty(value = \"标签颜色\")\n    private String color;\n\n    @ApiModelProperty(value = \"标签所属oj\")\n    private String oj;\n\n    @ApiModelProperty(value = \"标签分类ID\")\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    private Long tcid;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/problem/TagClassification.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.problem;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author chenfan\n * @Date 2022/8/3\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value=\"TagClassification对象\", description=\"标签分类\")\npublic class TagClassification {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"标签分类名字\")\n    private String name;\n\n    @ApiModelProperty(value = \"标签分类所属oj\")\n    private String oj;\n\n    @ApiModelProperty(value = \"标签分类优先级 越小越高\")\n    @TableField(\"`rank`\")\n    private Integer rank;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/training/MappingTrainingCategory.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.training;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/19 21:58\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"MappingTrainingCategory对象\", description = \"\")\npublic class MappingTrainingCategory implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"训练id\")\n    private Long tid;\n\n    @ApiModelProperty(value = \"训练分类id(TrainingCategory)\")\n    private Long cid;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/training/Training.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.training;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/19 21:37\n * @Description:\n */\n\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Training对象\", description = \"训练题单实体\")\npublic class Training implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    @ApiModelProperty(value = \"主键id\")\n    private Long id;\n\n    @ApiModelProperty(value = \"训练题单标题\")\n    private String title;\n\n    @ApiModelProperty(value = \"训练题单简介\")\n    private String description;\n\n    @ApiModelProperty(value = \"训练题单创建者用户名\")\n    private String author;\n\n    @ApiModelProperty(value = \"训练题单权限类型：Public、Private\")\n    private String auth;\n\n    @ApiModelProperty(value = \"训练题单权限为Private时的密码\")\n    private String privatePwd;\n\n    @ApiModelProperty(value = \"是否可用\")\n    private Boolean status;\n\n    @ApiModelProperty(value = \"编号，升序排序\")\n    @TableField(\"`rank`\")\n    private Integer rank;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/training/TrainingCategory.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.training;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/19 21:56\n * @Description:\n */\n\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"TrainingCategory对象\", description = \"\")\npublic class TrainingCategory implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"训练题单专用分类名字\")\n    private String name;\n\n    @ApiModelProperty(value = \"训练题单专用分类背景颜色\")\n    private String color;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/training/TrainingProblem.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.training;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/19 21:50\n * @Description:\n */\n\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"TrainingProblem对象\", description = \"\")\npublic class TrainingProblem implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"训练id\")\n    private Long tid;\n\n    @ApiModelProperty(value = \"题目源id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"题目展示id\")\n    private String displayId;\n\n    @ApiModelProperty(value = \"排序用\")\n    @TableField(\"`rank`\")\n    private Integer rank;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/training/TrainingRecord.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.training;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/19 21:52\n * @Description:\n */\n\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"TrainingRecord对象\", description = \"\")\npublic class TrainingRecord implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"训练id\")\n    private Long tid;\n\n    @ApiModelProperty(value = \"训练题目id\")\n    private Long tpid;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"提交id\")\n    private Long submitId;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/training/TrainingRegister.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.training;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/19 21:48\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"TrainingRegister对象\", description = \"\")\npublic class TrainingRegister implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"训练id\")\n    private Long tid;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"是否可用\")\n    private Boolean status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/user/Auth.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.user;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Auth对象\", description = \"\")\npublic class Auth implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"权限名称\")\n    private String name;\n\n    @ApiModelProperty(value = \"权限字符串\")\n    private String permission;\n\n    @ApiModelProperty(value = \"0可用，1不可用\")\n    private Integer status;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/user/Role.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.user;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"Role对象\", description = \"\")\npublic class Role implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.ID_WORKER)\n    private Long id;\n\n    @ApiModelProperty(value = \"角色\")\n    private String role;\n\n    @ApiModelProperty(value = \"描述\")\n    private String description;\n\n    @ApiModelProperty(value = \"默认0可用，1不可用\")\n    private Integer status;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @ApiModelProperty(value = \"修改时间\")\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/user/RoleAuth.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.user;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"RoleAuth对象\", description = \"\")\npublic class RoleAuth implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    private Long authId;\n\n    private Long roleId;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/user/Session.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.user;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/3 22:41\n * @Description:\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"session对象\", description = \"\")\npublic class Session {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    private String uid;\n\n    private String userAgent;\n\n    private String ip;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/user/UserAcproblem.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.user;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"UserAcproblem对象\", description = \"\")\npublic class UserAcproblem implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"用户id\")\n    private String uid;\n\n    @ApiModelProperty(value = \"ac的题目id\")\n    private Long pid;\n\n    @ApiModelProperty(value = \"提交的id\")\n    private Long submitId;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/user/UserInfo.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.user;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"UserInfo对象\", description = \"\")\npublic class UserInfo implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"uuid\", type = IdType.UUID)\n    private String uuid;\n\n    @ApiModelProperty(value = \"用户名\")\n    private String username;\n\n    @ApiModelProperty(value = \"密码\")\n    private String password;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"昵称\")\n    private String nickname;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"学校\")\n    private String school;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"专业\")\n    private String course;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"学号\")\n    private String number;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"性别\")\n    private String gender;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"真实姓名\")\n    private String realname;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"cf的username\")\n    private String cfUsername;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"github地址\")\n    private String github;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"博客地址\")\n    private String blog;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"邮箱\")\n    private String email;\n\n    @ApiModelProperty(value = \"头像地址\")\n    private String avatar;\n\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    @ApiModelProperty(value = \"个性介绍\")\n    private String signature;\n\n    @ApiModelProperty(value = \"头衔、称号\")\n    private String titleName;\n\n    @ApiModelProperty(value = \"头衔、称号的颜色\")\n    private String titleColor;\n\n    @ApiModelProperty(value = \"0可用，-1不可用\")\n    private int status;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @ApiModelProperty(value = \"修改时间\")\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/pojo/entity/user/UserRole.java",
    "content": "package com.simplefanc.voj.common.pojo.entity.user;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport io.swagger.annotations.ApiModel;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n *\n * </p>\n *\n * @Author: chenfan\n * @since 2021-10-23\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@ApiModel(value = \"UserRole对象\", description = \"\")\npublic class UserRole implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    private String uid;\n\n    private Long roleId;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date gmtCreate;\n\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private Date gmtModified;\n\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/result/CommonResult.java",
    "content": "package com.simplefanc.voj.common.result;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class CommonResult<T> {\n\n    /**\n     * 状态码\n     */\n    private Integer status;\n\n    /**\n     * 返回的数据\n     */\n    private T data;\n\n    /**\n     * 自定义信息\n     */\n    private String msg;\n\n    /**\n     * 成功的结果\n     *\n     * @param data 返回结果\n     * @param msg  返回信息\n     */\n    public static <T> CommonResult<T> successResponse(T data, String msg) {\n        return new CommonResult<>(ResultStatus.SUCCESS.getStatus(), data, msg);\n    }\n\n    /**\n     * 成功的结果\n     *\n     * @param data 返回结果\n     */\n    public static <T> CommonResult<T> successResponse(T data) {\n        return new CommonResult<>(ResultStatus.SUCCESS.getStatus(), data, \"success\");\n    }\n\n    /**\n     * 成功的结果\n     *\n     * @param msg 返回信息\n     */\n    public static <T> CommonResult<T> successResponse(String msg) {\n        return new CommonResult<>(ResultStatus.SUCCESS.getStatus(), null, msg);\n    }\n\n    /**\n     * 成功的结果\n     */\n    public static <T> CommonResult<T> successResponse() {\n        return new CommonResult<>(ResultStatus.SUCCESS.getStatus(), null, \"success\");\n    }\n\n    /**\n     * 失败的结果，无异常\n     *\n     * @param msg 返回信息\n     */\n    public static <T> CommonResult<T> errorResponse(String msg) {\n        return new CommonResult<>(ResultStatus.FAIL.getStatus(), null, msg);\n    }\n\n    public static <T> CommonResult<T> errorResponse(ResultStatus resultStatus) {\n        return new CommonResult<>(resultStatus.getStatus(), null, resultStatus.getDescription());\n    }\n\n    public static <T> CommonResult<T> errorResponse(String msg, ResultStatus resultStatus) {\n        return new CommonResult<>(resultStatus.getStatus(), null, msg);\n    }\n\n    public static <T> CommonResult<T> errorResponse(String msg, Integer status) {\n        return new CommonResult<>(status, null, msg);\n    }\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/result/ResultStatus.java",
    "content": "package com.simplefanc.voj.common.result;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/9 15:17\n * @Description:\n */\n@Getter\n@AllArgsConstructor\npublic enum ResultStatus {\n\n    SUCCESS(200, \"成功\"),\n\n    FAIL(400, \"失败\"),\n\n    ACCESS_DENIED(401, \"访问受限\"),\n\n    FORBIDDEN(403, \"拒绝访问\"),\n\n    NOT_FOUND(404, \"数据不存在\"),\n\n    SYSTEM_ERROR(500, \"系统错误\");\n\n    private int status;\n\n    private String description;\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/utils/CodeForcesAES.js",
    "content": "function getRCPC(a, b, c) {\n    a = toNumbers(a);\n    b = toNumbers(b);\n    c = toNumbers(c);\n    return toHex(slowAES.decrypt(c, 2, a, b));\n}\n\nfunction toNumbers(d) {\n    var e = [];\n    d.replace(/(..)/g, function(d) {\n        e.push(parseInt(d, 16))\n    });\n    return e\n}\n\nfunction toHex() {\n    for (var d = [], d = 1 == arguments.length && arguments[0].constructor == Array ? arguments[0] : arguments, e = \"\", f = 0; f < d.length; f++) e += (16 > d[f] ? \"0\" : \"\") + d[f].toString(16);\n    return e.toLowerCase()\n}\n\nvar slowAES = {\n    /*\n     * START AES SECTION\n     */\n    aes: {\n        // structure of valid key sizes\n        keySize: {\n            SIZE_128: 16,\n            SIZE_192: 24,\n            SIZE_256: 32\n        },\n\n        // Rijndael S-box\n        sbox: [\n            0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,\n            0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,\n            0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,\n            0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,\n            0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,\n            0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,\n            0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,\n            0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,\n            0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,\n            0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,\n            0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,\n            0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,\n            0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,\n            0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,\n            0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,\n            0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16\n        ],\n\n        // Rijndael Inverted S-box\n        rsbox: [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d],\n\n        /* rotate the word eight bits to the left */\n        rotate: function(word) {\n            var c = word[0];\n            for (var i = 0; i < 3; i++)\n                word[i] = word[i + 1];\n            word[3] = c;\n\n            return word;\n        },\n\n        // Rijndael Rcon\n        Rcon: [\n            0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,\n            0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,\n            0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,\n            0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d,\n            0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab,\n            0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,\n            0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25,\n            0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01,\n            0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,\n            0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa,\n            0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a,\n            0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02,\n            0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,\n            0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,\n            0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,\n            0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,\n            0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f,\n            0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,\n            0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33,\n            0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb\n        ],\n\n        G2X: [\n            0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16,\n            0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e,\n            0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 0x40, 0x42, 0x44, 0x46,\n            0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,\n            0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76,\n            0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e,\n            0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 0xa0, 0xa2, 0xa4, 0xa6,\n            0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,\n            0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6,\n            0xd8, 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee,\n            0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, 0x1b, 0x19, 0x1f, 0x1d,\n            0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05,\n            0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d,\n            0x23, 0x21, 0x27, 0x25, 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55,\n            0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, 0x7b, 0x79, 0x7f, 0x7d,\n            0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65,\n            0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d,\n            0x83, 0x81, 0x87, 0x85, 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5,\n            0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, 0xdb, 0xd9, 0xdf, 0xdd,\n            0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5,\n            0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed,\n            0xe3, 0xe1, 0xe7, 0xe5\n        ],\n\n        G3X: [\n            0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d,\n            0x14, 0x17, 0x12, 0x11, 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39,\n            0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, 0x60, 0x63, 0x66, 0x65,\n            0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71,\n            0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d,\n            0x44, 0x47, 0x42, 0x41, 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9,\n            0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, 0xf0, 0xf3, 0xf6, 0xf5,\n            0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1,\n            0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd,\n            0xb4, 0xb7, 0xb2, 0xb1, 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99,\n            0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, 0x9b, 0x98, 0x9d, 0x9e,\n            0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a,\n            0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6,\n            0xbf, 0xbc, 0xb9, 0xba, 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2,\n            0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, 0xcb, 0xc8, 0xcd, 0xce,\n            0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda,\n            0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46,\n            0x4f, 0x4c, 0x49, 0x4a, 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62,\n            0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, 0x3b, 0x38, 0x3d, 0x3e,\n            0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a,\n            0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16,\n            0x1f, 0x1c, 0x19, 0x1a\n        ],\n\n        G9X: [\n            0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53,\n            0x6c, 0x65, 0x7e, 0x77, 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf,\n            0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, 0x3b, 0x32, 0x29, 0x20,\n            0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,\n            0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8,\n            0xc7, 0xce, 0xd5, 0xdc, 0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49,\n            0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, 0xe6, 0xef, 0xf4, 0xfd,\n            0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91,\n            0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e,\n            0x21, 0x28, 0x33, 0x3a, 0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2,\n            0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, 0xec, 0xe5, 0xfe, 0xf7,\n            0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b,\n            0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f,\n            0x10, 0x19, 0x02, 0x0b, 0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8,\n            0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, 0x47, 0x4e, 0x55, 0x5c,\n            0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30,\n            0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9,\n            0xf6, 0xff, 0xe4, 0xed, 0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35,\n            0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, 0xa1, 0xa8, 0xb3, 0xba,\n            0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6,\n            0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62,\n            0x5d, 0x54, 0x4f, 0x46\n        ],\n\n        GBX: [\n            0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45,\n            0x74, 0x7f, 0x62, 0x69, 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81,\n            0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, 0x7b, 0x70, 0x6d, 0x66,\n            0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12,\n            0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e,\n            0xbf, 0xb4, 0xa9, 0xa2, 0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7,\n            0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, 0x46, 0x4d, 0x50, 0x5b,\n            0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f,\n            0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8,\n            0xf9, 0xf2, 0xef, 0xe4, 0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c,\n            0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, 0xf7, 0xfc, 0xe1, 0xea,\n            0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e,\n            0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02,\n            0x33, 0x38, 0x25, 0x2e, 0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd,\n            0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, 0x3c, 0x37, 0x2a, 0x21,\n            0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55,\n            0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44,\n            0x75, 0x7e, 0x63, 0x68, 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80,\n            0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, 0x7a, 0x71, 0x6c, 0x67,\n            0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13,\n            0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f,\n            0xbe, 0xb5, 0xa8, 0xa3\n        ],\n\n        GDX: [\n            0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f,\n            0x5c, 0x51, 0x46, 0x4b, 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3,\n            0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, 0xbb, 0xb6, 0xa1, 0xac,\n            0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0,\n            0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14,\n            0x37, 0x3a, 0x2d, 0x20, 0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e,\n            0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, 0xbd, 0xb0, 0xa7, 0xaa,\n            0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6,\n            0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9,\n            0x8a, 0x87, 0x90, 0x9d, 0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25,\n            0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, 0xda, 0xd7, 0xc0, 0xcd,\n            0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91,\n            0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75,\n            0x56, 0x5b, 0x4c, 0x41, 0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42,\n            0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, 0xb1, 0xbc, 0xab, 0xa6,\n            0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa,\n            0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8,\n            0xeb, 0xe6, 0xf1, 0xfc, 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44,\n            0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, 0x0c, 0x01, 0x16, 0x1b,\n            0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47,\n            0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3,\n            0x80, 0x8d, 0x9a, 0x97\n        ],\n\n        GEX: [\n            0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62,\n            0x48, 0x46, 0x54, 0x5a, 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca,\n            0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, 0xdb, 0xd5, 0xc7, 0xc9,\n            0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81,\n            0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59,\n            0x73, 0x7d, 0x6f, 0x61, 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87,\n            0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, 0x4d, 0x43, 0x51, 0x5f,\n            0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17,\n            0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14,\n            0x3e, 0x30, 0x22, 0x2c, 0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc,\n            0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, 0x41, 0x4f, 0x5d, 0x53,\n            0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b,\n            0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3,\n            0xe9, 0xe7, 0xf5, 0xfb, 0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0,\n            0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, 0x7a, 0x74, 0x66, 0x68,\n            0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20,\n            0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e,\n            0xa4, 0xaa, 0xb8, 0xb6, 0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26,\n            0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, 0x37, 0x39, 0x2b, 0x25,\n            0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d,\n            0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5,\n            0x9f, 0x91, 0x83, 0x8d\n        ],\n\n        // Key Schedule Core\n        core: function(word, iteration) {\n            /* rotate the 32-bit word 8 bits to the left */\n            word = this.rotate(word);\n            /* apply S-Box substitution on all 4 parts of the 32-bit word */\n            for (var i = 0; i < 4; ++i)\n                word[i] = this.sbox[word[i]];\n            /* XOR the output of the rcon operation with i to the first part (leftmost) only */\n            word[0] = word[0] ^ this.Rcon[iteration];\n            return word;\n        },\n\n        /* Rijndael's key expansion\n         * expands an 128,192,256 key into an 176,208,240 bytes key\n         *\n         * expandedKey is a pointer to an char array of large enough size\n         * key is a pointer to a non-expanded key\n         */\n        expandKey: function(key, size) {\n            var expandedKeySize = (16 * (this.numberOfRounds(size) + 1));\n\n            /* current expanded keySize, in bytes */\n            var currentSize = 0;\n            var rconIteration = 1;\n            var t = []; // temporary 4-byte variable\n\n            var expandedKey = [];\n            for (var i = 0; i < expandedKeySize; i++)\n                expandedKey[i] = 0;\n\n            /* set the 16,24,32 bytes of the expanded key to the input key */\n            for (var j = 0; j < size; j++)\n                expandedKey[j] = key[j];\n            currentSize += size;\n\n            while (currentSize < expandedKeySize) {\n                /* assign the previous 4 bytes to the temporary value t */\n                for (var k = 0; k < 4; k++)\n                    t[k] = expandedKey[(currentSize - 4) + k];\n\n                /* every 16,24,32 bytes we apply the core schedule to t\n                 * and increment rconIteration afterwards\n                 */\n                if (currentSize % size == 0)\n                    t = this.core(t, rconIteration++);\n\n                /* For 256-bit keys, we add an extra sbox to the calculation */\n                if (size == this.keySize.SIZE_256 && ((currentSize % size) == 16))\n                    for (var l = 0; l < 4; l++)\n                        t[l] = this.sbox[t[l]];\n\n                /* We XOR t with the four-byte block 16,24,32 bytes before the new expanded key.\n                 * This becomes the next four bytes in the expanded key.\n                 */\n                for (var m = 0; m < 4; m++) {\n                    expandedKey[currentSize] = expandedKey[currentSize - size] ^ t[m];\n                    currentSize++;\n                }\n            }\n            return expandedKey;\n        },\n\n        // Adds (XORs) the round key to the state\n        addRoundKey: function(state, roundKey) {\n            for (var i = 0; i < 16; i++)\n                state[i] ^= roundKey[i];\n            return state;\n        },\n\n        // Creates a round key from the given expanded key and the\n        // position within the expanded key.\n        createRoundKey: function(expandedKey, roundKeyPointer) {\n            var roundKey = [];\n            for (var i = 0; i < 4; i++)\n                for (var j = 0; j < 4; j++)\n                    roundKey[j * 4 + i] = expandedKey[roundKeyPointer + i * 4 + j];\n            return roundKey;\n        },\n\n        /* substitute all the values from the state with the value in the SBox\n         * using the state value as index for the SBox\n         */\n        subBytes: function(state, isInv) {\n            for (var i = 0; i < 16; i++)\n                state[i] = isInv ? this.rsbox[state[i]] : this.sbox[state[i]];\n            return state;\n        },\n\n        /* iterate over the 4 rows and call shiftRow() with that row */\n        shiftRows: function(state, isInv) {\n            for (var i = 0; i < 4; i++)\n                state = this.shiftRow(state, i * 4, i, isInv);\n            return state;\n        },\n\n        /* each iteration shifts the row to the left by 1 */\n        shiftRow: function(state, statePointer, nbr, isInv) {\n            for (var i = 0; i < nbr; i++) {\n                if (isInv) {\n                    var tmp = state[statePointer + 3];\n                    for (var j = 3; j > 0; j--)\n                        state[statePointer + j] = state[statePointer + j - 1];\n                    state[statePointer] = tmp;\n                } else {\n                    var tmp = state[statePointer];\n                    for (var j = 0; j < 3; j++)\n                        state[statePointer + j] = state[statePointer + j + 1];\n                    state[statePointer + 3] = tmp;\n                }\n            }\n            return state;\n        },\n\n        // galois multiplication of 8 bit characters a and b\n        galois_multiplication: function(a, b) {\n            var p = 0;\n            for (var counter = 0; counter < 8; counter++) {\n                if ((b & 1) == 1)\n                    p ^= a;\n                if (p > 0x100) p ^= 0x100;\n                var hi_bit_set = (a & 0x80); //keep p 8 bit\n                a <<= 1;\n                if (a > 0x100) a ^= 0x100; //keep a 8 bit\n                if (hi_bit_set == 0x80)\n                    a ^= 0x1b;\n                if (a > 0x100) a ^= 0x100; //keep a 8 bit\n                b >>= 1;\n                if (b > 0x100) b ^= 0x100; //keep b 8 bit\n            }\n            return p;\n        },\n\n        // galois multipication of the 4x4 matrix\n        mixColumns: function(state, isInv) {\n            var column = [];\n            /* iterate over the 4 columns */\n            for (var i = 0; i < 4; i++) {\n                /* construct one column by iterating over the 4 rows */\n                for (var j = 0; j < 4; j++)\n                    column[j] = state[(j * 4) + i];\n                /* apply the mixColumn on one column */\n                column = this.mixColumn(column, isInv);\n                /* put the values back into the state */\n                for (var k = 0; k < 4; k++)\n                    state[(k * 4) + i] = column[k];\n            }\n            return state;\n        },\n\n        // galois multipication of 1 column of the 4x4 matrix\n        mixColumn: function(column, isInv) {\n            var mult = [];\n            if (isInv)\n                mult = [14, 9, 13, 11];\n            else\n                mult = [2, 1, 1, 3];\n            var cpy = [];\n            for (var i = 0; i < 4; i++)\n                cpy[i] = column[i];\n\n            column[0] = this.galois_multiplication(cpy[0], mult[0]) ^\n                this.galois_multiplication(cpy[3], mult[1]) ^\n                this.galois_multiplication(cpy[2], mult[2]) ^\n                this.galois_multiplication(cpy[1], mult[3]);\n            column[1] = this.galois_multiplication(cpy[1], mult[0]) ^\n                this.galois_multiplication(cpy[0], mult[1]) ^\n                this.galois_multiplication(cpy[3], mult[2]) ^\n                this.galois_multiplication(cpy[2], mult[3]);\n            column[2] = this.galois_multiplication(cpy[2], mult[0]) ^\n                this.galois_multiplication(cpy[1], mult[1]) ^\n                this.galois_multiplication(cpy[0], mult[2]) ^\n                this.galois_multiplication(cpy[3], mult[3]);\n            column[3] = this.galois_multiplication(cpy[3], mult[0]) ^\n                this.galois_multiplication(cpy[2], mult[1]) ^\n                this.galois_multiplication(cpy[1], mult[2]) ^\n                this.galois_multiplication(cpy[0], mult[3]);\n            return column;\n        },\n\n        // applies the 4 operations of the forward round in sequence\n        round: function(state, roundKey) {\n            state = this.subBytes(state, false);\n            state = this.shiftRows(state, false);\n            state = this.mixColumns(state, false);\n            state = this.addRoundKey(state, roundKey);\n            return state;\n        },\n\n        // applies the 4 operations of the inverse round in sequence\n        invRound: function(state, roundKey) {\n            state = this.shiftRows(state, true);\n            state = this.subBytes(state, true);\n            state = this.addRoundKey(state, roundKey);\n            state = this.mixColumns(state, true);\n            return state;\n        },\n\n        /*\n         * Perform the initial operations, the standard round, and the final operations\n         * of the forward aes, creating a round key for each round\n         */\n        main: function(state, expandedKey, nbrRounds) {\n            state = this.addRoundKey(state, this.createRoundKey(expandedKey, 0));\n            for (var i = 1; i < nbrRounds; i++)\n                state = this.round(state, this.createRoundKey(expandedKey, 16 * i));\n            state = this.subBytes(state, false);\n            state = this.shiftRows(state, false);\n            state = this.addRoundKey(state, this.createRoundKey(expandedKey, 16 * nbrRounds));\n            return state;\n        },\n\n        /*\n         * Perform the initial operations, the standard round, and the final operations\n         * of the inverse aes, creating a round key for each round\n         */\n        invMain: function(state, expandedKey, nbrRounds) {\n            state = this.addRoundKey(state, this.createRoundKey(expandedKey, 16 * nbrRounds));\n            for (var i = nbrRounds - 1; i > 0; i--)\n                state = this.invRound(state, this.createRoundKey(expandedKey, 16 * i));\n            state = this.shiftRows(state, true);\n            state = this.subBytes(state, true);\n            state = this.addRoundKey(state, this.createRoundKey(expandedKey, 0));\n            return state;\n        },\n\n        numberOfRounds: function(size) {\n            var nbrRounds;\n            switch (size) /* set the number of rounds */ {\n                case this.keySize.SIZE_128:\n                    nbrRounds = 10;\n                    break;\n                case this.keySize.SIZE_192:\n                    nbrRounds = 12;\n                    break;\n                case this.keySize.SIZE_256:\n                    nbrRounds = 14;\n                    break;\n                default:\n                    return null;\n                    break;\n            }\n            return nbrRounds;\n        },\n\n        // encrypts a 128 bit input block against the given key of size specified\n        encrypt: function(input, key, size) {\n            var output = [];\n            var block = []; /* the 128 bit block to encode */\n            var nbrRounds = this.numberOfRounds(size);\n            /* Set the block values, for the block:\n             * a0,0 a0,1 a0,2 a0,3\n             * a1,0 a1,1 a1,2 a1,3\n             * a2,0 a2,1 a2,2 a2,3\n             * a3,0 a3,1 a3,2 a3,3\n             * the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3\n             */\n            for (var i = 0; i < 4; i++) /* iterate over the columns */\n                for (var j = 0; j < 4; j++) /* iterate over the rows */\n                    block[(i + (j * 4))] = input[(i * 4) + j];\n\n            /* expand the key into an 176, 208, 240 bytes key */\n            var expandedKey = this.expandKey(key, size); /* the expanded key */\n            /* encrypt the block using the expandedKey */\n            block = this.main(block, expandedKey, nbrRounds);\n            for (var k = 0; k < 4; k++) /* unmap the block again into the output */\n                for (var l = 0; l < 4; l++) /* iterate over the rows */\n                    output[(k * 4) + l] = block[(k + (l * 4))];\n            return output;\n        },\n\n        // decrypts a 128 bit input block against the given key of size specified\n        decrypt: function(input, key, size) {\n            var output = [];\n            var block = []; /* the 128 bit block to decode */\n            var nbrRounds = this.numberOfRounds(size);\n            /* Set the block values, for the block:\n             * a0,0 a0,1 a0,2 a0,3\n             * a1,0 a1,1 a1,2 a1,3\n             * a2,0 a2,1 a2,2 a2,3\n             * a3,0 a3,1 a3,2 a3,3\n             * the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3\n             */\n            for (var i = 0; i < 4; i++) /* iterate over the columns */\n                for (var j = 0; j < 4; j++) /* iterate over the rows */\n                    block[(i + (j * 4))] = input[(i * 4) + j];\n            /* expand the key into an 176, 208, 240 bytes key */\n            var expandedKey = this.expandKey(key, size);\n            /* decrypt the block using the expandedKey */\n            block = this.invMain(block, expandedKey, nbrRounds);\n            for (var k = 0; k < 4; k++) /* unmap the block again into the output */\n                for (var l = 0; l < 4; l++) /* iterate over the rows */\n                    output[(k * 4) + l] = block[(k + (l * 4))];\n            return output;\n        }\n    },\n    /*\n     * END AES SECTION\n     */\n\n    /*\n     * START MODE OF OPERATION SECTION\n     */\n    //structure of supported modes of operation\n    modeOfOperation: {\n        OFB: 0,\n        CFB: 1,\n        CBC: 2\n    },\n\n    // get a 16 byte block (aes operates on 128bits)\n    getBlock: function(bytesIn, start, end, mode) {\n        if (end - start > 16)\n            end = start + 16;\n\n        return bytesIn.slice(start, end);\n    },\n\n    /*\n     * Mode of Operation Encryption\n     * bytesIn - Input String as array of bytes\n     * mode - mode of type modeOfOperation\n     * key - a number array of length 'size'\n     * size - the bit length of the key\n     * iv - the 128 bit number array Initialization Vector\n     */\n    encrypt: function(bytesIn, mode, key, iv) {\n        var size = key.length;\n        if (iv.length % 16) {\n            throw 'iv length must be 128 bits.';\n        }\n        // the AES input/output\n        var byteArray = [];\n        var input = [];\n        var output = [];\n        var ciphertext = [];\n        var cipherOut = [];\n        // char firstRound\n        var firstRound = true;\n        if (mode == this.modeOfOperation.CBC)\n            this.padBytesIn(bytesIn);\n        if (bytesIn !== null) {\n            for (var j = 0; j < Math.ceil(bytesIn.length / 16); j++) {\n                var start = j * 16;\n                var end = j * 16 + 16;\n                if (j * 16 + 16 > bytesIn.length)\n                    end = bytesIn.length;\n                byteArray = this.getBlock(bytesIn, start, end, mode);\n                if (mode == this.modeOfOperation.CFB) {\n                    if (firstRound) {\n                        output = this.aes.encrypt(iv, key, size);\n                        firstRound = false;\n                    } else\n                        output = this.aes.encrypt(input, key, size);\n                    for (var i = 0; i < 16; i++)\n                        ciphertext[i] = byteArray[i] ^ output[i];\n                    for (var k = 0; k < end - start; k++)\n                        cipherOut.push(ciphertext[k]);\n                    input = ciphertext;\n                } else if (mode == this.modeOfOperation.OFB) {\n                    if (firstRound) {\n                        output = this.aes.encrypt(iv, key, size);\n                        firstRound = false;\n                    } else\n                        output = this.aes.encrypt(input, key, size);\n                    for (var i = 0; i < 16; i++)\n                        ciphertext[i] = byteArray[i] ^ output[i];\n                    for (var k = 0; k < end - start; k++)\n                        cipherOut.push(ciphertext[k]);\n                    input = output;\n                } else if (mode == this.modeOfOperation.CBC) {\n                    for (var i = 0; i < 16; i++)\n                        input[i] = byteArray[i] ^ ((firstRound) ? iv[i] : ciphertext[i]);\n                    firstRound = false;\n                    ciphertext = this.aes.encrypt(input, key, size);\n                    // always 16 bytes because of the padding for CBC\n                    for (var k = 0; k < 16; k++)\n                        cipherOut.push(ciphertext[k]);\n                }\n            }\n        }\n        return cipherOut;\n    },\n\n    /*\n     * Mode of Operation Decryption\n     * cipherIn - Encrypted String as array of bytes\n     * originalsize - The unencrypted string length - required for CBC\n     * mode - mode of type modeOfOperation\n     * key - a number array of length 'size'\n     * size - the bit length of the key\n     * iv - the 128 bit number array Initialization Vector\n     */\n    decrypt: function(cipherIn, mode, key, iv) {\n        var size = key.length;\n        if (iv.length % 16) {\n            throw 'iv length must be 128 bits.';\n        }\n        // the AES input/output\n        var ciphertext = [];\n        var input = [];\n        var output = [];\n        var byteArray = [];\n        var bytesOut = [];\n        // char firstRound\n        var firstRound = true;\n        if (cipherIn !== null) {\n            for (var j = 0; j < Math.ceil(cipherIn.length / 16); j++) {\n                var start = j * 16;\n                var end = j * 16 + 16;\n                if (j * 16 + 16 > cipherIn.length)\n                    end = cipherIn.length;\n                ciphertext = this.getBlock(cipherIn, start, end, mode);\n                if (mode == this.modeOfOperation.CFB) {\n                    if (firstRound) {\n                        output = this.aes.encrypt(iv, key, size);\n                        firstRound = false;\n                    } else\n                        output = this.aes.encrypt(input, key, size);\n                    for (i = 0; i < 16; i++)\n                        byteArray[i] = output[i] ^ ciphertext[i];\n                    for (var k = 0; k < end - start; k++)\n                        bytesOut.push(byteArray[k]);\n                    input = ciphertext;\n                } else if (mode == this.modeOfOperation.OFB) {\n                    if (firstRound) {\n                        output = this.aes.encrypt(iv, key, size);\n                        firstRound = false;\n                    } else\n                        output = this.aes.encrypt(input, key, size);\n                    for (i = 0; i < 16; i++)\n                        byteArray[i] = output[i] ^ ciphertext[i];\n                    for (var k = 0; k < end - start; k++)\n                        bytesOut.push(byteArray[k]);\n                    input = output;\n                } else if (mode == this.modeOfOperation.CBC) {\n                    output = this.aes.decrypt(ciphertext, key, size);\n                    for (i = 0; i < 16; i++)\n                        byteArray[i] = ((firstRound) ? iv[i] : input[i]) ^ output[i];\n                    firstRound = false;\n                    for (var k = 0; k < end - start; k++)\n                        bytesOut.push(byteArray[k]);\n                    input = ciphertext;\n                }\n            }\n            if (mode == this.modeOfOperation.CBC)\n                this.unpadBytesOut(bytesOut);\n        }\n        return bytesOut;\n    },\n    padBytesIn: function(data) {\n        var len = data.length;\n        var padByte = 16 - (len % 16);\n        for (var i = 0; i < padByte; i++) {\n            data.push(padByte);\n        }\n    },\n    unpadBytesOut: function(data) {\n            var padCount = 0;\n            var padByte = -1;\n            var blockSize = 16;\n            if (data.length > 16) {\n                for (var i = data.length - 1; i >= data.length - 1 - blockSize; i--) {\n                    if (data[i] <= blockSize) {\n                        if (padByte == -1)\n                            padByte = data[i];\n                        if (data[i] != padByte) {\n                            padCount = 0;\n                            break;\n                        }\n                        padCount++;\n                    } else\n                        break;\n                    if (padCount == padByte)\n                        break;\n                }\n                if (padCount > 0)\n                    data.splice(data.length - padCount, padCount);\n            }\n        }\n        /*\n         * END MODE OF OPERATION SECTION\n         */\n};"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/utils/CodeForcesUtils.java",
    "content": "package com.simplefanc.voj.common.utils;\n\nimport lombok.extern.slf4j.Slf4j;\n\nimport javax.script.*;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.util.List;\n\n@Slf4j(topic = \"voj\")\npublic class CodeForcesUtils {\n    private static String RCPC;\n\n    public static String getRCPC() {\n        return RCPC;\n    }\n\n    public static void updateRCPC(List<String> list) {\n\n        ScriptEngine se = new ScriptEngineManager().getEngineByName(\"javascript\");\n        Bindings bindings = se.createBindings();\n        bindings.put(\"string\", 4);\n        se.setBindings(bindings, ScriptContext.ENGINE_SCOPE);\n\n        String file = \"CodeForcesAES.js\";\n        try {\n            se.eval(file);\n            // 是否可调用\n            if (se instanceof Invocable) {\n                Invocable in = (Invocable) se;\n                RCPC = (String) in.invokeFunction(\"getRCPC\", list.get(0), list.get(1), list.get(2));\n            }\n        } catch (ScriptException e) {\n            log.error(\"CodeForcesUtils.updateRCPC throw ScriptException\", e);\n        } catch (NoSuchMethodException e) {\n            log.error(\"CodeForcesUtils.updateRCPC throw NoSuchMethodException\", e);\n        }\n    }\n\n    public static void downloadPDF(String urlStr, String savePath) {\n        try {\n            int byteRead;\n            URL url = new URL(urlStr);\n            URLConnection conn = url.openConnection();\n            conn.setConnectTimeout(30000);\n            conn.setReadTimeout(30000);\n            conn.setRequestProperty(\"User-Agent\",\n                    \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36\");\n            conn.setRequestProperty(\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\");\n            conn.setRequestProperty(\"Accept-Language\", \"zh-CN,zh;q=0.9,en;q=0.8\");\n            // 注意编码，gzip可能会乱码\n            conn.setRequestProperty(\"Accept-Encoding\", \"gzip, deflate, br\");\n            conn.setRequestProperty(\"Content-Encoding\", \"utf8\");\n            conn.setRequestProperty(\"Connection\", \"keep-alive\");\n            conn.setRequestProperty(\"Upgrade-Insecure-Requests\", \"1\");\n            conn.setRequestProperty(\"cookie\", \"RCPC=\" + getRCPC());\n            conn.setRequestProperty(\"Cache-Control\", \"max-age=0\");\n            conn.setRequestProperty(\"Content-Type\", \"application/pdf\");\n\n            InputStream inStream = conn.getInputStream();\n            FileOutputStream fs = new FileOutputStream(savePath);\n\n            byte[] buffer = new byte[1024];\n            while ((byteRead = inStream.read(buffer)) != -1) {\n                fs.write(buffer, 0, byteRead);\n            }\n            inStream.close();\n            fs.close();\n        } catch (FileNotFoundException e1) {\n            log.error(\"CodeForcesUtils.downloadPDF throw FileNotFoundException ->\", e1);\n        } catch (MalformedURLException e2) {\n            log.error(\"CodeForcesUtils.downloadPDF throw MalformedURLException ->\", e2);\n        } catch (IOException e3) {\n            log.error(\"CodeForcesUtils.downloadPDF throw IOException ->\", e3);\n        }\n    }\n}\n"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/utils/IpUtil.java",
    "content": "package com.simplefanc.voj.common.utils;\n\nimport lombok.extern.slf4j.Slf4j;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.net.*;\nimport java.util.Enumeration;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/30 11:12\n * @Description:\n */\n@Slf4j(topic = \"voj\")\npublic class IpUtil {\n\n    public static String getUserIpAddr(HttpServletRequest request) {\n        String ipAddress = null;\n        try {\n            ipAddress = request.getHeader(\"x-forwarded-for\");\n            if (ipAddress == null || ipAddress.length() == 0 || \"unknown\".equalsIgnoreCase(ipAddress)) {\n                ipAddress = request.getHeader(\"Proxy-Client-IP\");\n            }\n            if (ipAddress == null || ipAddress.length() == 0 || \"unknown\".equalsIgnoreCase(ipAddress)) {\n                ipAddress = request.getHeader(\"WL-Proxy-Client-IP\");\n            }\n            if (ipAddress == null || ipAddress.length() == 0 || \"unknown\".equalsIgnoreCase(ipAddress)) {\n                ipAddress = request.getRemoteAddr();\n                if (\"127.0.0.1\".equals(ipAddress)) {\n                    // 根据网卡取本机配置的IP\n                    try {\n                        ipAddress = InetAddress.getLocalHost().getHostAddress();\n                    } catch (UnknownHostException e) {\n                        log.error(\"用户ip获取异常------->{}\", e.getMessage());\n                    }\n                }\n            }\n            // 通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割\n            if (ipAddress != null) {\n                if (ipAddress.contains(\",\")) {\n                    return ipAddress.split(\",\")[0];\n                } else {\n                    return ipAddress;\n                }\n            } else {\n                return \"\";\n            }\n        } catch (Exception e) {\n            log.error(\"用户ip获取异常------->{}\", e.getMessage());\n            return \"\";\n        }\n    }\n\n    public static String getServiceIp() {\n        InetAddress address = null;\n        try {\n            address = InetAddress.getLocalHost();\n        } catch (UnknownHostException e) {\n            log.error(\"本地ip获取异常---------->{}\", e.getMessage());\n        }\n        // 返回IP地址\n        return address.getHostAddress();\n    }\n\n    public static String getLocalIpv4Address() {\n        Enumeration<NetworkInterface> ifaces = null;\n        try {\n            ifaces = NetworkInterface.getNetworkInterfaces();\n        } catch (SocketException e) {\n            log.error(\"本地ipv4获取异常---------->{}\", e.getMessage());\n        }\n        String siteLocalAddress = null;\n        while (ifaces.hasMoreElements()) {\n            NetworkInterface iface = ifaces.nextElement();\n            Enumeration<InetAddress> addresses = iface.getInetAddresses();\n            while (addresses.hasMoreElements()) {\n                InetAddress addr = addresses.nextElement();\n                String hostAddress = addr.getHostAddress();\n                if (addr instanceof Inet4Address) {\n                    if (addr.isSiteLocalAddress()) {\n                        siteLocalAddress = hostAddress;\n                    } else {\n                        return hostAddress;\n                    }\n                }\n            }\n        }\n        return siteLocalAddress == null ? \"\" : siteLocalAddress;\n    }\n\n}"
  },
  {
    "path": "voj-common/src/main/java/com/simplefanc/voj/common/utils/Tools.java",
    "content": "package com.simplefanc.voj.common.utils;\n\nimport org.springframework.beans.factory.config.BeanDefinition;\nimport org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;\nimport org.springframework.core.type.filter.AssignableTypeFilter;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * 公用工具类\n *\n * @author Administrator\n */\npublic class Tools {\n\n    public static <T> List<Class<? extends T>> findSubClasses(String packagePath, Class<T> parentClass)\n            throws ClassNotFoundException {\n        List<Class<? extends T>> result = new ArrayList<Class<? extends T>>();\n\n        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);\n        provider.addIncludeFilter(new AssignableTypeFilter(parentClass));\n\n        Set<BeanDefinition> components = provider.findCandidateComponents(packagePath);\n        for (BeanDefinition component : components) {\n            Class<? extends T> clazz = (Class<? extends T>) Class.forName(component.getBeanClassName());\n            result.add(clazz);\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>voj</artifactId>\n        <groupId>com.simplefanc</groupId>\n        <version>1.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>voj-judger</artifactId>\n    <version>1.0</version>\n    <packaging>jar</packaging>\n\n    <name>voj-judger</name>\n    <dependencies>\n        <dependency>\n            <groupId>com.simplefanc</groupId>\n            <artifactId>voj-common</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>druid</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n        </dependency>\n        <!-- hutool工具类-->\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-all</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.github.oshi</groupId>\n            <artifactId>oshi-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.httpcomponents</groupId>\n            <artifactId>httpclient</artifactId>\n        </dependency>\n        <!--单元测试-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/JudgeServerApplication.java",
    "content": "package com.simplefanc.voj.judger;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.scheduling.annotation.EnableAsync;\nimport org.springframework.transaction.annotation.EnableTransactionManagement;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/29 22:12\n * @Description: 判题机服务系统启动类\n */\n@EnableDiscoveryClient // 开启服务注册发现功能\n@SpringBootApplication\n@EnableAsync(proxyTargetClass = true) // 开启异步注解\n@EnableTransactionManagement\npublic class JudgeServerApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(JudgeServerApplication.class, args);\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/constants/CompileConfig.java",
    "content": "package com.simplefanc.voj.judger.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.List;\n\n/**\n * {0} --> tmpfs_dir\n * {1} --> srcName\n * {2} --> exeName\n */\n@Getter\n@AllArgsConstructor\npublic enum CompileConfig {\n\n    C(\"C\", \"main.c\", \"main\", 3000L, 10000L, 256 * 1024 * 1024L,\n            \"/usr/bin/gcc -DONLINE_JUDGE -w -fmax-errors=3 -std=c11 {1} -lm -o {2}\", Constants.DEFAULT_ENV),\n\n    CWithO2(\"C With O2\", \"main.c\", \"main\", 3000L, 10000L, 256 * 1024 * 1024L,\n            \"/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c11 {1} -lm -o {2}\", Constants.DEFAULT_ENV),\n\n    CPP(\"C++\", \"main.cpp\", \"main\", 10000L, 20000L, 512 * 1024 * 1024L,\n            \"/usr/bin/g++ -DONLINE_JUDGE -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}\", Constants.DEFAULT_ENV),\n\n    CPPWithO2(\"C++ With O2\", \"main.cpp\", \"main\", 10000L, 20000L, 512 * 1024 * 1024L,\n            \"/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}\", Constants.DEFAULT_ENV),\n\n    JAVA(\"Java\", \"Main.java\", \"Main.jar\", 10000L, 20000L, 512 * 1024 * 1024L,\n            \"/bin/bash -c \\\"javac -encoding utf8 {1} && jar -cvf {2} *.class\\\"\", Constants.DEFAULT_ENV),\n\n    PYTHON2(\"Python2\", \"main.py\", \"main.pyc\", 3000L, 10000L, 128 * 1024 * 1024L, \"/usr/bin/python -m py_compile ./{1}\",\n            Constants.DEFAULT_ENV),\n\n    PYTHON3(\"Python3\", \"main.py\", \"__pycache__/main.cpython-37.pyc\", 3000L, 10000L, 128 * 1024 * 1024L,\n            \"/usr/bin/python3.7 -m py_compile ./{1}\", Constants.DEFAULT_ENV),\n\n    GOLANG(\"Golang\", \"main.go\", \"main\", 3000L, 5000L, 512 * 1024 * 1024L, \"/usr/bin/go build -o {2} {1}\",\n            Constants.DEFAULT_ENV),\n\n    CS(\"C#\", \"Main.cs\", \"main\", 5000L, 10000L, 512 * 1024 * 1024L, \"/usr/bin/mcs -optimize+ -out:{0}/{2} {0}/{1}\",\n            Constants.DEFAULT_ENV),\n\n    PyPy2(\"PyPy2\", \"main.py\", \"__pycache__/main.pypy-73.pyc\", 3000L, 10000L, 256 * 1024 * 1024L,\n            \"/usr/bin/pypy -m py_compile {0}/{1}\", Constants.DEFAULT_ENV),\n\n    PyPy3(\"PyPy3\", \"main.py\", \"__pycache__/main.pypy38.pyc\", 3000L, 10000L, 256 * 1024 * 1024L,\n            \"/usr/bin/pypy3 -m py_compile {0}/{1}\", Constants.DEFAULT_ENV),\n\n    SPJ_C(\"SPJ-C\", \"spj.c\", \"spj\", 3000L, 5000L, 512 * 1024 * 1024L,\n            \"/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {1} -lm -o {2}\", Constants.DEFAULT_ENV),\n\n    SPJ_CPP(\"SPJ-C++\", \"spj.cpp\", \"spj\", 10000L, 20000L, 512 * 1024 * 1024L,\n            \"/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}\", Constants.DEFAULT_ENV),\n\n    INTERACTIVE_C(\"INTERACTIVE-C\", \"interactive.c\", \"interactive\", 3000L, 5000L, 512 * 1024 * 1024L,\n            \"/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {1} -lm -o {2}\", Constants.DEFAULT_ENV),\n\n    INTERACTIVE_CPP(\"INTERACTIVE-C++\", \"interactive.cpp\", \"interactive\", 10000L, 20000L, 512 * 1024 * 1024L,\n            \"/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}\", Constants.DEFAULT_ENV);\n\n    private final String language;\n\n    private final String srcName;\n\n    private final String exeName;\n\n    private final Long maxCpuTime;\n\n    private final Long maxRealTime;\n\n    private final Long maxMemory;\n\n    private final String command;\n\n    private final List<String> envs;\n\n    public static CompileConfig getCompilerByLanguage(String language) {\n        for (CompileConfig compileConfig : CompileConfig.values()) {\n            if (compileConfig.getLanguage().equals(language)) {\n                return compileConfig;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/constants/Constants.java",
    "content": "package com.simplefanc.voj.judger.common.constants;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/1 13:00\n * @Description: 常量类\n */\npublic interface Constants {\n\n    List<String> DEFAULT_ENV = Arrays.asList(\n            \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n            \"LANG=en_US.UTF-8\",\n            \"LANGUAGE=en_US:en\",\n            \"HOME=/w\");\n\n    List<String> PYTHON3_ENV = Arrays.asList(\n            \"LANG=en_US.UTF-8\",\n            \"LANGUAGE=en_US:en\",\n            \"PYTHONIOENCODING=utf-8\");\n\n    List<String> GOLANG_ENV = Arrays.asList(\n            \"GODEBUG=madvdontneed=1\",\n            \"GOCACHE=off\",\n            \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n            \"LANG=en_US.UTF-8\",\n            \"LANGUAGE=en_US:en\");\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/constants/JudgeDir.java",
    "content": "package com.simplefanc.voj.judger.common.constants;\n\n/**\n * @author chenfan\n * @date 2022/4/18 16:27\n **/\npublic interface JudgeDir {\n\n    String RUN_WORKPLACE_DIR = \"/judge/run\";\n\n    String TEST_CASE_DIR = \"/judge/testcase\";\n\n    String SPJ_WORKPLACE_DIR = \"/judge/spj\";\n\n    String INTERACTIVE_WORKPLACE_DIR = \"/judge/interactive\";\n\n    String TMPFS_DIR = \"/w\";\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/constants/JudgeLanguage.java",
    "content": "package com.simplefanc.voj.judger.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n@Getter\n@AllArgsConstructor\npublic enum JudgeLanguage {\n    C(\"C\"),\n    CWithO2(\"C With O2\"),\n    CPP(\"C++\"),\n    CPPWithO2(\"C++ With O2\"),\n    JAVA(\"Java\"),\n    PYTHON2(\"Python2\"),\n    PYTHON3(\"Python3\"),\n    GOLANG(\"Golang\"),\n    CS(\"C#\");\n    private final String language;\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/constants/JudgeServerConstant.java",
    "content": "package com.simplefanc.voj.judger.common.constants;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * @author chenfan\n * @date 2022/5/7 21:46\n **/\npublic interface JudgeServerConstant {\n    List<String> LANGUAGE_LIST = Arrays.asList(\"G++ 7.5.0\", \"GCC 7.5.0\", \"Python 3.7.5\", \"Python 2.7.17\", \"OpenJDK 1.8\", \"Golang 1.16\",\n            \"C# Mono 4.6.2\", \"PHP 7.3.33\", \"JavaScript Node 14.19.0\", \"JavaScript V8 8.4.109\",\n            \"PyPy 2.7.18 (7.3.8)\", \"PyPy 3.8.12 (7.3.8)\");\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/constants/RunConfig.java",
    "content": "package com.simplefanc.voj.judger.common.constants;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.List;\n\n/**\n * {0} --> tmpfs_dir\n * {1} --> exeName (user or spj)\n * {2} --> The test case standard input file name of question\n * {3} --> The user's program output file name of question\n * {4} --> The test case standard output file name of question\n */\n@Getter\n@AllArgsConstructor\npublic enum RunConfig {\n\n    C(\"C\", \"{0}/{1}\", \"main\", Constants.DEFAULT_ENV),\n\n    CWithO2(\"C With O2\", \"{0}/{1}\", \"main\", Constants.DEFAULT_ENV),\n\n    CPP(\"C++\", \"{0}/{1}\", \"main\", Constants.DEFAULT_ENV),\n\n    CPPWithO2(\"C++ With O2\", \"{0}/{1}\", \"main\", Constants.DEFAULT_ENV),\n\n    JAVA(\"Java\", \"/usr/bin/java -cp {0}/{1} Main\", \"Main.jar\", Constants.DEFAULT_ENV),\n\n    PYTHON2(\"Python2\", \"/usr/bin/python {1}\", \"main\", Constants.DEFAULT_ENV),\n\n    PYTHON3(\"Python3\", \"/usr/bin/python3.7 {1}\", \"main\", Constants.PYTHON3_ENV),\n\n    GOLANG(\"Golang\", \"{0}/{1}\", \"main\", Constants.GOLANG_ENV),\n\n    CS(\"C#\", \"/usr/bin/mono {0}/{1}\", \"main\", Constants.DEFAULT_ENV),\n\n    PyPy2(\"PyPy2\", \"/usr/bin/pypy {1}\", \"main.pyc\", Constants.DEFAULT_ENV),\n\n    PyPy3(\"PyPy3\", \"/usr/bin/pypy3 {1}\", \"main.pyc\", Constants.PYTHON3_ENV),\n\n    PHP(\"PHP\", \"/usr/bin/php {1}\", \"main.php\", Constants.DEFAULT_ENV),\n\n    JS_NODE(\"JavaScript Node\", \"/usr/bin/node {1}\", \"main.js\", Constants.DEFAULT_ENV),\n\n    JS_V8(\"JavaScript V8\", \"/usr/bin/jsv8/d8 {1}\", \"main.js\", Constants.DEFAULT_ENV),\n\n    SPJ_C(\"SPJ-C\", \"{0}/{1} {2} {3} {4}\", \"spj\", Constants.DEFAULT_ENV),\n\n    SPJ_CPP(\"SPJ-C++\", \"{0}/{1} {2} {3} {4}\", \"spj\", Constants.DEFAULT_ENV),\n\n    INTERACTIVE_C(\"INTERACTIVE-C\", \"{0}/{1} {2} {3} {4}\", \"interactive\", Constants.DEFAULT_ENV),\n\n    INTERACTIVE_CPP(\"INTERACTIVE-C++\", \"{0}/{1} {2} {3} {4}\", \"interactive\", Constants.DEFAULT_ENV);\n\n    private final String language;\n\n    private final String command;\n\n    private final String exeName;\n\n    private final List<String> envs;\n\n    public static RunConfig getRunnerByLanguage(String language) {\n        for (RunConfig runConfig : RunConfig.values()) {\n            if (runConfig.getLanguage().equals(language)) {\n                return runConfig;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/exception/CompileException.java",
    "content": "package com.simplefanc.voj.judger.common.exception;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/31 00:16\n * @Description:\n */\n@Data\npublic class CompileException extends Exception {\n\n    private String stdout;\n\n    private String stderr;\n\n    public CompileException(String message, String stdout, String stderr) {\n        super(message);\n        this.stdout = stdout;\n        this.stderr = stderr;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/exception/RuntimeException.java",
    "content": "package com.simplefanc.voj.judger.common.exception;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/31 00:16\n * @Description:\n */\n@Data\npublic class RuntimeException extends Exception {\n\n    private String stdout;\n\n    private String stderr;\n\n    public RuntimeException(String message, String stdout, String stderr) {\n        super(message);\n        this.stdout = stdout;\n        this.stderr = stderr;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/exception/SubmitException.java",
    "content": "package com.simplefanc.voj.judger.common.exception;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/16 13:52\n * @Description:\n */\n@Data\npublic class SubmitException extends Exception {\n\n    private String stdout;\n\n    private String stderr;\n\n    public SubmitException(String message, String stdout, String stderr) {\n        super(message);\n        this.stdout = stdout;\n        this.stderr = stderr;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/exception/SystemException.java",
    "content": "package com.simplefanc.voj.judger.common.exception;\n\nimport lombok.Data;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/31 00:17\n * @Description:\n */\n@Data\npublic class SystemException extends Exception {\n\n    private String stdout;\n\n    private String stderr;\n\n    public SystemException(String message, String stdout, String stderr) {\n        super(message + \" \" + stderr);\n        this.stdout = stdout;\n        this.stderr = stderr;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/utils/JudgeUtil.java",
    "content": "package com.simplefanc.voj.judger.common.utils;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\n\nimport java.util.*;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/24 19:16\n * @Description:\n */\npublic class JudgeUtil {\n\n    @SuppressWarnings(\"All\")\n    public static HashMap<String, String> getProblemExtraFileMap(Problem problem, String type) {\n        if (\"user\".equals(type)) {\n            if (StrUtil.isNotEmpty(problem.getUserExtraFile())) {\n                return (HashMap<String, String>) JSONUtil.toBean(problem.getUserExtraFile(), Map.class);\n            }\n        } else if (\"judge\".equals(type)) {\n            if (StrUtil.isNotEmpty(problem.getJudgeExtraFile())) {\n                return (HashMap<String, String>) JSONUtil.toBean(problem.getJudgeExtraFile(), Map.class);\n            }\n        }\n        return null;\n    }\n\n    public static List<String> translateCommandline(String toProcess) {\n        if (toProcess != null && !toProcess.isEmpty()) {\n            int state = 0;\n            StringTokenizer tok = new StringTokenizer(toProcess, \"\\\"' \", true);\n            List<String> result = new ArrayList<>();\n            StringBuilder current = new StringBuilder();\n            boolean lastTokenHasBeenQuoted = false;\n\n            while (true) {\n                while (tok.hasMoreTokens()) {\n                    String nextTok = tok.nextToken();\n                    switch (state) {\n                        case 1:\n                            if (\"'\".equals(nextTok)) {\n                                lastTokenHasBeenQuoted = true;\n                                state = 0;\n                            } else {\n                                current.append(nextTok);\n                            }\n                            continue;\n                        case 2:\n                            if (\"\\\"\".equals(nextTok)) {\n                                lastTokenHasBeenQuoted = true;\n                                state = 0;\n                            } else {\n                                current.append(nextTok);\n                            }\n                            continue;\n                    }\n\n                    if (\"'\".equals(nextTok)) {\n                        state = 1;\n                    } else if (\"\\\"\".equals(nextTok)) {\n                        state = 2;\n                    } else if (\" \".equals(nextTok)) {\n                        if (lastTokenHasBeenQuoted || current.length() > 0) {\n                            result.add(current.toString());\n                            current.setLength(0);\n                        }\n                    } else {\n                        current.append(nextTok);\n                    }\n\n                    lastTokenHasBeenQuoted = false;\n                }\n\n                if (lastTokenHasBeenQuoted || current.length() > 0) {\n                    result.add(current.toString());\n                }\n\n                if (state != 1 && state != 2) {\n                    return result;\n                }\n\n                throw new RuntimeException(\"unbalanced quotes in \" + toProcess);\n            }\n        } else {\n            return new ArrayList<>();\n        }\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/common/utils/ThreadPoolUtil.java",
    "content": "package com.simplefanc.voj.judger.common.utils;\n\nimport java.util.concurrent.*;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/21 12:06\n * @Description:\n */\npublic class ThreadPoolUtil {\n\n    private static final int CPU_NUM = Runtime.getRuntime().availableProcessors();\n\n    private static ExecutorService executorService;\n\n    private ThreadPoolUtil() {\n        // 手动创建线程池.\n        executorService = new ThreadPoolExecutor(\n                // 核心线程数\n                CPU_NUM,\n                // 最大线程数。最多几个线程并发。\n                CPU_NUM + 1,\n                // 当非核心线程无任务时，几秒后结束该线程\n                3,\n                // 结束线程时间单位\n                TimeUnit.SECONDS,\n                // 工作队列：阻塞队列，限制等候线程数\n                new LinkedBlockingDeque<>(200 * CPU_NUM),\n                Executors.defaultThreadFactory(),\n                // 队列满了，尝试去和最早的竞争，也不会抛出异常！\n                new ThreadPoolExecutor.DiscardOldestPolicy());\n    }\n\n    public static ThreadPoolUtil getInstance() {\n        return PluginConfigHolder.INSTANCE;\n    }\n\n    public ExecutorService getThreadPool() {\n        return executorService;\n    }\n\n    private static class PluginConfigHolder {\n\n        private final static ThreadPoolUtil INSTANCE = new ThreadPoolUtil();\n\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/config/AsyncTaskConfig.java",
    "content": "package com.simplefanc.voj.judger.config;\n\nimport org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.annotation.AsyncConfigurer;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\n\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ThreadPoolExecutor;\n\n/**\n * @Author: chenfan\n * @Date: 2021/11/6 23:36\n * @Description:\n */\n@Configuration\npublic class AsyncTaskConfig implements AsyncConfigurer {\n\n    @Override\n    public Executor getAsyncExecutor() {\n        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();\n        // 线程池维护线程的最少数量\n        taskExecutor.setCorePoolSize(10);\n        // 线程池维护线程的最大数量\n        taskExecutor.setMaxPoolSize(50);\n        // 缓存队列\n        taskExecutor.setQueueCapacity(200);\n        // 活跃时间\n        taskExecutor.setKeepAliveSeconds(10);\n        // 对拒绝task的处理策略\n        // (1) 默认的ThreadPoolExecutor.AbortPolicy 处理程序遭到拒绝将抛出运行时RejectedExecutionException;\n        // (2) ThreadPoolExecutor.CallerRunsPolicy 线程调用运行该任务的 execute\n        // 本身。此策略提供简单的反馈控制机制，能够减缓新任务的提交速度\n        // (3) ThreadPoolExecutor.DiscardPolicy 不能执行的任务将被删除;\n        // (4) ThreadPoolExecutor.DiscardOldestPolicy\n        // 如果执行程序尚未关闭，则位于工作队列头部的任务将被删除，然后重试执行程序（如果再次失败，则重复此过程）\n        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());\n        // 线程名前缀,方便排查问题\n        taskExecutor.setThreadNamePrefix(\"order-send-thread-\");\n        // 注意一定要初始化\n        taskExecutor.initialize();\n\n        return taskExecutor;\n\n    }\n\n    @Override\n    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {\n        return null;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/config/DruidConfiguration.java",
    "content": "package com.simplefanc.voj.judger.config;\n\nimport com.alibaba.druid.pool.DruidDataSource;\nimport lombok.Data;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/21 17:57\n * @Description:\n */\n@Configuration\n@RefreshScope\n@Data\npublic class DruidConfiguration {\n\n    @Value(\"${voj.db.username}\")\n    private String username;\n\n    @Value(\"${voj.db.password}\")\n    private String password;\n\n    @Value(\"${voj.db.public-host}\")\n    private String host;\n\n    @Value(\"${voj.db.port}\")\n    private Integer port;\n\n    @Value(\"${voj.db.name}\")\n    private String name;\n\n    @Value(\"${spring.datasource.driver-class-name}\")\n    private String driverClassName;\n\n    @Value(\"${spring.datasource.initial-size}\")\n    private Integer initialSize;\n\n    @Value(\"${spring.datasource.min-idle}\")\n    private Integer minIdle;\n\n    @Value(\"${spring.datasource.maxActive}\")\n    private Integer maxActive;\n\n    @Value(\"${spring.datasource.maxWait}\")\n    private Integer maxWait;\n\n    @Bean(name = \"datasource\")\n    @RefreshScope\n    public DruidDataSource dataSource() {\n        DruidDataSource datasource = new DruidDataSource();\n        String url = \"jdbc:mysql://\" + host + \":\" + port + \"/\" + name\n                + \"?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true\";\n        datasource.setUrl(url);\n        datasource.setUsername(username);\n        datasource.setPassword(password);\n        datasource.setDriverClassName(driverClassName);\n        datasource.setMaxActive(maxActive);\n        datasource.setInitialSize(initialSize);\n        datasource.setMinIdle(minIdle);\n        datasource.setMaxWait(maxWait);\n        return datasource;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/config/MyMetaObjectConfig.java",
    "content": "package com.simplefanc.voj.judger.config;\n\nimport com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;\nimport org.apache.ibatis.reflection.MetaObject;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Date;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/4 14:14\n * @Description: 处理mybatis-plus自动填充时间\n */\n@Component\npublic class MyMetaObjectConfig implements MetaObjectHandler {\n\n    @Override\n    public void insertFill(MetaObject metaObject) {\n        this.setFieldValByName(\"gmtCreate\", new Date(), metaObject);\n        this.setFieldValByName(\"gmtModified\", new Date(), metaObject);\n    }\n\n    @Override\n    public void updateFill(MetaObject metaObject) {\n        this.setFieldValByName(\"gmtModified\", new Date(), metaObject);\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/config/MybatisPlusConfig.java",
    "content": "package com.simplefanc.voj.judger.config;\n\nimport com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;\nimport org.mybatis.spring.annotation.MapperScan;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.transaction.annotation.EnableTransactionManagement;\n\n/**\n * @Author: chenfan\n * @Date: 2021/7/19 21:04\n * @Description:\n */\n@Configuration\n@EnableTransactionManagement\n@MapperScan(\"com.simplefanc.voj.judger.mapper\")\npublic class MybatisPlusConfig {\n\n    /**\n     * 注册乐观锁插件\n     *\n     * @return\n     */\n    @Bean\n    public OptimisticLockerInterceptor optimisticLockerInterceptor() {\n        return new OptimisticLockerInterceptor();\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/config/NacosConfig.java",
    "content": "package com.simplefanc.voj.judger.config;\n\nimport com.alibaba.cloud.nacos.NacosDiscoveryProperties;\nimport com.simplefanc.voj.common.utils.IpUtil;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Primary;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2021/2/6 00:46\n * @Description:\n */\n@Configuration\npublic class NacosConfig {\n\n    private static final int CPU_NUM = Runtime.getRuntime().availableProcessors();\n\n    @Value(\"${voj-judge-server.max-task-num}\")\n    private Integer maxTaskNum;\n\n    @Value(\"${voj-judge-server.remote-judge.max-task-num}\")\n    private Integer maxRemoteTaskNum;\n\n    @Value(\"${voj-judge-server.remote-judge.open}\")\n    private Boolean openRemoteJudge;\n\n    @Value(\"${voj-judge-server.ip}\")\n    private String ip;\n\n    @Value(\"${voj-judge-server.port}\")\n    private Integer port;\n\n    @Value(\"${voj-judge-server.name}\")\n    private String judgeServerName;\n\n    /**\n     * 用于改变程序自动获取的本机ip\n     */\n    @Bean\n    @Primary\n    public NacosDiscoveryProperties nacosProperties() {\n        NacosDiscoveryProperties nacosDiscoveryProperties = new NacosDiscoveryProperties();\n        // 此处只改了ip，其他参数可以根据自己的需求改变\n        nacosDiscoveryProperties.setIp(IpUtil.getServiceIp());\n        HashMap<String, String> meta = new HashMap<>();\n        int max = CPU_NUM + 1;\n        if (maxTaskNum != -1) {\n            max = maxTaskNum;\n        }\n        meta.put(\"maxTaskNum\", String.valueOf(max));\n        if (openRemoteJudge) {\n            max = CPU_NUM * 2 + 1;\n            if (maxRemoteTaskNum != -1) {\n                max = maxRemoteTaskNum;\n            }\n            meta.put(\"maxRemoteTaskNum\", String.valueOf(max));\n        }\n        meta.put(\"judgeName\", judgeServerName);\n        nacosDiscoveryProperties.setMetadata(meta);\n        if (!\"-1\".equals(ip)) {\n            nacosDiscoveryProperties.setIp(ip);\n        }\n        nacosDiscoveryProperties.setPort(port);\n\n        return nacosDiscoveryProperties;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/config/StartupRunner.java",
    "content": "package com.simplefanc.voj.judger.config;\n\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeServer;\nimport com.simplefanc.voj.common.utils.IpUtil;\nimport com.simplefanc.voj.judger.dao.JudgeServerEntityService;\nimport com.simplefanc.voj.judger.service.SystemConfigService;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.stereotype.Component;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2021/2/19 22:11\n * @Description:项目启动加载类，启动完毕将该判题机加入到redis里面\n */\n@Component\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class StartupRunner implements CommandLineRunner {\n\n    private static final int CPU_NUM = Runtime.getRuntime().availableProcessors();\n\n    @Value(\"${voj-judge-server.max-task-num}\")\n    private Integer maxTaskNum;\n\n    @Value(\"${voj-judge-server.remote-judge.max-task-num}\")\n    private Integer maxRemoteTaskNum;\n\n    @Value(\"${voj-judge-server.remote-judge.open}\")\n    private Boolean openRemoteJudge;\n\n    @Value(\"${voj-judge-server.name}\")\n    private String judgeServerName;\n\n    @Value(\"${voj-judge-server.ip}\")\n    private String ip;\n\n    @Value(\"${voj-judge-server.port}\")\n    private Integer port;\n\n    private final JudgeServerEntityService judgeServerEntityService;\n\n    private final SystemConfigService systemConfigService;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void run(String... args) {\n\n        log.info(\"IP of the current judge server:\" + ip);\n        log.info(\"Port of the current judge server:\" + port);\n\n        if (maxTaskNum == -1) {\n            maxTaskNum = CPU_NUM + 1;\n        }\n        if (\"-1\".equals(ip)) {\n            ip = IpUtil.getLocalIpv4Address();\n        }\n        UpdateWrapper<JudgeServer> judgeServerQueryWrapper = new UpdateWrapper<>();\n        judgeServerQueryWrapper.eq(\"ip\", ip).eq(\"port\", port);\n        judgeServerEntityService.remove(judgeServerQueryWrapper);\n\n        final JudgeServer entity = new JudgeServer().setCpuCore(CPU_NUM).setIp(ip).setPort(port).setUrl(ip + \":\" + port).setName(judgeServerName)\n                .setMaxTaskNumber(maxTaskNum).setIsRemote(false);\n        boolean isOk1 = judgeServerEntityService.save(entity);\n        boolean isOk2 = true;\n        if (openRemoteJudge) {\n            if (maxRemoteTaskNum == -1) {\n                maxRemoteTaskNum = CPU_NUM * 2 + 1;\n            }\n            entity.setMaxTaskNumber(maxRemoteTaskNum).setIsRemote(true);\n            isOk2 = judgeServerEntityService.save(entity);\n        }\n\n        if (!isOk1 || !isOk2) {\n            log.error(\"初始化判题机信息到数据库失败，请重新启动试试！\");\n        } else {\n            HashMap<String, Object> judgeServerInfo = systemConfigService.getJudgeServerInfo();\n            log.info(\"VOJ-JudgeServer had successfully started! The judge config and sandbox config Info: {}\",\n                    judgeServerInfo);\n        }\n\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/controller/JudgeController.java",
    "content": "package com.simplefanc.voj.judger.controller;\n\nimport com.simplefanc.voj.common.pojo.dto.CompileDTO;\nimport com.simplefanc.voj.common.pojo.dto.JudgeDTO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.result.CommonResult;\nimport com.simplefanc.voj.common.result.ResultStatus;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.service.JudgeService;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * @Author: chenfan\n * @Date: 2021/10/29 22:22\n * @Description: 处理代码提交\n */\n@RestController\n@RefreshScope\n@RequiredArgsConstructor\npublic class JudgeController {\n\n    private final JudgeService judgeService;\n\n    @Value(\"${voj.judge.token}\")\n    private String judgeToken;\n\n    @Value(\"${voj-judge-server.remote-judge.open}\")\n    private Boolean openRemoteJudge;\n\n    @PostMapping(value = \"/judge\")\n    public CommonResult submitProblemJudge(@RequestBody JudgeDTO toJudge) {\n        if (!toJudge.getToken().equals(judgeToken)) {\n            return CommonResult.errorResponse(\"对不起！您使用的判题服务调用凭证不正确！访问受限！\", ResultStatus.ACCESS_DENIED);\n        }\n\n        Judge judge = toJudge.getJudge();\n\n        if (judge == null || judge.getSubmitId() == null || judge.getUid() == null || judge.getPid() == null) {\n            return CommonResult.errorResponse(\"调用参数错误！请检查您的调用参数！\");\n        }\n\n        judgeService.localJudge(judge);\n\n        return CommonResult.successResponse(\"判题机评测完成！\");\n    }\n\n    @PostMapping(value = \"/compile-spj\")\n    public CommonResult compileSpj(@RequestBody CompileDTO compileDTO) {\n        if (!compileDTO.getToken().equals(judgeToken)) {\n            return CommonResult.errorResponse(\"对不起！您使用的判题服务调用凭证不正确！访问受限！\", ResultStatus.ACCESS_DENIED);\n        }\n\n        try {\n            judgeService.compileSpj(compileDTO.getCode(), compileDTO.getPid(), compileDTO.getLanguage(),\n                    compileDTO.getExtraFiles());\n            return CommonResult.successResponse(null, \"编译成功！\");\n        } catch (SystemException systemException) {\n            return CommonResult.errorResponse(systemException.getStderr(), ResultStatus.SYSTEM_ERROR);\n        }\n    }\n\n    @PostMapping(value = \"/compile-interactive\")\n    public CommonResult compileInteractive(@RequestBody CompileDTO compileDTO) {\n        if (!compileDTO.getToken().equals(judgeToken)) {\n            return CommonResult.errorResponse(\"对不起！您使用的判题服务调用凭证不正确！访问受限！\", ResultStatus.ACCESS_DENIED);\n        }\n\n        try {\n            judgeService.compileInteractive(compileDTO.getCode(), compileDTO.getPid(), compileDTO.getLanguage(),\n                    compileDTO.getExtraFiles());\n            return CommonResult.successResponse(null, \"编译成功！\");\n        } catch (SystemException systemException) {\n            return CommonResult.errorResponse(systemException.getStderr(), ResultStatus.SYSTEM_ERROR);\n        }\n    }\n\n    @PostMapping(value = \"/remote-judge\")\n    public CommonResult remoteJudge(@RequestBody JudgeDTO toJudge) {\n        if (!openRemoteJudge) {\n            return CommonResult.errorResponse(\"对不起！该判题服务器未开启远程虚拟判题功能！\", ResultStatus.ACCESS_DENIED);\n        }\n\n        if (!toJudge.getToken().equals(judgeToken)) {\n            return CommonResult.errorResponse(\"对不起！您使用的判题服务调用凭证不正确！访问受限！\", ResultStatus.ACCESS_DENIED);\n        }\n\n        if (toJudge.getJudge() == null) {\n            return CommonResult.errorResponse(\"请求参数不能为空！\");\n        }\n\n        judgeService.remoteJudge(toJudge);\n\n        return CommonResult.successResponse(\"提交成功\");\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/controller/SystemConfigController.java",
    "content": "package com.simplefanc.voj.judger.controller;\n\nimport com.simplefanc.voj.common.result.CommonResult;\nimport com.simplefanc.voj.judger.dao.JudgeServerEntityService;\nimport com.simplefanc.voj.judger.service.SystemConfigService;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/3 20:12\n * @Description:\n */\n@RestController\n@RequiredArgsConstructor\npublic class SystemConfigController {\n\n    private final SystemConfigService systemConfigService;\n\n    @RequestMapping(\"/get-sys-config\")\n    public HashMap<String, Object> getSystemConfig() {\n        return systemConfigService.getSystemConfig();\n    }\n\n    @RequestMapping(\"/version\")\n    public CommonResult<HashMap<String, Object>> getVersion() {\n        return CommonResult.successResponse(systemConfigService.getJudgeServerInfo(), \"运行正常\");\n    }\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/ContestEntityService.java",
    "content": "package com.simplefanc.voj.judger.dao;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\npublic interface ContestEntityService extends IService<Contest> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/ContestRecordEntityService.java",
    "content": "package com.simplefanc.voj.judger.dao;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\npublic interface ContestRecordEntityService extends IService<ContestRecord> {\n\n    void updateContestRecord(Judge judge);\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/JudgeCaseEntityService.java",
    "content": "package com.simplefanc.voj.judger.dao;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\npublic interface JudgeCaseEntityService extends IService<JudgeCase> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/JudgeEntityService.java",
    "content": "package com.simplefanc.voj.judger.dao;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\npublic interface JudgeEntityService extends IService<Judge> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/JudgeServerEntityService.java",
    "content": "package com.simplefanc.voj.judger.dao;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeServer;\n\nimport java.util.HashMap;\n\npublic interface JudgeServerEntityService extends IService<JudgeServer> {\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/ProblemCaseEntityService.java",
    "content": "package com.simplefanc.voj.judger.dao;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/14 19:58\n * @Description:\n */\npublic interface ProblemCaseEntityService extends IService<ProblemCase> {\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/ProblemEntityService.java",
    "content": "package com.simplefanc.voj.judger.dao;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n\npublic interface ProblemEntityService extends IService<Problem> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/RemoteJudgeAccountEntityService.java",
    "content": "package com.simplefanc.voj.judger.dao;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\n\npublic interface RemoteJudgeAccountEntityService extends IService<RemoteJudgeAccount> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/UserAcproblemEntityService.java",
    "content": "package com.simplefanc.voj.judger.dao;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\n\n/**\n * <p>\n * 服务类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\npublic interface UserAcproblemEntityService extends IService<UserAcproblem> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/impl/ContestEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.dao.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport com.simplefanc.voj.judger.dao.ContestEntityService;\nimport com.simplefanc.voj.judger.mapper.ContestMapper;\nimport org.springframework.stereotype.Service;\n\n;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Service\npublic class ContestEntityServiceImpl extends ServiceImpl<ContestMapper, Contest> implements ContestEntityService {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/impl/ContestRecordEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.dao.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.common.constants.ContestEnum;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.judger.dao.ContestRecordEntityService;\nimport com.simplefanc.voj.judger.mapper.ContestRecordMapper;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Service\n@RequiredArgsConstructor\npublic class ContestRecordEntityServiceImpl extends ServiceImpl<ContestRecordMapper, ContestRecord>\n        implements ContestRecordEntityService {\n\n    private static final List<Integer> PENALTY_STATUS = Arrays.asList(JudgeStatus.STATUS_PRESENTATION_ERROR.getStatus(),\n            JudgeStatus.STATUS_WRONG_ANSWER.getStatus(), JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED.getStatus(),\n            JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus(), JudgeStatus.STATUS_RUNTIME_ERROR.getStatus());\n\n    private final ContestRecordMapper contestRecordMapper;\n\n    @Override\n    public void updateContestRecord(Judge judge) {\n        UpdateWrapper<ContestRecord> updateWrapper = new UpdateWrapper<>();\n        if (judge.getStatus().intValue() == JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n            // 如果是AC\n            updateWrapper.set(\"status\", ContestEnum.RECORD_AC.getCode());\n        } else if (judge.getStatus().intValue() == JudgeStatus.STATUS_PARTIAL_ACCEPTED.getStatus()) {\n            // 部分通过\n            updateWrapper.set(\"status\", ContestEnum.RECORD_NOT_AC_PENALTY.getCode());\n        } else if (PENALTY_STATUS.contains(judge.getStatus())) {\n            // 需要被罚时的状态\n            updateWrapper.set(\"status\", ContestEnum.RECORD_NOT_AC_PENALTY.getCode());\n        } else {\n            updateWrapper.set(\"status\", ContestEnum.RECORD_NOT_AC_NOT_PENALTY.getCode());\n        }\n\n        if (judge.getScore() != null) {\n            updateWrapper.set(\"score\", judge.getScore());\n        }\n\n        updateWrapper.set(\"use_time\", judge.getTime());\n        // submit_id一定只有一个\n        updateWrapper.eq(\"submit_id\", judge.getSubmitId())\n                .eq(\"cid\", judge.getCid())\n                .eq(\"uid\", judge.getUid());\n        boolean result = contestRecordMapper.update(null, updateWrapper) > 0;\n        if (!result) {\n            tryAgainUpdate(updateWrapper);\n        }\n    }\n\n    public void tryAgainUpdate(UpdateWrapper<ContestRecord> updateWrapper) {\n        boolean retryable;\n        int attemptNumber = 0;\n        do {\n            boolean result = contestRecordMapper.update(null, updateWrapper) > 0;\n            if (result) {\n                break;\n            } else {\n                attemptNumber++;\n                retryable = attemptNumber < 8;\n                if (attemptNumber == 8) {\n                    log.error(\"更新contest_record表超过最大重试次数\");\n                    break;\n                }\n                try {\n                    Thread.sleep(300);\n                } catch (InterruptedException e) {\n                    log.error(e.getMessage());\n                }\n            }\n        }\n        while (retryable);\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/impl/JudgeCaseEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.dao.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport com.simplefanc.voj.judger.dao.JudgeCaseEntityService;\nimport com.simplefanc.voj.judger.mapper.JudgeCaseMapper;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Service\npublic class JudgeCaseEntityServiceImpl extends ServiceImpl<JudgeCaseMapper, JudgeCase>\n        implements JudgeCaseEntityService {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/impl/JudgeEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.dao.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.judger.dao.JudgeEntityService;\nimport com.simplefanc.voj.judger.mapper.JudgeMapper;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Service\npublic class JudgeEntityServiceImpl extends ServiceImpl<JudgeMapper, Judge> implements JudgeEntityService {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/impl/JudgeServerEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.dao.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeServer;\nimport com.simplefanc.voj.judger.dao.JudgeServerEntityService;\nimport com.simplefanc.voj.judger.mapper.JudgeServerMapper;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/15 11:27\n * @Description:\n */\n@Service\npublic class JudgeServerEntityServiceImpl extends ServiceImpl<JudgeServerMapper, JudgeServer>\n        implements JudgeServerEntityService {\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/impl/ProblemCaseEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.dao.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport com.simplefanc.voj.judger.dao.ProblemCaseEntityService;\nimport com.simplefanc.voj.judger.mapper.ProblemCaseMapper;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/14 19:59\n * @Description:\n */\n@Service\npublic class ProblemCaseEntityServiceImpl extends ServiceImpl<ProblemCaseMapper, ProblemCase>\n        implements ProblemCaseEntityService {\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/impl/ProblemEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.dao.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.judger.dao.ProblemEntityService;\nimport com.simplefanc.voj.judger.mapper.ProblemMapper;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Service\npublic class ProblemEntityServiceImpl extends ServiceImpl<ProblemMapper, Problem> implements ProblemEntityService {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/impl/RemoteJudgeAccountEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.dao.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\nimport com.simplefanc.voj.judger.dao.RemoteJudgeAccountEntityService;\nimport com.simplefanc.voj.judger.mapper.RemoteJudgeAccountMapper;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2021/5/18 17:46\n * @Description:\n */\n@Service\npublic class RemoteJudgeAccountEntityServiceImpl extends ServiceImpl<RemoteJudgeAccountMapper, RemoteJudgeAccount>\n        implements RemoteJudgeAccountEntityService {\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/dao/impl/UserAcproblemEntityServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.dao.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport com.simplefanc.voj.judger.dao.UserAcproblemEntityService;\nimport com.simplefanc.voj.judger.mapper.UserAcproblemMapper;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Service\npublic class UserAcproblemEntityServiceImpl extends ServiceImpl<UserAcproblemMapper, UserAcproblem>\n        implements UserAcproblemEntityService {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/Compiler.java",
    "content": "package com.simplefanc.voj.judger.judge.local;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.common.constants.CompileConfig;\nimport com.simplefanc.voj.judger.common.constants.JudgeDir;\nimport com.simplefanc.voj.judger.common.exception.CompileException;\nimport com.simplefanc.voj.judger.common.exception.SubmitException;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.common.utils.JudgeUtil;\n\nimport java.io.File;\nimport java.text.MessageFormat;\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/16 12:14\n * @Description: 判题流程解耦重构2.0，该类只负责编译\n */\npublic class Compiler {\n\n    public static String compile(CompileConfig compileConfig,\n                                 String code,\n                                 String language,\n                                 HashMap<String, String> extraFiles) throws SystemException, CompileException, SubmitException {\n        if (compileConfig == null) {\n            throw new RuntimeException(\"Unsupported language \" + language);\n        }\n\n        // 调用安全沙箱进行编译\n        JSONArray result = SandboxRun.compile(\n                compileConfig.getMaxCpuTime(),\n                compileConfig.getMaxRealTime(),\n                compileConfig.getMaxMemory(),\n                256 * 1024 * 1024L,\n                compileConfig.getSrcName(),\n                compileConfig.getExeName(),\n                parseCompileCommand(compileConfig),\n                compileConfig.getEnvs(),\n                code,\n                extraFiles,\n                true,\n                false,\n                null);\n        JSONObject compileResult = (JSONObject) result.get(0);\n        if (compileResult.getInt(\"status\").intValue() != JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n            throw new CompileException(\"Compile Error.\", ((JSONObject) compileResult.get(\"files\")).getStr(\"stdout\"),\n                    ((JSONObject) compileResult.get(\"files\")).getStr(\"stderr\"));\n        }\n\n        String fileId = ((JSONObject) compileResult.get(\"fileIds\")).getStr(compileConfig.getExeName());\n        if (StrUtil.isEmpty(fileId)) {\n            throw new SubmitException(\"Executable file not found.\",\n                    ((JSONObject) compileResult.get(\"files\")).getStr(\"stdout\"),\n                    ((JSONObject) compileResult.get(\"files\")).getStr(\"stderr\"));\n        }\n        return fileId;\n    }\n\n    public static Boolean compileSpj(String code, Long pid, String language, HashMap<String, String> extraFiles)\n            throws SystemException {\n        CompileConfig spjCompiler = CompileConfig.getCompilerByLanguage(\"SPJ-\" + language);\n        if (spjCompiler == null) {\n            throw new RuntimeException(\"Unsupported SPJ language:\" + language);\n        }\n\n        boolean copyOutExe = true;\n        // 题目id为空，则不进行本地存储，可能为新建题目时测试特判程序是否正常的判断而已\n        if (pid == null) {\n            copyOutExe = false;\n        }\n\n        // 调用安全沙箱对特别判题程序进行编译\n        JSONArray res = SandboxRun.compile(\n                spjCompiler.getMaxCpuTime(),\n                spjCompiler.getMaxRealTime(),\n                spjCompiler.getMaxMemory(),\n                256 * 1024 * 1024L,\n                spjCompiler.getSrcName(),\n                spjCompiler.getExeName(),\n                parseCompileCommand(spjCompiler),\n                spjCompiler.getEnvs(),\n                code,\n                extraFiles,\n                false,\n                copyOutExe,\n                JudgeDir.SPJ_WORKPLACE_DIR + File.separator + pid);\n        JSONObject compileResult = (JSONObject) res.get(0);\n        if (compileResult.getInt(\"status\").intValue() != JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n            throw new SystemException(\"Special Judge Code Compile Error.\",\n                    ((JSONObject) compileResult.get(\"files\")).getStr(\"stdout\"),\n                    ((JSONObject) compileResult.get(\"files\")).getStr(\"stderr\"));\n        }\n        return true;\n    }\n\n    public static Boolean compileInteractive(String code, Long pid, String language, HashMap<String, String> extraFiles)\n            throws SystemException {\n        CompileConfig interactiveCompiler = CompileConfig.getCompilerByLanguage(\"INTERACTIVE-\" + language);\n        if (interactiveCompiler == null) {\n            throw new RuntimeException(\"Unsupported interactive language:\" + language);\n        }\n\n        // 题目id为空，则不进行本地存储，可能为新建题目时测试特判程序是否正常的判断而已\n        boolean copyOutExe = pid != null;\n\n        // 调用安全沙箱对特别判题程序进行编译\n        JSONArray res = SandboxRun.compile(\n                interactiveCompiler.getMaxCpuTime(),\n                interactiveCompiler.getMaxRealTime(),\n                interactiveCompiler.getMaxMemory(),\n                256 * 1024 * 1024L,\n                interactiveCompiler.getSrcName(),\n                interactiveCompiler.getExeName(),\n                parseCompileCommand(interactiveCompiler),\n                interactiveCompiler.getEnvs(),\n                code,\n                extraFiles,\n                false,\n                copyOutExe,\n                JudgeDir.INTERACTIVE_WORKPLACE_DIR + File.separator + pid);\n        JSONObject compileResult = (JSONObject) res.get(0);\n        if (compileResult.getInt(\"status\").intValue() != JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n            throw new SystemException(\"Interactive Judge Code Compile Error.\",\n                    ((JSONObject) compileResult.get(\"files\")).getStr(\"stdout\"),\n                    ((JSONObject) compileResult.get(\"files\")).getStr(\"stderr\"));\n        }\n        return true;\n    }\n\n    private static List<String> parseCompileCommand(CompileConfig compileConfig) {\n        String command = MessageFormat.format(compileConfig.getCommand(),\n                JudgeDir.TMPFS_DIR, compileConfig.getSrcName(), compileConfig.getExeName());\n        return JudgeUtil.translateCommandline(command);\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/JudgeContext.java",
    "content": "package com.simplefanc.voj.judger.judge.local;\n\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.judger.common.constants.JudgeLanguage;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeResult;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/12 15:49\n * @Description: 判题上下文\n */\n@Component\n@RequiredArgsConstructor\npublic class JudgeContext {\n\n    private final JudgeProcess judgeProcess;\n\n    public void judge(Judge judge, Problem problem) {\n        // c和c++为一倍时间和空间，其它语言为2倍时间和空间\n        if (!JudgeLanguage.CPP.getLanguage().equals(judge.getLanguage()) &&\n                !JudgeLanguage.C.getLanguage().equals(judge.getLanguage()) &&\n                !JudgeLanguage.CPPWithO2.getLanguage().equals(judge.getLanguage()) &&\n                !JudgeLanguage.CWithO2.getLanguage().equals(judge.getLanguage())) {\n            problem.setTimeLimit(problem.getTimeLimit() * 2);\n            problem.setMemoryLimit(problem.getMemoryLimit() * 2);\n        }\n\n        JudgeResult judgeResult = judgeProcess.execute(problem, judge);\n        wrapJudgeResult(judge, judgeResult, problem);\n    }\n\n    private void wrapJudgeResult(Judge judge, JudgeResult judgeResult, Problem problem) {\n        // 设置最终结果状态码\n        judge.setStatus(judgeResult.getStatus());\n        judge.setErrorMessage(judgeResult.getErrMsg());\n        // 设置最大时间和最大空间不超过题目限制时间和空间\n        judge.setMemory(Math.min(judgeResult.getMemory(), problem.getMemoryLimit() * 1024));\n        judge.setTime(Math.min(judgeResult.getTime(), problem.getTimeLimit()));\n        judge.setScore(judgeResult.getScore());\n        judge.setOiRankScore(judgeResult.getOiRankScore());\n    }\n\n    public Boolean compileSpj(String code, Long pid, String spjLanguage, HashMap<String, String> extraFiles)\n            throws SystemException {\n        return Compiler.compileSpj(code, pid, spjLanguage, extraFiles);\n    }\n\n    public Boolean compileInteractive(String code, Long pid, String interactiveLanguage,\n                                      HashMap<String, String> extraFiles) throws SystemException {\n        return Compiler.compileInteractive(code, pid, interactiveLanguage, extraFiles);\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/JudgeProcess.java",
    "content": "package com.simplefanc.voj.judger.judge.local;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileReader;\nimport cn.hutool.core.io.file.FileWriter;\nimport cn.hutool.core.util.StrUtil;\nimport com.simplefanc.voj.common.constants.JudgeMode;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.judger.common.constants.CompileConfig;\nimport com.simplefanc.voj.judger.common.constants.JudgeDir;\nimport com.simplefanc.voj.judger.common.exception.CompileException;\nimport com.simplefanc.voj.judger.common.exception.SubmitException;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.common.utils.JudgeUtil;\nimport com.simplefanc.voj.judger.dao.JudgeCaseEntityService;\nimport com.simplefanc.voj.judger.dao.JudgeEntityService;\nimport com.simplefanc.voj.judger.judge.local.pojo.CaseResult;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeResult;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\nimport java.io.File;\nimport java.util.*;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/12 15:49\n * @Description: 执行判题流程\n */\n@Slf4j(topic = \"voj\")\n@Component\n@RequiredArgsConstructor\npublic class JudgeProcess {\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final JudgeCaseEntityService judgeCaseEntityService;\n\n    private final JudgeRun judgeRun;\n\n    @Value(\"${voj-judge-server.name}\")\n    private String judgeServerName;\n\n    public JudgeResult execute(Problem problem, Judge judge) {\n        JudgeResult result = new JudgeResult();\n        // 编译好的临时代码文件id\n        String userFileId = null;\n        String userFileSrc = null;\n        // 标志该判题过程进入编译阶段\n        judge.setJudger(judgeServerName);\n        judge.setStatus(JudgeStatus.STATUS_COMPILING.getStatus());\n        judgeEntityService.updateById(judge);\n\n        // 对用户源代码进行编译 获取tmpfs中的fileId\n        CompileConfig compileConfig = CompileConfig.getCompilerByLanguage(judge.getLanguage());\n        try {\n            // 有的语言可能不支持编译\n            if (compileConfig != null) {\n                userFileId = Compiler.compile(compileConfig, judge.getCode(), judge.getLanguage(),\n                        JudgeUtil.getProblemExtraFileMap(problem, \"user\"));\n            } else {\n                // 目前只有js、php不支持编译，需要提供源代码文件的绝对路径\n                userFileSrc = JudgeDir.RUN_WORKPLACE_DIR + File.separator + problem.getId() + File.separator\n                        + getUserFileName(judge.getLanguage());\n                FileWriter fileWriter = new FileWriter(userFileSrc);\n                fileWriter.write(judge.getCode());\n            }\n\n            // 检查是否为spj或者interactive，同时是否有对应编译完成的文件，若不存在，就先编译生成该文件，同时也要检查版本\n            boolean isOk = checkOrCompileExtraProgram(problem);\n            if (!isOk) {\n                handleJudgeError(result, JudgeStatus.STATUS_SYSTEM_ERROR, \"The special judge or interactive program code does not exist.\");\n                return result;\n            }\n\n            // 更新状态为评测数据中\n            judge.setStatus(JudgeStatus.STATUS_JUDGING.getStatus());\n            judgeEntityService.updateById(judge);\n            // 开始测试每个测试点\n            List<CaseResult> allCaseResultList = judgeRun.judgeAllCase(judge, problem, userFileId, userFileSrc, false);\n\n            // 对全部测试点结果进行评判，获取最终评判结果\n            return getJudgeResult(allCaseResultList, problem, judge);\n        } catch (SystemException systemException) {\n            handleJudgeError(result, JudgeStatus.STATUS_SYSTEM_ERROR, \"Oops, something has gone wrong with the judgeServer. Please report this to administrator.\");\n            log.error(\"题号为：\" + problem.getId() + \"的题目，提交id为\" + judge.getSubmitId() + \"在评测过程中发生SystemError异常------------------->\", systemException);\n        } catch (SubmitException submitException) {\n            handleJudgeError(result, JudgeStatus.STATUS_SUBMITTED_FAILED, mergeNonEmptyStrings(submitException.getMessage(), submitException.getStdout(), submitException.getStderr()));\n            log.error(\"题号为：\" + problem.getId() + \"的题目，提交id为\" + judge.getSubmitId() + \"在评测过程中发生SubmitError异常-------------------->\", submitException);\n        } catch (CompileException compileException) {\n            handleJudgeError(result, JudgeStatus.STATUS_COMPILE_ERROR, mergeNonEmptyStrings(compileException.getStdout(), compileException.getStderr()));\n        } catch (Exception e) {\n            handleJudgeError(result, JudgeStatus.STATUS_SYSTEM_ERROR, \"Oops, something has gone wrong with the judgeServer. Please report this to administrator.\");\n            log.error(\"题号为：\" + problem.getId() + \"的题目，提交id为\" + judge.getSubmitId() + \"在评测过程中发生Exception异常-------------------->\", e);\n        } finally {\n            // 删除tmpfs内存中的用户代码可执行文件\n            if (StrUtil.isNotEmpty(userFileId)) {\n                SandboxRun.delFile(userFileId);\n            }\n        }\n        return result;\n    }\n\n\n    private Boolean checkOrCompileExtraProgram(Problem problem) throws CompileException, SystemException {\n        JudgeMode judgeMode = JudgeMode.getJudgeMode(problem.getJudgeMode());\n        String currentVersion = problem.getCaseVersion();\n        Boolean isOk;\n        switch (Objects.requireNonNull(judgeMode)) {\n            case DEFAULT:\n                return true;\n            case SPJ:\n                isOk = isCompileSpjOk(problem, currentVersion);\n                if (isOk != null) {\n                    return isOk;\n                }\n                break;\n            case INTERACTIVE:\n                isOk = isCompileInteractive(problem, currentVersion);\n                if (isOk != null) {\n                    return isOk;\n                }\n                break;\n            default:\n                throw new RuntimeException(\"The problem mode is error:\" + judgeMode);\n        }\n\n        return true;\n    }\n\n    private void handleJudgeError(JudgeResult result, JudgeStatus status, String errMsg) {\n        result.setStatus(status.getStatus());\n        result.setErrMsg(errMsg);\n        result.setTime(0);\n        result.setMemory(0);\n    }\n\n    private Boolean isCompileInteractive(Problem problem, String currentVersion) throws SystemException {\n        CompileConfig compiler = CompileConfig.getCompilerByLanguage(\"INTERACTIVE-\" + problem.getSpjLanguage());\n        String programFilePath = JudgeDir.INTERACTIVE_WORKPLACE_DIR + File.separator + problem.getId() + File.separator + compiler.getExeName();\n        String programVersionPath = JudgeDir.INTERACTIVE_WORKPLACE_DIR + File.separator + problem.getId() + File.separator + \"version\";\n\n        // 如果不存在该已经编译好的程序，则需要再次进行编译 版本变动也需要重新编译\n        if (!FileUtil.exist(programFilePath) || !FileUtil.exist(programVersionPath)) {\n            boolean isCompileInteractive = Compiler.compileInteractive(problem.getSpjCode(), problem.getId(),\n                    problem.getSpjLanguage(), JudgeUtil.getProblemExtraFileMap(problem, \"judge\"));\n            FileWriter fileWriter = new FileWriter(programVersionPath);\n            fileWriter.write(currentVersion);\n            return isCompileInteractive;\n        }\n\n        FileReader interactiveVersionFileReader = new FileReader(programVersionPath);\n        String recordInteractiveVersion = interactiveVersionFileReader.readString();\n\n        // 版本变动也需要重新编译\n        if (!currentVersion.equals(recordInteractiveVersion)) {\n            boolean isCompileInteractive = Compiler.compileSpj(problem.getSpjCode(), problem.getId(),\n                    problem.getSpjLanguage(), JudgeUtil.getProblemExtraFileMap(problem, \"judge\"));\n\n            FileWriter fileWriter = new FileWriter(programVersionPath);\n            fileWriter.write(currentVersion);\n\n            return isCompileInteractive;\n        }\n        return null;\n    }\n\n    private Boolean isCompileSpjOk(Problem problem, String currentVersion) throws SystemException {\n        CompileConfig compiler = CompileConfig.getCompilerByLanguage(\"SPJ-\" + problem.getSpjLanguage());\n        String programFilePath = JudgeDir.SPJ_WORKPLACE_DIR + File.separator + problem.getId() + File.separator\n                + compiler.getExeName();\n        String programVersionPath = JudgeDir.SPJ_WORKPLACE_DIR + File.separator + problem.getId() + File.separator\n                + \"version\";\n\n        // 如果不存在该已经编译好的程序，则需要再次进行编译\n        if (!FileUtil.exist(programFilePath) || !FileUtil.exist(programVersionPath)) {\n            boolean isCompileSpjOk = Compiler.compileSpj(problem.getSpjCode(), problem.getId(),\n                    problem.getSpjLanguage(), JudgeUtil.getProblemExtraFileMap(problem, \"judge\"));\n\n            FileWriter fileWriter = new FileWriter(programVersionPath);\n            fileWriter.write(currentVersion);\n            return isCompileSpjOk;\n        }\n\n        FileReader spjVersionReader = new FileReader(programVersionPath);\n        String recordSpjVersion = spjVersionReader.readString();\n\n        // 版本变动也需要重新编译\n        if (!currentVersion.equals(recordSpjVersion)) {\n            boolean isCompileSpjOk = Compiler.compileSpj(problem.getSpjCode(), problem.getId(),\n                    problem.getSpjLanguage(), JudgeUtil.getProblemExtraFileMap(problem, \"judge\"));\n            FileWriter fileWriter = new FileWriter(programVersionPath);\n            fileWriter.write(currentVersion);\n            return isCompileSpjOk;\n        }\n        return null;\n    }\n\n    /**\n     * 进行最终测试结果的判断（除编译失败外的评测状态码，时间，空间，OI题目的得分）\n     *\n     * @param testCaseResultList\n     * @param problem\n     * @param judge\n     * @return\n     */\n    private JudgeResult getJudgeResult(List<CaseResult> testCaseResultList, Problem problem, Judge judge) {\n        List<JudgeCase> allCaseResList = new ArrayList<>();\n        List<CaseResult> errorTestCaseList = new ArrayList<>();\n        // 记录所有测试点的结果\n        testCaseResultList.forEach(testCaseResult -> handleTestCaseResult(testCaseResult, problem, judge, allCaseResList, errorTestCaseList));\n        // 更新到数据库\n        judgeCaseEntityService.saveBatch(allCaseResList);\n        // 获取判题的time，memory，OI score\n        return computeJudgeResultInfo(allCaseResList, testCaseResultList, errorTestCaseList, problem.getDifficulty());\n    }\n\n    private void handleTestCaseResult(CaseResult result, Problem problem, Judge judge, List<JudgeCase> allCaseResList, List<CaseResult> errorTestCaseList) {\n        Integer time = result.getTime().intValue();\n        Integer memory = result.getMemory().intValue();\n        Integer status = result.getStatus();\n        Long caseId = result.getCaseId();\n        String inputFileName = result.getInputFileName();\n        String outputFileName = result.getOutputFileName();\n        String msg = result.getErrMsg();\n        int oiScore = result.getScore();\n\n        JudgeCase judgeCase = new JudgeCase();\n        judgeCase.setTime(time).setMemory(memory).setStatus(status).setInputData(inputFileName)\n                .setOutputData(outputFileName).setPid(problem.getId()).setUid(judge.getUid()).setCaseId(caseId)\n                .setSubmitId(judge.getSubmitId());\n        if (StrUtil.isNotEmpty(msg) && !status.equals(JudgeStatus.STATUS_COMPILE_ERROR.getStatus())) {\n            judgeCase.setUserOutput(msg);\n        }\n\n        int score = 0;\n        if (status.equals(JudgeStatus.STATUS_ACCEPTED.getStatus())) {\n            score = oiScore;\n        } else {\n            errorTestCaseList.add(result);\n            if (status.equals(JudgeStatus.STATUS_PARTIAL_ACCEPTED.getStatus())) {\n                // SPJ_PC\n                Double percentage = result.getPercentage();\n                if (percentage != null) {\n                    score = (int) Math.floor(percentage * oiScore);\n                }\n            }\n        }\n        judgeCase.setScore(score);\n\n        allCaseResList.add(judgeCase);\n    }\n\n    /**\n     * 获取判题的运行时间，运行空间，得分\n     */\n    private JudgeResult computeJudgeResultInfo(List<JudgeCase> allTestCaseResultList, List<CaseResult> testCaseResultList, List<CaseResult> errorTestCaseList, Integer problemDifficulty) {\n        JudgeResult result = new JudgeResult();\n        // 用时和内存占用保存为多个测试点中最长的\n        allTestCaseResultList.stream()\n                .max(Comparator.comparing(JudgeCase::getTime))\n                .ifPresent(t -> result.setTime(t.getTime()));\n\n        allTestCaseResultList.stream()\n                .max(Comparator.comparing(JudgeCase::getMemory))\n                .ifPresent(t -> result.setMemory(t.getMemory()));\n\n        int totalScore = allTestCaseResultList.stream()\n                .mapToInt(JudgeCase::getScore)\n                .sum();\n        result.setScore(totalScore);\n\n        boolean accepted = errorTestCaseList.isEmpty();\n        if (accepted) {\n            result.setStatus(JudgeStatus.STATUS_ACCEPTED.getStatus());\n            // 全对的直接用总分*0.1+2*题目难度\n            int oiRankScore = (int) Math.round(totalScore * 0.1 + 2 * problemDifficulty);\n            result.setOiRankScore(oiRankScore);\n        } else {\n            CaseResult caseResult = errorTestCaseList.get(0);\n            result.setStatus(caseResult.getStatus());\n            result.setErrMsg(caseResult.getErrMsg());\n            // 测试点总得分*0.1+2*题目难度*（测试点总得分/题目总分）\n            int fullScore = testCaseResultList.stream()\n                    .mapToInt(CaseResult::getScore)\n                    .sum();\n            int oiRankScore = (int) Math.round(totalScore * 0.1 + 2 * problemDifficulty * (totalScore * 1.0 / fullScore));\n            result.setOiRankScore(oiRankScore);\n        }\n        return result;\n    }\n\n    private String getUserFileName(String language) {\n        switch (language) {\n            case \"PHP\":\n                return \"main.php\";\n            case \"JavaScript Node\":\n            case \"JavaScript V8\":\n                return \"main.js\";\n            default:\n                return \"main\";\n        }\n    }\n\n    private String mergeNonEmptyStrings(String... strings) {\n        StringBuilder sb = new StringBuilder();\n        for (String str : strings) {\n            if (StrUtil.isNotEmpty(str)) {\n                sb.append(str, 0, Math.min(1024 * 1024, str.length())).append(\"\\n\");\n            }\n        }\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/JudgeRun.java",
    "content": "package com.simplefanc.voj.judger.judge.local;\n\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.common.constants.JudgeCaseMode;\nimport com.simplefanc.voj.common.constants.JudgeMode;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.judger.common.constants.JudgeDir;\nimport com.simplefanc.voj.judger.common.constants.RunConfig;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.common.utils.JudgeUtil;\nimport com.simplefanc.voj.judger.common.utils.ThreadPoolUtil;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeCaseDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeGlobalDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.CaseResult;\nimport com.simplefanc.voj.judger.judge.local.strategy.AbstractJudge;\nimport com.simplefanc.voj.judger.judge.local.strategy.DefaultJudge;\nimport com.simplefanc.voj.judger.judge.local.strategy.InteractiveJudge;\nimport com.simplefanc.voj.judger.judge.local.strategy.SpecialJudge;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.io.File;\nimport java.io.UnsupportedEncodingException;\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.concurrent.*;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/16 12:15\n * @Description: 运行判题任务\n */\n@Component\n@RequiredArgsConstructor\npublic class JudgeRun {\n\n    private final DefaultJudge defaultJudge;\n\n    private final SpecialJudge specialJudge;\n\n    private final InteractiveJudge interactiveJudge;\n\n    private final ProblemTestCaseUtils problemTestCaseUtils;\n\n    public List<CaseResult> judgeAllCase(Judge judge, Problem problem, String userFileId, String userFileSrc, Boolean getUserOutput)\n            throws SystemException, ExecutionException, InterruptedException, UnsupportedEncodingException {\n\n        JudgeGlobalDTO judgeGlobalDTO = getJudgeGlobalDTO(judge, problem, userFileId, userFileSrc, getUserOutput);\n\n        List<JudgeTask> judgeTasks = getJudgeTasks(judgeGlobalDTO);\n        if (JudgeCaseMode.ITERATE_UNTIL_WRONG.getMode().equals(problem.getJudgeCaseMode())) {\n            // 顺序评测测试点，遇到非AC就停止！\n            return iterateJudgeAllCase(judgeTasks);\n        } else {\n            return defaultJudgeAllCase(judgeTasks);\n        }\n    }\n\n    private List<CaseResult> iterateJudgeAllCase(List<JudgeTask> judgeTasks) throws ExecutionException, InterruptedException {\n        List<CaseResult> result = new LinkedList<>();\n        for (JudgeTask judgeTask : judgeTasks) {\n            // 提交到线程池进行执行\n            FutureTask<CaseResult> futureTask = new FutureTask<>(judgeTask);\n            ThreadPoolUtil.getInstance().getThreadPool().submit(futureTask);\n            final CaseResult judgeRes = futureTask.get();\n            result.add(judgeRes);\n            Integer status = judgeRes.getStatus();\n            if (!JudgeStatus.STATUS_ACCEPTED.getStatus().equals(status)) {\n                break;\n            }\n        }\n        return result;\n    }\n\n    private List<CaseResult> defaultJudgeAllCase(List<JudgeTask> judgeTasks) throws InterruptedException, ExecutionException {\n        ExecutorService threadPool = ThreadPoolUtil.getInstance().getThreadPool();\n        CompletableFuture[] futures = new CompletableFuture[judgeTasks.size()];\n        for (int i = 0; i < judgeTasks.size(); i++) {\n            final JudgeTask judgeTask = judgeTasks.get(i);\n            futures[i]  = CompletableFuture.supplyAsync(() -> {\n                try {\n                    // 普通方法\n                    return judgeTask.call();\n                } catch (SystemException e) {\n                    throw new RuntimeException(e);\n                }\n            }, threadPool);\n        }\n        // allOf() 方法会等到所有的 CompletableFuture 都运行完成之后再返回\n        CompletableFuture<Void> headerFuture = CompletableFuture.allOf(futures);\n        // 都运行完了之后再继续执行\n        headerFuture.join();\n        List<CaseResult> res = new ArrayList<>();\n        for (int i = 0; i < judgeTasks.size(); i++) {\n            res.add((CaseResult) futures[i].get());\n        }\n        return res;\n    }\n\n    private List<JudgeTask> getJudgeTasks(JudgeGlobalDTO judgeGlobalDTO) {\n        List<JudgeTask> judgeTasks = new ArrayList<>();\n        final JSONArray testcaseList = (JSONArray) judgeGlobalDTO.getTestCaseInfo().get(\"testCases\");\n        for (int index = 0; index < testcaseList.size(); index++) {\n            JSONObject testcase = (JSONObject) testcaseList.get(index);\n            final int testCaseNum = index + 1;\n            // 输入文件名\n            final String inputFileName = testcase.getStr(\"inputName\");\n            // 输出文件名\n            final String outputFileName = testcase.getStr(\"outputName\");\n            // 题目数据的输入文件的路径\n            final String testCaseInputPath = judgeGlobalDTO.getTestCasesDir() + File.separator + inputFileName;\n            // 题目数据的输出文件的路径\n            final String testCaseOutputPath = judgeGlobalDTO.getTestCasesDir() + File.separator + outputFileName;\n            // 数据库表的测试样例id\n            final Long caseId = testcase.getLong(\"caseId\", null);\n            // 该测试点的满分\n            final Integer score = testcase.getInt(\"score\", 0);\n\n            final Long maxOutputSize = Math.max(testcase.getLong(\"outputSize\", 0L) * 2, 16 * 1024 * 1024L);\n\n            JudgeCaseDTO judgeDTO = JudgeCaseDTO.builder()\n                    .testCaseNum(testCaseNum)\n                    .testCaseInputFileName(inputFileName)\n                    .testCaseInputPath(testCaseInputPath)\n                    .testCaseOutputFileName(outputFileName)\n                    .testCaseOutputPath(testCaseOutputPath)\n                    .problemCaseId(caseId)\n                    .score(score)\n                    .maxOutputSize(maxOutputSize)\n                    .build();\n            judgeTasks.add(new JudgeTask(judgeDTO, judgeGlobalDTO));\n        }\n        return judgeTasks;\n    }\n\n    private JudgeGlobalDTO getJudgeGlobalDTO(Judge judge, Problem problem, String userFileId, String userFileSrc, Boolean getUserOutput) throws SystemException, UnsupportedEncodingException {\n        Long submitId = judge.getSubmitId();\n        String judgeLanguage = judge.getLanguage();\n\n        // 默认给题目限制时间+200ms用来测评\n        Long testTime = (long) problem.getTimeLimit() + 200;\n\n        JudgeMode judgeMode = JudgeMode.getJudgeMode(problem.getJudgeMode());\n        if (judgeMode == null) {\n            throw new RuntimeException(\n                    \"The judge mode of problem \" + problem.getProblemId() + \" error:\" + problem.getJudgeMode());\n        }\n\n        // 从文件中加载测试数据json\n        JSONObject testCasesInfo = problemTestCaseUtils.loadTestCaseInfo(problem);\n        if (testCasesInfo == null) {\n            throw new SystemException(\"The evaluation data of the problem does not exist\", null, null);\n        }\n\n        // 测试数据文件所在文件夹\n        String testCasesDir = JudgeDir.TEST_CASE_DIR + File.separator + \"problem_\" + problem.getId();\n\n        // 用户输出的文件夹\n        String runDir = JudgeDir.RUN_WORKPLACE_DIR + File.separator + submitId;\n\n        RunConfig runConfig = RunConfig.getRunnerByLanguage(judgeLanguage);\n        RunConfig spjConfig = RunConfig.getRunnerByLanguage(\"SPJ-\" + problem.getSpjLanguage());\n        RunConfig interactiveConfig = RunConfig.getRunnerByLanguage(\"INTERACTIVE-\" + problem.getSpjLanguage());\n\n        return JudgeGlobalDTO.builder()\n                .problemId(problem.getId())\n                .judgeMode(judgeMode)\n                .userFileId(userFileId)\n                .userFileSrc(userFileSrc)\n                .runDir(runDir)\n                .testTime(testTime)\n                .maxMemory((long) problem.getMemoryLimit())\n                .maxTime((long) problem.getTimeLimit())\n                .maxStack(problem.getStackLimit())\n                .testCasesDir(testCasesDir)\n                .testCaseInfo(testCasesInfo)\n                .judgeExtraFiles(JudgeUtil.getProblemExtraFileMap(problem, \"judge\"))\n                .runConfig(runConfig)\n                .spjRunConfig(spjConfig)\n                .interactiveRunConfig(interactiveConfig)\n                .needUserOutputFile(getUserOutput)\n                .removeEOLBlank(problem.getIsRemoveEndBlank()).build();\n    }\n\n    class JudgeTask implements Callable<CaseResult> {\n        JudgeCaseDTO judgeDTO;\n        JudgeGlobalDTO judgeGlobalDTO;\n\n        public JudgeTask(JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {\n            this.judgeDTO = judgeDTO;\n            this.judgeGlobalDTO = judgeGlobalDTO;\n        }\n\n        @Override\n        public CaseResult call() throws SystemException {\n            final AbstractJudge abstractJudge = getAbstractJudge(judgeGlobalDTO.getJudgeMode());\n\n            CaseResult result = abstractJudge.judge(judgeDTO, judgeGlobalDTO);\n            result.setCaseId(judgeDTO.getProblemCaseId());\n            result.setScore(judgeDTO.getScore());\n            result.setInputFileName(judgeDTO.getTestCaseInputFileName());\n            result.setOutputFileName(judgeDTO.getTestCaseOutputFileName());\n            return result;\n        }\n\n        private AbstractJudge getAbstractJudge(JudgeMode judgeMode) {\n            switch (judgeMode) {\n                case DEFAULT:\n                    return defaultJudge;\n                case SPJ:\n                    return specialJudge;\n                case INTERACTIVE:\n                    return interactiveJudge;\n                default:\n                    throw new RuntimeException(\"The problem judge mode is error:\" + judgeMode);\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/ProblemTestCaseUtils.java",
    "content": "package com.simplefanc.voj.judger.judge.local;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileReader;\nimport cn.hutool.core.io.file.FileWriter;\nimport cn.hutool.core.util.CharsetUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.simplefanc.voj.common.constants.JudgeMode;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport com.simplefanc.voj.judger.common.constants.JudgeDir;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.dao.ProblemCaseEntityService;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.DigestUtils;\n\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2021/4/16 13:21\n * @Description: 判题流程解耦重构2.0，该类只负责题目测试数据的检查与初始化\n */\n@Component\n@RequiredArgsConstructor\npublic class ProblemTestCaseUtils {\n\n    private final ProblemCaseEntityService problemCaseEntityService;\n\n    public JSONObject loadTestCaseInfo(Problem problem) throws SystemException {\n        Long problemId = problem.getId();\n        String testCasesDir = JudgeDir.TEST_CASE_DIR + File.separator + \"problem_\" + problem.getId();\n        String version = problem.getCaseVersion();\n        String mode = problem.getJudgeMode();\n\n        if (FileUtil.exist(testCasesDir + File.separator + \"info\")) {\n            FileReader fileReader = new FileReader(testCasesDir + File.separator + \"info\", CharsetUtil.UTF_8);\n            String infoStr = fileReader.readString();\n            JSONObject testcaseInfo = JSONUtil.parseObj(infoStr);\n            // 测试样例被改动需要重新生成\n            if (!version.equals(testcaseInfo.getStr(\"version\", null))) {\n                return tryInitTestCaseInfo(testCasesDir, problemId, version, mode);\n            }\n            return testcaseInfo;\n        } else {\n            return tryInitTestCaseInfo(testCasesDir, problemId, version, mode);\n        }\n    }\n\n    /**\n     * 若没有测试数据，则尝试从数据库获取并且初始化到本地，如果数据库中该题目测试数据为空，rsync同步也出了问题，则直接判系统错误\n     *\n     * @param testCasesDir\n     * @param problemId\n     * @param version\n     * @param mode\n     * @return\n     * @throws SystemException\n     */\n    private JSONObject tryInitTestCaseInfo(String testCasesDir, Long problemId, String version, String mode)\n            throws SystemException {\n\n        QueryWrapper<ProblemCase> queryWrapper = new QueryWrapper<>();\n        queryWrapper.eq(\"pid\", problemId);\n        List<ProblemCase> problemCases = problemCaseEntityService.list(queryWrapper);\n        // 数据库也为空的话\n        if (problemCases.isEmpty()) {\n            throw new SystemException(\"problemID:[\" + problemId + \"] test case has not found.\", null, null);\n        }\n\n        // 文件上传\n        if(StrUtil.isEmpty(problemCases.get(0).getOutput())\n                || (problemCases.get(0).getOutput().endsWith(\".out\") || problemCases.get(0).getOutput().endsWith(\".ans\"))) {\n            // 如果本地对应文件夹也为空，说明文件丢失了\n            if (FileUtil.isEmpty(new File(testCasesDir))) {\n                throw new SystemException(\"problemID:[\" + problemId + \"] test case has not found.\", null, null);\n            } else {\n                return initLocalTestCase(mode, version, testCasesDir, problemCases);\n            }\n        } else { // 手动输入\n            List<HashMap<String, Object>> testCases = new LinkedList<>();\n            for (ProblemCase problemCase : problemCases) {\n                HashMap<String, Object> tmp = new HashMap<>();\n                tmp.put(\"input\", problemCase.getInput());\n                tmp.put(\"output\", problemCase.getOutput());\n                tmp.put(\"caseId\", problemCase.getId());\n                tmp.put(\"score\", problemCase.getScore());\n                testCases.add(tmp);\n            }\n            return initTestCase(testCases, problemId, version, mode);\n        }\n    }\n\n    /**\n     * 本地有文件，进行数据初始化 生成json文件\n     *\n     * @param mode\n     * @param version\n     * @param testCasesDir\n     * @param problemCaseList\n     * @return\n     */\n    private JSONObject initLocalTestCase(String mode, String version, String testCasesDir,\n                                        List<ProblemCase> problemCaseList) {\n        JSONObject result = new JSONObject();\n        result.set(\"mode\", mode);\n        result.set(\"version\", version);\n        result.set(\"testCasesSize\", problemCaseList.size());\n        result.set(\"testCases\", new JSONArray());\n\n        for (ProblemCase problemCase : problemCaseList) {\n            JSONObject jsonObject = new JSONObject();\n            jsonObject.set(\"caseId\", problemCase.getId());\n            jsonObject.set(\"score\", problemCase.getScore());\n            jsonObject.set(\"inputName\", problemCase.getInput());\n            jsonObject.set(\"outputName\", problemCase.getOutput());\n            // 读取输出文件\n            FileReader readFile = new FileReader(testCasesDir + File.separator + problemCase.getOutput(),\n                    CharsetUtil.UTF_8);\n            String output = readFile.readString().replaceAll(\"\\r\\n\", \"\\n\");\n\n            // spj或interactive是根据特判程序输出判断结果，所以无需初始化测试数据\n            if (JudgeMode.DEFAULT.getMode().equals(mode)) {\n                initOutputData(jsonObject, output);\n            }\n\n            ((JSONArray) result.get(\"testCases\")).put(jsonObject);\n        }\n\n        FileWriter infoFile = new FileWriter(testCasesDir + File.separator + \"info\", CharsetUtil.UTF_8);\n        // 写入记录文件\n        infoFile.write(JSONUtil.toJsonStr(result));\n\n        return result;\n    }\n\n    /**\n     * 本地无文件初始化测试数据，写成json文件\n     *\n     * @param testCases\n     * @param problemId\n     * @param version\n     * @param mode\n     * @return\n     * @throws SystemException\n     */\n    private JSONObject initTestCase(List<HashMap<String, Object>> testCases, Long problemId, String version, String mode)\n            throws SystemException {\n        // TODO 参数\n        if (testCases == null || testCases.isEmpty()) {\n            throw new SystemException(\"题号为：\" + problemId + \"的评测数据为空！\", null, \"The test cases does not exist.\");\n        }\n\n        JSONObject result = new JSONObject();\n        result.set(\"mode\", mode);\n        result.set(\"version\", version);\n        result.set(\"testCasesSize\", testCases.size());\n\n        JSONArray testCaseList = new JSONArray(testCases.size());\n\n        String testCasesDir = JudgeDir.TEST_CASE_DIR + \"/problem_\" + problemId;\n\n        // 无论有没有测试数据，一旦执行该函数，一律清空，重新生成该题目对应的测试数据文件\n        FileUtil.del(testCasesDir);\n        for (int index = 0; index < testCases.size(); index++) {\n            JSONObject jsonObject = new JSONObject();\n            String inputName = (index + 1) + \".in\";\n            jsonObject.set(\"caseId\", testCases.get(index).get(\"caseId\"));\n            jsonObject.set(\"score\", testCases.get(index).getOrDefault(\"score\", null));\n            jsonObject.set(\"inputName\", inputName);\n            // 生成对应文件\n            FileWriter infileWriter = new FileWriter(testCasesDir + \"/\" + inputName, CharsetUtil.UTF_8);\n            // 将该测试数据的输入写入到文件\n            infileWriter.write((String) testCases.get(index).get(\"input\"));\n\n            String outputName = (index + 1) + \".out\";\n            jsonObject.set(\"outputName\", outputName);\n            // 生成对应文件\n            String outputData = (String) testCases.get(index).get(\"output\");\n            FileWriter outFile = new FileWriter(testCasesDir + \"/\" + outputName, CharsetUtil.UTF_8);\n            outFile.write(outputData);\n\n            // spj或interactive是根据特判程序输出判断结果，所以无需初始化测试数据\n            if (JudgeMode.DEFAULT.getMode().equals(mode)) {\n                initOutputData(jsonObject, outputData);\n            }\n\n            testCaseList.add(jsonObject);\n        }\n\n        result.set(\"testCases\", testCaseList);\n\n        FileWriter infoFile = new FileWriter(testCasesDir + File.separator + \"info\", CharsetUtil.UTF_8);\n        // 写入记录文件\n        infoFile.write(JSONUtil.toJsonStr(result));\n        return result;\n    }\n\n    private void initOutputData(JSONObject jsonObject, String outputData) {\n        // 原数据MD5\n        jsonObject.set(\"outputMd5\", DigestUtils.md5DigestAsHex(outputData.getBytes()));\n        // 原数据大小\n        jsonObject.set(\"outputSize\", outputData.getBytes(StandardCharsets.UTF_8).length);\n        // 去掉全部空格的MD5，用来判断pe\n        jsonObject.set(\"allStrippedOutputMd5\",\n                DigestUtils.md5DigestAsHex(outputData.replaceAll(\"\\\\s+\", \"\").getBytes()));\n        // 默认去掉文末空格的MD5\n        jsonObject.set(\"EOFStrippedOutputMd5\", DigestUtils.md5DigestAsHex(rtrim(outputData).getBytes()));\n    }\n\n    /**\n     * 去除每行末尾的空白符\n     *\n     * @param value\n     * @return\n     */\n    private static String rtrim(String value) {\n        if (value == null) {\n            return null;\n        }\n        return value.replaceAll(\"[^\\\\S\\\\r\\\\n]+(?=\\\\n|\\\\r)|\\\\s+(?=$)\", \"\");\n    }\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/SandboxRun.java",
    "content": "package com.simplefanc.voj.judger.judge.local;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.http.client.SimpleClientHttpRequestFactory;\nimport org.springframework.web.client.RestClientResponseException;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @Author: chenfan\n * @Date: 2021/1/23 13:44\n * @Description: 调用判题安全沙箱\n */\n\n/**\n * args: string[]; // command line argument\n * env?: string[]; // environment\n * <p>\n * // specifies file input / pipe collector for program file descriptors\n * files?: (LocalFile | MemoryFile | PreparedFile | Pipe | null)[];\n * tty?: boolean; // enables tty on the input and output pipes (should have just one input & one output)\n * // Notice: must have TERM environment variables (e.g. TERM=xterm)\n * <p>\n * // limitations\n * cpuLimit?: number;     // ns\n * realCpuLimit?: number; // deprecated: use clock limit instead (still working)\n * clockLimit?: number;   // ns\n * memoryLimit?: number;  // byte\n * stackLimit?: number;   // byte (N/A on windows, macOS cannot set over 32M)\n * procLimit?: number;\n * <p>\n * // copy the correspond file to the container dst path\n * copyIn?: {[dst:string]:LocalFile | MemoryFile | PreparedFile};\n * <p>\n * // copy out specifies files need to be copied out from the container after execution\n * copyOut?: string[];\n * // similar to copyOut but stores file in executor service and returns fileId, later download through /file/:fileId\n * copyOutCached?: string[];\n * // specifies the directory to dump container /w content\n * copyOutDir: string\n * // specifies the max file size to copy out\n * copyOutMax: number; // byte\n */\n@Slf4j(topic = \"voj\")\npublic class SandboxRun {\n    /**\n     * 单例模式\n     */\n    private static final SandboxRun INSTANCE = new SandboxRun();\n\n    private SandboxRun() {\n    }\n\n    private static final RestTemplate REST_TEMPLATE;\n\n    static {\n        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();\n        requestFactory.setConnectTimeout(20000);\n        requestFactory.setReadTimeout(180000);\n        REST_TEMPLATE = new RestTemplate(requestFactory);\n    }\n\n    public static RestTemplate getRestTemplate() {\n        return REST_TEMPLATE;\n    }\n\n    private static final String SANDBOX_BASE_URL = \"http://localhost:5050\";\n\n    public static String getSandboxBaseUrl() {\n        return SANDBOX_BASE_URL;\n    }\n\n    private static final int MAX_PROCESS_NUMBER = 128;\n\n    private static final int TIME_LIMIT_MS = 16000;\n\n    private static final int MEMORY_LIMIT_MB = 512;\n\n    private static final int STACK_LIMIT_MB = 128;\n\n    private static final int STDIO_SIZE_MB = 32;\n\n    /**\n     * \"files\": [{ \"content\": \"\" }, { \"name\": \"stdout\", \"max\": 1024 * 1024 * 32 }, {\n     * \"name\": \"stderr\", \"max\": 1024 * 1024 * 32 }]\n     */\n    private static final JSONArray COMPILE_FILES = new JSONArray();\n\n    static {\n        JSONObject content = new JSONObject();\n        content.set(\"content\", \"\");\n\n        JSONObject stdout = new JSONObject();\n        stdout.set(\"name\", \"stdout\");\n        stdout.set(\"max\", 1024 * 1024 * STDIO_SIZE_MB);\n\n        JSONObject stderr = new JSONObject();\n        stderr.set(\"name\", \"stderr\");\n        stderr.set(\"max\", 1024 * 1024 * STDIO_SIZE_MB);\n        COMPILE_FILES.put(content);\n        COMPILE_FILES.put(stdout);\n        COMPILE_FILES.put(stderr);\n    }\n\n    public static final HashMap<String, Integer> RESULT_STATUS_MAP = new HashMap<>() {\n        {\n            put(\"Time Limit Exceeded\", JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED.getStatus());\n            put(\"Memory Limit Exceeded\", JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());\n            put(\"Output Limit Exceeded\", JudgeStatus.STATUS_RUNTIME_ERROR.getStatus());\n            put(\"Accepted\", JudgeStatus.STATUS_ACCEPTED.getStatus());\n            put(\"Nonzero Exit Status\", JudgeStatus.STATUS_RUNTIME_ERROR.getStatus());\n            put(\"Internal Error\", JudgeStatus.STATUS_SYSTEM_ERROR.getStatus());\n            put(\"File Error\", JudgeStatus.STATUS_SYSTEM_ERROR.getStatus());\n            put(\"Signalled\", JudgeStatus.STATUS_RUNTIME_ERROR.getStatus());\n        }\n    };\n\n    public static final Map<Integer, String> SIGNALS = new HashMap<>() {\n        {\n            put(0, \"\");\n            put(1, \"Hangup\");\n            put(2, \"Interrupt\");\n            put(3, \"Quit\");\n            put(4, \"Illegal instruction\");\n            put(5, \"Trace/breakpoint trap\");\n            put(6, \"Aborted\");\n            put(7, \"Bus error\");\n            put(8, \"Floating point exception\");\n            put(9, \"Killed\");\n            put(10, \"User defined signal 1\");\n            put(11, \"Segmentation fault\");\n            put(12, \"User defined signal 2\");\n            put(13, \"Broken pipe\");\n            put(14, \"Alarm clock\");\n            put(15, \"Terminated\");\n            put(16, \"Stack fault\");\n            put(17, \"Child exited\");\n            put(18, \"Continued\");\n            put(19, \"Stopped (signal)\");\n            put(20, \"Stopped\");\n            put(21, \"Stopped (tty input)\");\n            put(22, \"Stopped (tty output)\");\n            put(23, \"Urgent I/O condition\");\n            put(24, \"CPU time limit exceeded\");\n            put(25, \"File size limit exceeded\");\n            put(26, \"Virtual timer expired\");\n            put(27, \"Profiling timer expired\");\n            put(28, \"Window changed\");\n            put(29, \"I/O possible\");\n            put(30, \"Power failure\");\n            put(31, \"Bad system call\");\n        }\n    };\n\n    public JSONArray run(String uri, JSONObject param) throws SystemException {\n        HttpHeaders headers = new HttpHeaders();\n        headers.setContentType(MediaType.APPLICATION_JSON);\n        HttpEntity<String> request = new HttpEntity<>(JSONUtil.toJsonStr(param), headers);\n        ResponseEntity<String> postForEntity;\n        try {\n            postForEntity = REST_TEMPLATE.postForEntity(SANDBOX_BASE_URL + uri, request, String.class);\n            // TODO 疑似出现OOM 普通评测：如果沙盒运行程序不是 Accepted 可以不获取 stdout\n            // 临时解决：顺序评测 and 降低程序的并发数\n            // 如果测试数据标准输出真的特别大 只有增大JVM配置\n            return JSONUtil.parseArray(postForEntity.getBody());\n        } catch (RestClientResponseException ex) {\n            if (ex.getRawStatusCode() != 200) {\n                throw new SystemException(\"Cannot connect to sandbox service.\", null, ex.getResponseBodyAsString());\n            }\n        } catch (Exception e) {\n            throw new SystemException(\"Call SandBox Error.\", null, e.getMessage());\n        }\n        return null;\n    }\n\n    public static void delFile(String fileId) {\n        try {\n            REST_TEMPLATE.delete(SANDBOX_BASE_URL + \"/file/{0}\", fileId);\n        } catch (RestClientResponseException ex) {\n            if (ex.getRawStatusCode() != 200) {\n                log.error(\"安全沙箱判题的删除内存中的文件缓存操作异常----------------->{}\", ex.getResponseBodyAsString());\n            }\n        }\n    }\n\n    /**\n     * @param maxCpuTime        最大编译的cpu时间 ms\n     * @param maxRealTime       最大编译的真实时间 ms\n     * @param maxMemory         最大编译的空间 b\n     * @param maxStack          最大编译的栈空间 b\n     * @param srcName           编译的源文件名字\n     * @param exeName           编译生成的exe文件名字\n     * @param args              编译的cmd参数\n     * @param envs              编译的环境变量\n     * @param code              编译的源代码\n     * @param extraFiles        编译所需的额外文件 key:文件名，value:文件内容\n     * @param needCopyOutCached 是否需要生成编译后的用户程序exe文件\n     * @param needCopyOutExe    是否需要生成用户程序的缓存文件，即生成用户程序id\n     * @param copyOutDir        生成编译后的用户程序exe文件的指定路径\n     * @MethodName compile\n     * @Description 编译运行\n     * @Return\n     * @Since 2022/1/3\n     */\n    public static JSONArray compile(Long maxCpuTime,\n                                    Long maxRealTime,\n                                    Long maxMemory,\n                                    Long maxStack,\n                                    String srcName,\n                                    String exeName,\n                                    List<String> args,\n                                    List<String> envs,\n                                    String code,\n                                    HashMap<String, String> extraFiles,\n                                    Boolean needCopyOutCached,\n                                    Boolean needCopyOutExe,\n                                    String copyOutDir) throws SystemException {\n        JSONObject cmd = new JSONObject();\n        cmd.set(\"args\", args);\n        cmd.set(\"env\", envs);\n        cmd.set(\"files\", COMPILE_FILES);\n        // ms-->ns\n        cmd.set(\"cpuLimit\", maxCpuTime * 1000 * 1000L);\n        cmd.set(\"clockLimit\", maxRealTime * 1000 * 1000L);\n        // byte\n        cmd.set(\"memoryLimit\", maxMemory);\n        cmd.set(\"procLimit\", MAX_PROCESS_NUMBER);\n        cmd.set(\"stackLimit\", maxStack);\n\n        JSONObject fileContent = new JSONObject();\n        fileContent.set(\"content\", code);\n\n        JSONObject copyIn = new JSONObject();\n        copyIn.set(srcName, fileContent);\n\n        if (extraFiles != null) {\n            for (Map.Entry<String, String> entry : extraFiles.entrySet()) {\n                if (StrUtil.isNotEmpty(entry.getKey()) && StrUtil.isNotEmpty(entry.getValue())) {\n                    JSONObject content = new JSONObject();\n                    content.set(\"content\", entry.getValue());\n                    copyIn.set(entry.getKey(), content);\n                }\n            }\n        }\n\n        cmd.set(\"copyIn\", copyIn);\n        cmd.set(\"copyOut\", new JSONArray().put(\"stdout\").put(\"stderr\"));\n\n        if (needCopyOutCached) {\n            cmd.set(\"copyOutCached\", new JSONArray().put(exeName));\n        }\n\n        if (needCopyOutExe) {\n            cmd.set(\"copyOutDir\", copyOutDir);\n        }\n\n        JSONObject param = new JSONObject();\n        param.set(\"cmd\", new JSONArray().put(cmd));\n\n        JSONArray result = INSTANCE.run(\"/run\", param);\n        JSONObject tmp = (JSONObject) result.get(0);\n        ((JSONObject) result.get(0)).set(\"status\", RESULT_STATUS_MAP.get(tmp.getStr(\"status\")));\n        return result;\n    }\n\n    /**\n     * @param args          普通评测运行cmd的命令参数\n     * @param envs          普通评测运行的环境变量\n     * @param testCasePath  题目数据的输入文件路径\n     * @param maxTime       评测的最大限制时间 ms\n     * @param maxOutputSize 评测的最大输出大小 kb\n     * @param maxStack      评测的最大限制栈空间 mb\n     * @param exeName       评测的用户程序名称\n     * @param fileId        评测的用户程序文件id\n     * @param fileSrc       评测的用户程序文件绝对路径，如果userFileId存在则为null\n     * @MethodName testCase\n     * @Description 普通评测\n     * @Return JSONArray\n     * @Since 2022/1/3\n     */\n    public static JSONArray testCase(List<String> args,\n                                     List<String> envs,\n                                     String testCasePath,\n                                     Long maxTime,\n                                     Long maxMemory,\n                                     Long maxOutputSize,\n                                     Integer maxStack,\n                                     String exeName,\n                                     String fileId,\n                                     String fileSrc) throws SystemException {\n\n        JSONObject cmd = new JSONObject();\n        cmd.set(\"args\", args);\n        cmd.set(\"env\", envs);\n\n        JSONArray files = new JSONArray();\n        JSONObject content = new JSONObject();\n        content.set(\"src\", testCasePath);\n\n        JSONObject stdout = new JSONObject();\n        stdout.set(\"name\", \"stdout\");\n        stdout.set(\"max\", maxOutputSize);\n\n        JSONObject stderr = new JSONObject();\n        stderr.set(\"name\", \"stderr\");\n        stderr.set(\"max\", 1024 * 1024 * 16);\n        files.put(content);\n        files.put(stdout);\n        files.put(stderr);\n\n        cmd.set(\"files\", files);\n\n        // ms-->ns\n        cmd.set(\"cpuLimit\", maxTime * 1000 * 1000L);\n        cmd.set(\"clockLimit\", maxTime * 1000 * 1000L * 3);\n        // byte\n        cmd.set(\"memoryLimit\", (maxMemory + 100) * 1024 * 1024L);\n        cmd.set(\"procLimit\", MAX_PROCESS_NUMBER);\n        cmd.set(\"stackLimit\", maxStack * 1024 * 1024L);\n\n        JSONObject exeFile = new JSONObject();\n        if (StrUtil.isNotEmpty(fileId)) {\n            exeFile.set(\"fileId\", fileId);\n        } else {\n            exeFile.set(\"src\", fileSrc);\n        }\n        JSONObject copyIn = new JSONObject();\n        copyIn.set(exeName, exeFile);\n\n        cmd.set(\"copyIn\", copyIn);\n        cmd.set(\"copyOut\", new JSONArray().put(\"stdout\").put(\"stderr\"));\n\n        JSONObject param = new JSONObject();\n        param.set(\"cmd\", new JSONArray().put(cmd));\n\n        // 调用判题安全沙箱\n        JSONArray result = INSTANCE.run(\"/run\", param);\n\n        final JSONObject jsonObject = (JSONObject) result.get(0);\n        jsonObject.set(\"status\", RESULT_STATUS_MAP.get(jsonObject.getStr(\"status\")));\n        return result;\n    }\n\n    /**\n     * @param args                   特殊判题的运行cmd命令参数\n     * @param envs                   特殊判题的运行环境变量\n     * @param userOutputFilePath     用户程序输出文件的路径\n     * @param userOutputFileName     用户程序输出文件的名字\n     * @param testCaseInputFilePath  题目数据的输入文件的路径\n     * @param testCaseInputFileName  题目数据的输入文件的名字\n     * @param testCaseOutputFilePath 题目数据的输出文件的路径\n     * @param testCaseOutputFileName 题目数据的输出文件的路径\n     * @param spjExeSrc              特殊判题的exe文件的路径\n     * @param spjExeName             特殊判题的exe文件的名字\n     * @MethodName spjCheckResult\n     * @Description 特殊判题的评测\n     * @Return JSONArray\n     * @Since 2022/1/3\n     */\n    public static JSONArray spjCheckResult(List<String> args,\n                                           List<String> envs,\n                                           String userOutputFilePath,\n                                           String userOutputFileName,\n                                           String testCaseInputFilePath,\n                                           String testCaseInputFileName,\n                                           String testCaseOutputFilePath,\n                                           String testCaseOutputFileName,\n                                           String spjExeSrc,\n                                           String spjExeName) throws SystemException {\n\n        JSONObject cmd = new JSONObject();\n        cmd.set(\"args\", args);\n        cmd.set(\"env\", envs);\n\n        JSONArray outFiles = new JSONArray();\n\n        JSONObject content = new JSONObject();\n        content.set(\"content\", \"\");\n\n        JSONObject outStdout = new JSONObject();\n        outStdout.set(\"name\", \"stdout\");\n        outStdout.set(\"max\", 1024 * 1024 * 16);\n\n        JSONObject outStderr = new JSONObject();\n        outStderr.set(\"name\", \"stderr\");\n        outStderr.set(\"max\", 1024 * 1024 * 16);\n\n        outFiles.put(content);\n        outFiles.put(outStdout);\n        outFiles.put(outStderr);\n        cmd.set(\"files\", outFiles);\n\n        // ms-->ns\n        cmd.set(\"cpuLimit\", TIME_LIMIT_MS * 1000 * 1000L);\n        cmd.set(\"clockLimit\", TIME_LIMIT_MS * 1000 * 1000L * 3);\n        // byte\n        cmd.set(\"memoryLimit\", MEMORY_LIMIT_MB * 1024 * 1024L);\n        cmd.set(\"procLimit\", MAX_PROCESS_NUMBER);\n        cmd.set(\"stackLimit\", STACK_LIMIT_MB * 1024 * 1024L);\n\n        JSONObject spjExeFile = new JSONObject();\n        spjExeFile.set(\"src\", spjExeSrc);\n\n        JSONObject useOutputFileSrc = new JSONObject();\n        useOutputFileSrc.set(\"src\", userOutputFilePath);\n\n        JSONObject stdInputFileSrc = new JSONObject();\n        stdInputFileSrc.set(\"src\", testCaseInputFilePath);\n\n        JSONObject stdOutFileSrc = new JSONObject();\n        stdOutFileSrc.set(\"src\", testCaseOutputFilePath);\n\n        JSONObject spjCopyIn = new JSONObject();\n\n        spjCopyIn.set(spjExeName, spjExeFile);\n        spjCopyIn.set(userOutputFileName, useOutputFileSrc);\n        spjCopyIn.set(testCaseInputFileName, stdInputFileSrc);\n        spjCopyIn.set(testCaseOutputFileName, stdOutFileSrc);\n\n        cmd.set(\"copyIn\", spjCopyIn);\n        cmd.set(\"copyOut\", new JSONArray().put(\"stdout\").put(\"stderr\"));\n\n        JSONObject param = new JSONObject();\n\n        param.set(\"cmd\", new JSONArray().put(cmd));\n\n        // 调用判题安全沙箱\n        JSONArray result = INSTANCE.run(\"/run\", param);\n\n        JSONObject tmp = (JSONObject) result.get(0);\n        ((JSONObject) result.get(0)).set(\"status\", RESULT_STATUS_MAP.get(tmp.getStr(\"status\")));\n        return result;\n    }\n\n    /**\n     * @param args                   cmd的命令参数 评测运行的命令\n     * @param envs                   测评的环境变量\n     * @param userExeName            用户程序的名字\n     * @param userFileId             用户程序在编译后返回的id，主要是对应内存中已编译后的文件\n     * @param userFileSrc            用户程序文件的绝对路径，如果userFileId存在则为null\n     * @param userMaxTime            用户程序的最大测评时间 ms\n     * @param userMaxStack           用户程序的最大测评栈空间 mb\n     * @param testCaseInputPath      题目数据的输入文件路径\n     * @param testCaseInputFileName  题目数据的输入文件名字\n     * @param testCaseOutputFilePath 题目数据的输出文件路径\n     * @param testCaseOutputFileName 题目数据的输出文件名字\n     * @param userOutputFileName     用户程序的输出文件名字\n     * @param interactArgs           交互程序运行的cmd命令参数\n     * @param interactEnvs           交互程序运行的环境变量\n     * @param interactExeSrc         交互程序的exe文件路径\n     * @param interactExeName        交互程序的exe文件名字\n     * @MethodName interactTestCase\n     * @Description 交互评测\n     * @Return JSONArray\n     * @Since 2022/1/3\n     */\n    public static JSONArray interactTestCase(List<String> args,\n                                             List<String> envs,\n                                             String userExeName,\n                                             String userFileId,\n                                             String userFileSrc,\n                                             Long userMaxTime,\n                                             Long userMaxMemory,\n                                             Integer userMaxStack,\n                                             String testCaseInputPath,\n                                             String testCaseInputFileName,\n                                             String testCaseOutputFilePath,\n                                             String testCaseOutputFileName,\n                                             String userOutputFileName,\n                                             List<String> interactArgs,\n                                             List<String> interactEnvs,\n                                             String interactExeSrc,\n                                             String interactExeName) throws SystemException {\n\n        /**\n         * 注意：用户源代码需要先编译，若是通过编译需要先将文件存入内存，再利用管道判题，同时特殊判题程序必须已编译且存在（否则判题失败，系统错误）！\n         */\n\n        JSONObject pipeInputCmd = new JSONObject();\n        pipeInputCmd.set(\"args\", args);\n        pipeInputCmd.set(\"env\", envs);\n\n        JSONArray files = new JSONArray();\n\n        JSONObject stderr = new JSONObject();\n        stderr.set(\"name\", \"stderr\");\n        stderr.set(\"max\", 1024 * 1024 * STDIO_SIZE_MB);\n\n        files.put(new JSONObject());\n        files.put(new JSONObject());\n        files.put(stderr);\n\n        String inTmp = files.toString().replace(\"{}\", \"null\");\n        pipeInputCmd.set(\"files\", JSONUtil.parseArray(inTmp, false));\n\n        // ms-->ns\n        pipeInputCmd.set(\"cpuLimit\", userMaxTime * 1000 * 1000L);\n        pipeInputCmd.set(\"clockLimit\", userMaxTime * 1000 * 1000L * 3);\n\n        // byte\n\n        pipeInputCmd.set(\"memoryLimit\", (userMaxMemory + 100) * 1024 * 1024L);\n        pipeInputCmd.set(\"procLimit\", MAX_PROCESS_NUMBER);\n        pipeInputCmd.set(\"stackLimit\", userMaxStack * 1024 * 1024L);\n\n        JSONObject exeFile = new JSONObject();\n        if (StrUtil.isNotEmpty(userFileId)) {\n            exeFile.set(\"fileId\", userFileId);\n        } else {\n            exeFile.set(\"src\", userFileSrc);\n        }\n        JSONObject copyIn = new JSONObject();\n        copyIn.set(userExeName, exeFile);\n\n        pipeInputCmd.set(\"copyIn\", copyIn);\n        pipeInputCmd.set(\"copyOut\", new JSONArray());\n\n        // 管道输出，用户程序输出数据经过特殊判题程序后，得到的最终输出结果。\n        JSONObject pipeOutputCmd = new JSONObject();\n        pipeOutputCmd.set(\"args\", interactArgs);\n        pipeOutputCmd.set(\"env\", interactEnvs);\n\n        JSONArray outFiles = new JSONArray();\n\n        JSONObject outStderr = new JSONObject();\n        outStderr.set(\"name\", \"stderr\");\n        outStderr.set(\"max\", 1024 * 1024 * STDIO_SIZE_MB);\n        outFiles.put(new JSONObject());\n        outFiles.put(new JSONObject());\n        outFiles.put(outStderr);\n        String outTmp = outFiles.toString().replace(\"{}\", \"null\");\n        pipeOutputCmd.set(\"files\", JSONUtil.parseArray(outTmp, false));\n\n        // ms-->ns\n        pipeOutputCmd.set(\"cpuLimit\", userMaxTime * 1000 * 1000L * 2);\n        pipeOutputCmd.set(\"clockLimit\", userMaxTime * 1000 * 1000L * 3 * 2);\n        // byte\n        pipeOutputCmd.set(\"memoryLimit\", (userMaxMemory + 100) * 1024 * 1024L * 2);\n        pipeOutputCmd.set(\"procLimit\", MAX_PROCESS_NUMBER);\n        pipeOutputCmd.set(\"stackLimit\", STACK_LIMIT_MB * 1024 * 1024L);\n\n        JSONObject spjExeFile = new JSONObject();\n        spjExeFile.set(\"src\", interactExeSrc);\n\n        JSONObject stdInputFileSrc = new JSONObject();\n        stdInputFileSrc.set(\"src\", testCaseInputPath);\n\n        JSONObject stdOutFileSrc = new JSONObject();\n        stdOutFileSrc.set(\"src\", testCaseOutputFilePath);\n\n        JSONObject interactiveCopyIn = new JSONObject();\n        interactiveCopyIn.set(interactExeName, spjExeFile);\n        interactiveCopyIn.set(testCaseInputFileName, stdInputFileSrc);\n        interactiveCopyIn.set(testCaseOutputFileName, stdOutFileSrc);\n\n        pipeOutputCmd.set(\"copyIn\", interactiveCopyIn);\n        pipeOutputCmd.set(\"copyOut\", new JSONArray().put(userOutputFileName));\n\n        JSONArray cmdList = new JSONArray();\n        cmdList.put(pipeInputCmd);\n        cmdList.put(pipeOutputCmd);\n\n        JSONObject param = new JSONObject();\n        // 添加cmd指令\n        param.set(\"cmd\", cmdList);\n\n        // 添加管道映射\n        JSONArray pipeMapping = new JSONArray();\n        // 用户程序\n        JSONObject user = new JSONObject();\n\n        JSONObject userIn = new JSONObject();\n        userIn.set(\"index\", 0);\n        userIn.set(\"fd\", 1);\n\n        JSONObject userOut = new JSONObject();\n        userOut.set(\"index\", 1);\n        userOut.set(\"fd\", 0);\n\n        user.set(\"in\", userIn);\n        user.set(\"out\", userOut);\n        user.set(\"max\", STDIO_SIZE_MB * 1024 * 1024);\n        user.set(\"proxy\", true);\n        user.set(\"name\", \"stdout\");\n\n        // 评测程序\n        JSONObject judge = new JSONObject();\n\n        JSONObject judgeIn = new JSONObject();\n        judgeIn.set(\"index\", 1);\n        judgeIn.set(\"fd\", 1);\n\n        JSONObject judgeOut = new JSONObject();\n        judgeOut.set(\"index\", 0);\n        judgeOut.set(\"fd\", 0);\n\n        judge.set(\"in\", judgeIn);\n        judge.set(\"out\", judgeOut);\n        judge.set(\"max\", STDIO_SIZE_MB * 1024 * 1024);\n        judge.set(\"proxy\", true);\n        judge.set(\"name\", \"stdout\");\n\n        // 添加到管道映射列表\n        pipeMapping.add(user);\n        pipeMapping.add(judge);\n\n        param.set(\"pipeMapping\", pipeMapping);\n\n        // 调用判题安全沙箱\n        JSONArray result = INSTANCE.run(\"/run\", param);\n        JSONObject userRes = (JSONObject) result.get(0);\n        JSONObject interactiveRes = (JSONObject) result.get(1);\n        userRes.set(\"status\", RESULT_STATUS_MAP.get(userRes.getStr(\"status\")));\n        interactiveRes.set(\"status\", RESULT_STATUS_MAP.get(interactiveRes.getStr(\"status\")));\n        return result;\n    }\n}\n/*\n     1. compile\n        Json Request Body\n        {\n            \"cmd\": [{\n                \"args\": [\"/usr/bin/g++\", \"a.cc\", \"-o\", \"a\"],\n                \"env\": [\"PATH=/usr/bin:/bin\"],\n                \"files\": [{\n                    \"content\": \"\"\n                }, {\n                    \"name\": \"stdout\",\n                    \"max\": 10240\n                }, {\n                    \"name\": \"stderr\",\n                    \"max\": 10240\n                }],\n                \"cpuLimit\": 10000000000,\n                \"memoryLimit\": 104857600,\n                \"procLimit\": 50,\n                \"copyIn\": {\n                    \"a.cc\": {\n                        \"content\": \"#include <iostream>\\nusing namespace std;\\nint main() {\\nint a, b;\\ncin >> a >> b;\\ncout << a + b << endl;\\n}\"\n                    }\n                },\n                \"copyOut\": [\"stdout\", \"stderr\"],\n                \"copyOutCached\": [\"a.cc\", \"a\"],\n                \"copyOutDir\": \"1\"\n            }]\n        }\n\n        Json Response Data\n        [\n            {\n                \"status\": \"Accepted\",\n                \"exitStatus\": 0,\n                \"time\": 303225231,\n                \"memory\": 32243712,\n                \"runTime\": 524177700,\n                \"files\": {\n                    \"stderr\": \"\",\n                    \"stdout\": \"\"\n                },\n                \"fileIds\": {\n                    \"a\": \"5LWIZAA45JHX4Y4Z\",\n                    \"a.cc\": \"NOHPGGDTYQUFRSLJ\"\n                }\n            }\n        ]\n    2.test case\n\n      Json Request Body\n      {\n        \"cmd\": [{\n            \"args\": [\"a\"],\n            \"env\": [\"PATH=/usr/bin:/bin\",\"LANG=en_US.UTF-8\",\"LC_ALL=en_US.UTF-8\",\"LANGUAGE=en_US:en\"],\n            \"files\": [{\n                \"src\": \"/judge/test_case/problem_1010/1.in\"\n            }, {\n                \"name\": \"stdout\",\n                \"max\": 10240\n            }, {\n                \"name\": \"stderr\",\n                \"max\": 10240\n            }],\n            \"cpuLimit\": 10000000000,\n            \"realCpuLimit\":30000000000,\n            \"stackLimit\":134217728,\n            \"memoryLimit\": 104811111,\n            \"procLimit\": 50,\n            \"copyIn\": {\n                \"a\":{\"fileId\":\"WDQL5TNLRRVB2KAP\"}\n            },\n            \"copyOut\": [\"stdout\", \"stderr\"]\n        }]\n      }\n\n    Json Response Data\n     [{\n      \"status\": \"Accepted\",\n      \"exitStatus\": 0,\n      \"time\": 3171607,\n      \"memory\": 475136,\n      \"runTime\": 110396333,\n      \"files\": {\n        \"stderr\": \"\",\n        \"stdout\": \"23\\n\"\n      }\n    }]\n\n    3. Interactive\n    {\n        \"pipeMapping\": [{\n            \"in\": {\n                \"max\": 16777216,\n                \"index\": 0,\n                \"fd\": 1\n            },\n            \"out\": {\n                \"index\": 1,\n                \"fd\": 0\n            }\n        }],\n        \"cmd\": [{\n                \"stackLimit\": 134217728,\n                \"cpuLimit\": 3000000000,\n                \"realCpuLimit\": 9000000000,\n                \"clockLimit\": 64,\n                \"env\": [\n                    \"LANG=en_US.UTF-8\",\n                    \"LANGUAGE=en_US:en\",\n                    \"LC_ALL=en_US.UTF-8\",\n                    \"PYTHONIOENCODING=utf-8\"\n                ],\n                \"copyOut\": [\n                    \"stderr\"\n                ],\n                \"args\": [\n                    \"/usr/bin/python3\",\n                    \"main\"\n                ],\n                \"files\": [{\n                        \"src\": \"/judge/test_case/problem_1002/5.in\"\n                    },\n                    null,\n                    {\n                        \"max\": 16777216,\n                        \"name\": \"stderr\"\n                    }\n                ],\n                \"memoryLimit\": 536870912,\n                \"copyIn\": {\n                    \"main\": {\n                        \"fileId\": \"CGTRDEMKW5VAYN6O\"\n                    }\n                }\n            },\n            {\n                \"stackLimit\": 134217728,\n                \"cpuLimit\": 8000000000,\n                \"clockLimit\": 24000000000,\n                \"env\": [\n                    \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n                    \"LANG=en_US.UTF-8\",\n                    \"LANGUAGE=en_US:en\",\n                    \"LC_ALL=en_US.UTF-8\"\n                ],\n                \"copyOut\": [\n                    \"stdout\",\n                    \"stderr\"\n                ],\n                \"args\": [\n                    \"/w/spj\",\n                    \"/w/tmp\"\n                ],\n                \"files\": [\n                    null,\n                    {\n                        \"max\": 16777216,\n                        \"name\": \"stdout\"\n                    },\n                    {\n                        \"max\": 16777216,\n                        \"name\": \"stderr\"\n                    }\n                ],\n                \"memoryLimit\": 536870912,\n                \"copyIn\": {\n                    \"spj\": {\n                        \"src\": \"/judge/spj/1002/spj\"\n                    },\n                    \"tmp\": {\n                        \"src\": \"/judge/test_case/problem_1002/5.out\"\n                    }\n                },\n                \"procLimit\": 64\n            }\n        ]\n    }\n  */\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/pojo/CaseResult.java",
    "content": "package com.simplefanc.voj.judger.judge.local.pojo;\n\nimport lombok.Data;\n\n@Data\npublic class CaseResult {\n    private Integer status;\n    private Long time;\n    private Long memory;\n    private Double percentage; // 交互程序\n    private String errMsg;\n    private String output;\n\n    private Long caseId;\n    private Integer score;\n    private String inputFileName;\n    private String outputFileName;\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/pojo/JudgeCaseDTO.java",
    "content": "package com.simplefanc.voj.judger.judge.local.pojo;\n\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\n\n/**\n * @Author: chenfan\n * @Date: 2022/1/2 20:58\n * @Description: 评测题目的传输类\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@Builder\npublic class JudgeCaseDTO implements Serializable {\n\n    private static final long serialVersionUID = 666L;\n\n    /**\n     * 当前题目评测点的的编号\n     */\n    private Integer testCaseNum;\n\n    /**\n     * 当前题目评测点的输入文件的名字\n     */\n    private String testCaseInputFileName;\n\n    /**\n     * 当前题目评测点的输入文件的绝对路径\n     */\n    private String testCaseInputPath;\n\n    /**\n     * 当前题目评测点的输出文件的名字\n     */\n    private String testCaseOutputFileName;\n\n    /**\n     * 当前题目评测点的输出文件的绝对路径\n     */\n    private String testCaseOutputPath;\n\n    /**\n     * 数据库表的测试样例id\n     */\n    private Long problemCaseId;\n\n    /**\n     * 当前题目评测点的分数（OI题目的测试点才有）\n     */\n    private Integer score;\n\n    /**\n     * 当前题目评测点的输出字符大小限制 kb\n     */\n    private Long maxOutputSize;\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/pojo/JudgeGlobalDTO.java",
    "content": "package com.simplefanc.voj.judger.judge.local.pojo;\n\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.common.constants.JudgeMode;\nimport com.simplefanc.voj.judger.common.constants.RunConfig;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2022/1/3 11:53\n * @Description: 一次评测全局通用的传输实体类\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@Builder\npublic class JudgeGlobalDTO implements Serializable {\n\n    private static final long serialVersionUID = 888L;\n\n    /**\n     * 普通评测的命令配置\n     */\n    RunConfig runConfig;\n\n    /**\n     * 特殊判题的命令配置\n     */\n    RunConfig spjRunConfig;\n\n    /**\n     * 交互判题的命令配置\n     */\n    RunConfig interactiveRunConfig;\n\n    /**\n     * 当前评测题目的id\n     */\n    private Long problemId;\n\n    /**\n     * 当前评测题目的模式\n     */\n    private JudgeMode judgeMode;\n\n    /**\n     * 用户程序在沙盒编译后对应内存文件的id，运行时需要传入\n     */\n    private String userFileId;\n\n    /**\n     * 用户程序代码文件的绝对路径\n     */\n    private String userFileSrc;\n\n    /**\n     * 整个评测的工作目录\n     */\n    private String runDir;\n\n    /**\n     * 判题沙盒评测程序的最大实际时间，一般为题目最大限制时间+200ms\n     */\n    private Long testTime;\n\n    /**\n     * 当前题目评测的最大时间限制 ms\n     */\n    private Long maxTime;\n\n    /**\n     * 当前题目评测的最大空间限制 mb\n     */\n    private Long maxMemory;\n\n    /**\n     * 当前题目评测的最大栈空间限制 mb\n     */\n    private Integer maxStack;\n\n    /**\n     * 测试数据文件所在文件夹\n     */\n    String testCasesDir;\n\n    /**\n     * 评测数据json内容\n     */\n    private JSONObject testCaseInfo;\n\n    /**\n     * 交互程序或特判程序所需的额外文件 key:文件名，value：文件路径\n     */\n    private HashMap<String, String> judgeExtraFiles;\n\n    /**\n     * 是否需要生成用户程序输出的文件\n     */\n    private Boolean needUserOutputFile;\n\n    /**\n     * 是否需要自动移除评测数据的行末空格\n     */\n    private Boolean removeEOLBlank;\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/pojo/JudgeResult.java",
    "content": "package com.simplefanc.voj.judger.judge.local.pojo;\n\nimport lombok.Data;\n\n@Data\npublic class JudgeResult {\n    private Integer status;\n    private Integer time;\n    private Integer memory;\n    private Integer score;\n    private Integer oiRankScore;\n    private String errMsg;\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/pojo/SandBoxRes.java",
    "content": "package com.simplefanc.voj.judger.judge.local.pojo;\n\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\n/**\n * @Author: chenfan\n * @Date: 2022/1/3 15:27\n * @Description: 单个测评结果实体类\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@Builder\npublic class SandBoxRes {\n\n    /**\n     * 单个程序的状态码\n     */\n    private Integer status;\n\n    /**\n     * 单个程序的退出码\n     */\n    private Integer exitCode;\n\n    /**\n     * 单个程序的运行所耗空间 kb\n     */\n    private Long memory;\n\n    /**\n     * 单个程序的运行所耗时间 ms\n     */\n    private Long time;\n\n    /**\n     * 单个程序的标准输出\n     */\n    private String stdout;\n\n    /**\n     * 单个程序的错误信息\n     */\n    private String stderr;\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/strategy/AbstractJudge.java",
    "content": "package com.simplefanc.voj.judger.judge.local.strategy;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.judger.common.constants.JudgeDir;\nimport com.simplefanc.voj.judger.common.constants.RunConfig;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.common.utils.JudgeUtil;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeCaseDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeGlobalDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.CaseResult;\nimport com.simplefanc.voj.judger.judge.local.pojo.SandBoxRes;\n\nimport java.io.File;\nimport java.text.MessageFormat;\nimport java.util.List;\n\n/**\n * @Author: chenfan\n * @Date: 2022/1/2 20:46\n * @Description:\n */\npublic abstract class AbstractJudge {\n\n    protected static final int SPJ_PC = 99;\n\n    protected static final int SPJ_AC = 100;\n\n    protected static final int SPJ_PE = 101;\n\n    protected static final int SPJ_WA = 102;\n\n    protected static final int SPJ_ERROR = 103;\n\n    public CaseResult judge(JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemException {\n        // 判题\n        JSONArray judgeResultList = judgeCase(judgeDTO, judgeGlobalDTO);\n\n        // 处理判题结果\n        switch (judgeGlobalDTO.getJudgeMode()) {\n            case SPJ:\n            case DEFAULT:\n                return handle(judgeDTO, judgeGlobalDTO, judgeResultList);\n            case INTERACTIVE:\n                return handleMultiple(judgeDTO, judgeGlobalDTO, judgeResultList);\n            default:\n                throw new RuntimeException(\"The problem mode is error:\" + judgeGlobalDTO.getJudgeMode());\n        }\n\n    }\n\n    protected abstract JSONArray judgeCase(JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemException;\n\n    protected CaseResult processResult(SandBoxRes sandBoxRes, JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO)\n            throws SystemException {\n        return null;\n    }\n\n    protected CaseResult processMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes,\n                                               JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {\n        return null;\n    }\n\n    protected List<String> parseRunCommand(RunConfig runConfig, String testCaseInputName,\n                                           String userOutputName, String testCaseOutputName) {\n        String command = runConfig.getCommand();\n        command = MessageFormat.format(command,\n                JudgeDir.TMPFS_DIR, runConfig.getExeName(),\n                JudgeDir.TMPFS_DIR + File.separator + testCaseInputName,\n                JudgeDir.TMPFS_DIR + File.separator + userOutputName,\n                JudgeDir.TMPFS_DIR + File.separator + testCaseOutputName);\n\n        return JudgeUtil.translateCommandline(command);\n    }\n\n    protected JSONObject parseTestLibErr(String msg) {\n\n        JSONObject res = new JSONObject(2);\n        String output = msg.substring(0, Math.min(1024, msg.length()));\n        if (output.startsWith(\"ok \")) {\n            res.set(\"code\", SPJ_AC);\n            res.set(\"errMsg\", output.split(\"ok \")[1]);\n        } else if (output.startsWith(\"wrong answer \")) {\n            res.set(\"code\", SPJ_WA);\n            res.set(\"errMsg\", output.split(\"wrong answer \")[1]);\n        } else if (output.startsWith(\"wrong output format \")) {\n            res.set(\"code\", SPJ_WA);\n            res.set(\"errMsg\", \"May be output presentation error. \" + output.split(\"wrong output format\")[1]);\n        } else if (output.startsWith(\"partially correct \")) {\n            res.set(\"errMsg\", output.split(\"partially correct \")[1]);\n            String numStr = ReUtil.get(\"partially correct \\\\(([\\\\s\\\\S]*?)\\\\) \", output, 1);\n            double percentage = 0.0;\n            if (StrUtil.isNotEmpty(numStr)) {\n                percentage = Integer.parseInt(numStr) * 1.0 / 100;\n            }\n            res.set(\"percentage\", percentage);\n            res.set(\"code\", SPJ_PC);\n        } else if (output.startsWith(\"points \")) {\n            res.set(\"code\", SPJ_PC);\n            String numStr = output.split(\"points \")[1].split(\" \")[0];\n            double percentage = 0.0;\n            if (StrUtil.isNotEmpty(numStr)) {\n                percentage = Double.parseDouble(numStr) / 100;\n            }\n            if (percentage == 1) {\n                res.set(\"code\", SPJ_AC);\n            } else {\n                res.set(\"percentage\", percentage);\n            }\n            String tmp = output.split(\"points \")[1];\n            res.set(\"errMsg\", tmp.substring(0, Math.min(1024, tmp.length())));\n        } else if (output.startsWith(\"FAIL \")) {\n            res.set(\"code\", SPJ_ERROR);\n            res.set(\"errMsg\", output.split(\"FAIL \")[1]);\n        } else {\n            res.set(\"code\", SPJ_ERROR);\n            res.set(\"errMsg\", output);\n        }\n        return res;\n    }\n\n    private CaseResult handle(JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO, JSONArray judgeResultList)\n            throws SystemException {\n        SandBoxRes sandBoxRes = wrapToSandBoxRes((JSONObject) judgeResultList.get(0));\n        return processResult(sandBoxRes, judgeDTO, judgeGlobalDTO);\n    }\n\n    private CaseResult handleMultiple(JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO, JSONArray judgeResultList) {\n        JSONObject userJudgeResult = (JSONObject) judgeResultList.get(0);\n        SandBoxRes userSandBoxRes = wrapToSandBoxRes(userJudgeResult);\n\n        JSONObject interactiveJudgeResult = (JSONObject) judgeResultList.get(1);\n        SandBoxRes interactiveSandBoxRes = wrapToSandBoxRes(interactiveJudgeResult);\n        return processMultipleResult(userSandBoxRes, interactiveSandBoxRes, judgeDTO, judgeGlobalDTO);\n    }\n\n    private SandBoxRes wrapToSandBoxRes(JSONObject judgeResult) {\n        return SandBoxRes.builder()\n                // 普通评测：如果沙盒运行程序不是 Accepted 可以不获取 stdout\n                .stdout(((JSONObject) judgeResult.get(\"files\")).getStr(\"stdout\"))\n                .stderr(((JSONObject) judgeResult.get(\"files\")).getStr(\"stderr\"))\n                // ns->ms\n                .time(judgeResult.getLong(\"time\") / 1_000_000)\n                // b-->kb\n                .memory(judgeResult.getLong(\"memory\") / 1024)\n                .exitCode(judgeResult.getInt(\"exitStatus\"))\n                .status(judgeResult.getInt(\"status\"))\n                .build();\n    }\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/strategy/DefaultJudge.java",
    "content": "package com.simplefanc.voj.judger.judge.local.strategy;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.common.constants.RunConfig;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.judge.local.SandboxRun;\nimport com.simplefanc.voj.judger.judge.local.pojo.CaseResult;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeCaseDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeGlobalDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.SandBoxRes;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.DigestUtils;\n\nimport java.util.regex.Pattern;\n\n/**\n * @Author: chenfan\n * @Date: 2022/1/2 21:18\n * @Description: 普通评测\n */\n@Component\npublic class DefaultJudge extends AbstractJudge {\n    private final static Pattern EOL_PATTERN = Pattern.compile(\"[^\\\\S\\\\n]+(?=\\\\n)\");\n\n    @Override\n    public JSONArray judgeCase(JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemException {\n        RunConfig runConfig = judgeGlobalDTO.getRunConfig();\n        // 调用安全沙箱使用测试点对程序进行测试\n        return SandboxRun.testCase(\n                parseRunCommand(runConfig, null, null, null),\n                runConfig.getEnvs(),\n                judgeDTO.getTestCaseInputPath(),\n                judgeGlobalDTO.getTestTime(),\n                judgeGlobalDTO.getMaxMemory(),\n                judgeDTO.getMaxOutputSize(),\n                judgeGlobalDTO.getMaxStack(),\n                runConfig.getExeName(),\n                judgeGlobalDTO.getUserFileId(),\n                judgeGlobalDTO.getUserFileSrc());\n    }\n\n    @Override\n    public CaseResult processResult(SandBoxRes sandBoxRes, JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {\n        CaseResult result = new CaseResult();\n        // 如果测试跑题无异常\n        if (sandBoxRes.getStatus().equals(JudgeStatus.STATUS_ACCEPTED.getStatus())) {\n            // 比对题目限制、测试数据\n            success(result, sandBoxRes, judgeDTO, judgeGlobalDTO);\n        } else if (sandBoxRes.getStatus().equals(JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {\n            result.setStatus(JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED.getStatus());\n        } else if (sandBoxRes.getExitCode() != 0) {\n            // STATUS_RUNTIME_ERROR 记录errMsg\n            abort(result, sandBoxRes);\n        } else {\n            result.setStatus(sandBoxRes.getStatus());\n        }\n\n        result.setMemory(sandBoxRes.getMemory());\n        result.setTime(sandBoxRes.getTime());\n\n        // 如果需要获取用户对于该题目的输出\n        if (judgeGlobalDTO.getNeedUserOutputFile()) {\n            result.setOutput(sandBoxRes.getStdout());\n        }\n        return result;\n    }\n\n    private void abort(CaseResult result, SandBoxRes sandBoxRes) {\n        result.setStatus(JudgeStatus.STATUS_RUNTIME_ERROR.getStatus());\n        String errMsg;\n        if (sandBoxRes.getExitCode() < 32) {\n            errMsg = String.format(\"The program return exit status code: %s (%s)\\n\", sandBoxRes.getExitCode(),\n                    SandboxRun.SIGNALS.get(sandBoxRes.getExitCode()));\n        } else {\n            errMsg = String.format(\"The program return exit status code: %s\\n\", sandBoxRes.getExitCode());\n        }\n        result.setErrMsg(errMsg.substring(0, Math.min(1024 * 1024, errMsg.length())));\n    }\n\n\n    private void success(CaseResult result, SandBoxRes sandBoxRes, JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {\n        // 对结果的时间损耗和空间损耗与题目限制做比较，判断是否mle和tle\n        if (sandBoxRes.getTime() > judgeGlobalDTO.getMaxTime()) {\n            result.setStatus(JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED.getStatus());\n        } else if (sandBoxRes.getMemory() > judgeGlobalDTO.getMaxMemory() * 1024) {\n            result.setStatus(JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());\n        } else {\n            // 与原测试数据输出的md5进行对比 AC或者是WA\n            JSONObject testcaseInfo = (JSONObject) ((JSONArray) judgeGlobalDTO.getTestCaseInfo().get(\"testCases\"))\n                    .get(judgeDTO.getTestCaseNum() - 1);\n            result.setStatus(compareOutput(sandBoxRes.getStdout(), judgeGlobalDTO.getRemoveEOLBlank(), testcaseInfo));\n        }\n    }\n\n    /**\n     * 根据评测结果与用户程序输出的字符串MD5进行对比\n     *\n     * @param userOutput\n     * @param isRemoveEOLBlank\n     * @param testcaseInfo\n     * @return\n     */\n    private Integer compareOutput(String userOutput, Boolean isRemoveEOLBlank, JSONObject testcaseInfo) {\n        // 如果当前题目选择默认去掉字符串末位空格\n        if (isRemoveEOLBlank) {\n            String userOutputMd5 = DigestUtils.md5DigestAsHex(rtrim(userOutput).getBytes());\n            if (userOutputMd5.equals(testcaseInfo.getStr(\"EOFStrippedOutputMd5\"))) {\n                return JudgeStatus.STATUS_ACCEPTED.getStatus();\n            }\n            return JudgeStatus.STATUS_WRONG_ANSWER.getStatus();\n        }\n        // 不选择默认去掉文末空格 与原数据进行对比\n        String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.getBytes());\n        if (userOutputMd5.equals(testcaseInfo.getStr(\"outputMd5\"))) {\n            return JudgeStatus.STATUS_ACCEPTED.getStatus();\n        }\n        // 如果不AC, 进行PE判断, 否则为WA\n        userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.replaceAll(\"\\\\s+\", \"\").getBytes());\n        if (userOutputMd5.equals(testcaseInfo.getStr(\"allStrippedOutputMd5\"))) {\n            return JudgeStatus.STATUS_PRESENTATION_ERROR.getStatus();\n        }\n        return JudgeStatus.STATUS_WRONG_ANSWER.getStatus();\n    }\n\n\n    /**\n     * 去除行末尾空白符\n     *\n     * @param value\n     * @return\n     */\n    private String rtrim(String value) {\n        if (value == null) {\n            return null;\n        }\n        return EOL_PATTERN.matcher(StrUtil.trimEnd(value)).replaceAll(\"\");\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/strategy/InteractiveJudge.java",
    "content": "package com.simplefanc.voj.judger.judge.local.strategy;\n\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.common.constants.JudgeDir;\nimport com.simplefanc.voj.judger.common.constants.RunConfig;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.judge.local.SandboxRun;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeCaseDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeGlobalDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.CaseResult;\nimport com.simplefanc.voj.judger.judge.local.pojo.SandBoxRes;\nimport org.springframework.stereotype.Component;\n\nimport java.io.File;\n\n/**\n * @Author: chenfan\n * @Date: 2022/1/2 23:24\n * @Description: 交互评测\n */\n@Component\npublic class InteractiveJudge extends AbstractJudge {\n\n    @Override\n    public JSONArray judgeCase(JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemException {\n        RunConfig runConfig = judgeGlobalDTO.getRunConfig();\n        RunConfig interactiveRunConfig = judgeGlobalDTO.getInteractiveRunConfig();\n\n        // 交互程序的路径\n        String interactiveExeSrc = JudgeDir.INTERACTIVE_WORKPLACE_DIR + File.separator + judgeGlobalDTO.getProblemId()\n                + File.separator + interactiveRunConfig.getExeName();\n\n        String testCaseInputFileName = judgeGlobalDTO.getProblemId() + \"_input\";\n        String testCaseOutputFileName = judgeGlobalDTO.getProblemId() + \"_output\";\n\n        String userOutputFileName = judgeGlobalDTO.getProblemId() + \"_user_output\";\n\n        return SandboxRun.interactTestCase(\n                parseRunCommand(runConfig, null, null, null),\n                runConfig.getEnvs(),\n                runConfig.getExeName(),\n                judgeGlobalDTO.getUserFileId(),\n                judgeGlobalDTO.getUserFileSrc(),\n                judgeGlobalDTO.getTestTime(),\n                judgeGlobalDTO.getMaxMemory(),\n                judgeGlobalDTO.getMaxStack(),\n                judgeDTO.getTestCaseInputPath(),\n                testCaseInputFileName,\n                judgeDTO.getTestCaseOutputPath(),\n                testCaseOutputFileName, userOutputFileName,\n                parseRunCommand(interactiveRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),\n                interactiveRunConfig.getEnvs(),\n                interactiveExeSrc,\n                interactiveRunConfig.getExeName());\n    }\n\n    @Override\n    public CaseResult processMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes,\n                                            JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {\n        CaseResult result = new CaseResult();\n\n        // 记录错误信息\n        StringBuilder errMsg = new StringBuilder();\n\n        int userExitCode = userSandBoxRes.getExitCode();\n        result.setStatus(userSandBoxRes.getStatus());\n        // 如果运行超过题目限制时间，直接TLE\n        if (userSandBoxRes.getTime() > judgeGlobalDTO.getMaxTime()) {\n            result.setStatus(JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED.getStatus());\n        } else if (userSandBoxRes.getMemory() > judgeGlobalDTO.getMaxMemory() * 1024) {\n            // 如果运行超过题目限制空间，直接MLE\n            result.setStatus(JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());\n        } else if ((userExitCode != 0 && userExitCode != 13)\n                || (userExitCode == 13 && interactiveSandBoxRes.getExitCode() == 0)) {\n            // Broken Pipe\n            result.setStatus(JudgeStatus.STATUS_RUNTIME_ERROR.getStatus());\n            if (userExitCode < 32) {\n                errMsg.append(String.format(\"The program return exit status code: %s (%s)\\n\", userExitCode,\n                        SandboxRun.SIGNALS.get(userExitCode)));\n            } else {\n                errMsg.append(String.format(\"The program return exit status code: %s\\n\", userExitCode));\n            }\n        } else {\n            // 根据交互程序的退出状态码及输出进行判断\n            JSONObject interactiveCheckRes = checkInteractiveRes(interactiveSandBoxRes);\n            int code = interactiveCheckRes.getInt(\"code\");\n            if (code == SPJ_WA) {\n                result.setStatus(JudgeStatus.STATUS_WRONG_ANSWER.getStatus());\n            } else if (code == SPJ_AC) {\n                result.setStatus(JudgeStatus.STATUS_ACCEPTED.getStatus());\n            } else if (code == SPJ_PE) {\n                result.setStatus(JudgeStatus.STATUS_PRESENTATION_ERROR.getStatus());\n            } else if (code == SPJ_PC) {\n                result.setStatus(JudgeStatus.STATUS_PARTIAL_ACCEPTED.getStatus());\n                result.setPercentage(interactiveCheckRes.getDouble(\"percentage\"));\n            } else {\n                result.setStatus(JudgeStatus.STATUS_SYSTEM_ERROR.getStatus());\n            }\n\n            String spjErrMsg = interactiveCheckRes.getStr(\"errMsg\");\n            if (StrUtil.isNotEmpty(spjErrMsg)) {\n                errMsg.append(spjErrMsg).append(\" \");\n            }\n            if (interactiveSandBoxRes.getExitCode() != 0 && StrUtil.isNotEmpty(interactiveSandBoxRes.getStderr())) {\n                errMsg.append(String.format(\"Interactive program exited with code: %s\", interactiveSandBoxRes.getExitCode()));\n            }\n        }\n        // kb\n        result.setMemory(userSandBoxRes.getMemory());\n        // ms\n        result.setTime(userSandBoxRes.getTime());\n\n        // 记录该测试点的错误信息\n        if (StrUtil.isNotEmpty(errMsg.toString())) {\n            String str = errMsg.toString();\n            result.setErrMsg(str.substring(0, Math.min(1024 * 1024, str.length())));\n        }\n\n        return result;\n    }\n\n    private JSONObject checkInteractiveRes(SandBoxRes interactiveSandBoxRes) {\n        JSONObject result = new JSONObject();\n\n        int exitCode = interactiveSandBoxRes.getExitCode();\n\n        // 获取跑题用户输出或错误输出\n        if (StrUtil.isNotEmpty(interactiveSandBoxRes.getStderr())) {\n            result.set(\"errMsg\", interactiveSandBoxRes.getStderr());\n        }\n\n        // 如果程序无异常\n        if (interactiveSandBoxRes.getStatus().equals(JudgeStatus.STATUS_ACCEPTED.getStatus())) {\n            if (exitCode == JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n                result.set(\"code\", SPJ_AC);\n            } else {\n                result.set(\"code\", exitCode);\n            }\n        } else if (interactiveSandBoxRes.getStatus().equals(JudgeStatus.STATUS_RUNTIME_ERROR.getStatus())) {\n            if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {\n                result.set(\"code\", exitCode);\n            } else if (exitCode == SPJ_PC) {\n                result.set(\"code\", exitCode);\n                String stdout = interactiveSandBoxRes.getStdout();\n                if (NumberUtil.isNumber(stdout)) {\n                    double percentage = 0.0;\n                    percentage = Double.parseDouble(stdout) / 100;\n                    if (percentage == 1) {\n                        result.set(\"code\", SPJ_AC);\n                    } else {\n                        result.set(\"percentage\", percentage);\n                    }\n                }\n            } else {\n                if (StrUtil.isNotEmpty(interactiveSandBoxRes.getStderr())) {\n                    // 适配testlib.h 根据错误信息前缀判断\n                    return parseTestLibErr(interactiveSandBoxRes.getStderr());\n                } else {\n                    result.set(\"code\", SPJ_ERROR);\n                }\n            }\n        } else {\n            result.set(\"code\", SPJ_ERROR);\n        }\n        return result;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/local/strategy/SpecialJudge.java",
    "content": "package com.simplefanc.voj.judger.judge.local.strategy;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileWriter;\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.common.constants.JudgeDir;\nimport com.simplefanc.voj.judger.common.constants.RunConfig;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.judge.local.SandboxRun;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeCaseDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.JudgeGlobalDTO;\nimport com.simplefanc.voj.judger.judge.local.pojo.CaseResult;\nimport com.simplefanc.voj.judger.judge.local.pojo.SandBoxRes;\nimport org.springframework.stereotype.Component;\n\nimport java.io.File;\n\n/**\n * @Author: chenfan\n * @Date: 2022/1/2 22:23\n * @Description: 特殊判题 支持testlib\n */\n\n@Component\npublic class SpecialJudge extends AbstractJudge {\n\n    @Override\n    public JSONArray judgeCase(JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemException {\n        RunConfig runConfig = judgeGlobalDTO.getRunConfig();\n        // 调用安全沙箱使用测试点对程序进行测试\n        return SandboxRun.testCase(\n                parseRunCommand(runConfig, null, null, null),\n                runConfig.getEnvs(),\n                judgeDTO.getTestCaseInputPath(),\n                judgeGlobalDTO.getTestTime(),\n                judgeGlobalDTO.getMaxMemory(),\n                judgeDTO.getMaxOutputSize(),\n                judgeGlobalDTO.getMaxStack(),\n                runConfig.getExeName(),\n                judgeGlobalDTO.getUserFileId(),\n                judgeGlobalDTO.getUserFileSrc());\n    }\n\n    @Override\n    public CaseResult processResult(SandBoxRes sandBoxRes, JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO)\n            throws SystemException {\n        CaseResult result = new CaseResult();\n        // 如果测试跑题无异常\n        if (sandBoxRes.getStatus().equals(JudgeStatus.STATUS_ACCEPTED.getStatus())) {\n            success(result, sandBoxRes, judgeDTO, judgeGlobalDTO);\n        } else if (sandBoxRes.getStatus().equals(JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {\n            result.setStatus(JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED.getStatus());\n        } else if (sandBoxRes.getExitCode() != 0) {\n            abort(result, sandBoxRes);\n        } else {\n            result.setStatus(sandBoxRes.getStatus());\n        }\n\n        // b\n        result.setMemory(sandBoxRes.getMemory());\n        // ns->ms\n        result.setTime(sandBoxRes.getTime());\n\n        return result;\n    }\n\n    private void abort(CaseResult result, SandBoxRes sandBoxRes) {\n        result.setStatus(JudgeStatus.STATUS_RUNTIME_ERROR.getStatus());\n        if (sandBoxRes.getExitCode() < 32) {\n            result.setErrMsg(String.format(\"The program return exit status code: %s (%s)\\n\", sandBoxRes.getExitCode(),\n                    SandboxRun.SIGNALS.get(sandBoxRes.getExitCode())));\n        } else {\n            result.setErrMsg(String.format(\"The program return exit status code: %s\\n\", sandBoxRes.getExitCode()));\n        }\n    }\n\n    private void success(CaseResult result, SandBoxRes sandBoxRes, JudgeCaseDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemException {\n        // 对结果的时间损耗和空间损耗与题目限制做比较，判断是否mle和tle\n        if (sandBoxRes.getTime() > judgeGlobalDTO.getMaxTime()) {\n            result.setStatus(JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED.getStatus());\n        } else if (sandBoxRes.getMemory() > judgeGlobalDTO.getMaxMemory() * 1024) {\n            result.setStatus(JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());\n        } else {\n            // 对于当前测试样例，用户程序的输出对应生成的文件\n            String userOutputFilePath = judgeGlobalDTO.getRunDir() + File.separator + judgeDTO.getTestCaseNum() + \".out\";\n            FileWriter stdWriter = new FileWriter(userOutputFilePath);\n            stdWriter.write(sandBoxRes.getStdout());\n\n            RunConfig spjRunConfig = judgeGlobalDTO.getSpjRunConfig();\n\n            // 特判程序的路径\n            String spjExeSrc = JudgeDir.SPJ_WORKPLACE_DIR + File.separator + judgeGlobalDTO.getProblemId()\n                    + File.separator + spjRunConfig.getExeName();\n\n            String userOutputFileName = judgeGlobalDTO.getProblemId() + \"_user_output\";\n            String testCaseInputFileName = judgeGlobalDTO.getProblemId() + \"_input\";\n            String testCaseOutputFileName = judgeGlobalDTO.getProblemId() + \"_output\";\n            // 进行spj程序运行比对\n            JSONObject spjResult = spjRunAndCheckResult(\n                    userOutputFilePath,\n                    userOutputFileName,\n                    judgeDTO.getTestCaseInputPath(),\n                    testCaseInputFileName,\n                    judgeDTO.getTestCaseOutputPath(),\n                    testCaseOutputFileName,\n                    spjExeSrc,\n                    spjRunConfig);\n\n            // 删除用户输出文件\n            FileUtil.del(userOutputFilePath);\n\n            int code = spjResult.getInt(\"code\");\n            if (code == SPJ_WA) {\n                result.setStatus(JudgeStatus.STATUS_WRONG_ANSWER.getStatus());\n            } else if (code == SPJ_AC) {\n                result.setStatus(JudgeStatus.STATUS_ACCEPTED.getStatus());\n            } else if (code == SPJ_PE) {\n                result.setStatus(JudgeStatus.STATUS_PRESENTATION_ERROR.getStatus());\n            } else if (code == SPJ_PC) {\n                result.setStatus(JudgeStatus.STATUS_PARTIAL_ACCEPTED.getStatus());\n                result.setPercentage(spjResult.getDouble(\"percentage\"));\n            } else {\n                result.setStatus(JudgeStatus.STATUS_SYSTEM_ERROR.getStatus());\n            }\n\n            String spjErrMsg = spjResult.getStr(\"errMsg\");\n            if (StrUtil.isNotEmpty(spjErrMsg)) {\n                result.setErrMsg(spjErrMsg.substring(0, Math.min(1024 * 1024, spjErrMsg.length())));\n            }\n        }\n    }\n\n    // TODO 参数过多\n    private JSONObject spjRunAndCheckResult(String userOutputFilePath,\n                                            String userOutputFileName,\n                                            String testCaseInputFilePath,\n                                            String testCaseInputFileName,\n                                            String testCaseOutputFilePath,\n                                            String testCaseOutputFileName,\n                                            String spjExeSrc,\n                                            RunConfig spjRunConfig) throws SystemException {\n\n        // 调用安全沙箱运行spj程序\n        JSONArray spjJudgeResultList = SandboxRun.spjCheckResult(\n                parseRunCommand(spjRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),\n                spjRunConfig.getEnvs(),\n                userOutputFilePath,\n                userOutputFileName,\n                testCaseInputFilePath,\n                testCaseInputFileName,\n                testCaseOutputFilePath,\n                testCaseOutputFileName,\n                spjExeSrc,\n                spjRunConfig.getExeName());\n\n        JSONObject result = new JSONObject();\n\n        JSONObject spjJudgeResult = (JSONObject) spjJudgeResultList.get(0);\n\n        // 获取跑题用户输出或错误输出\n        String spjErrOut = ((JSONObject) spjJudgeResult.get(\"files\")).getStr(\"stderr\");\n        String spjStdOut = ((JSONObject) spjJudgeResult.get(\"files\")).getStr(\"stdout\");\n        if (StrUtil.isNotEmpty(spjErrOut)) {\n            result.set(\"errMsg\", spjErrOut);\n        }\n\n        // 退出状态码\n        int exitCode = spjJudgeResult.getInt(\"exitStatus\");\n        // 如果测试跑题无异常\n        if (spjJudgeResult.getInt(\"status\").intValue() == JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n            if (exitCode == JudgeStatus.STATUS_ACCEPTED.getStatus()) {\n                result.set(\"code\", SPJ_AC);\n            } else {\n                result.set(\"code\", exitCode);\n            }\n        } else if (spjJudgeResult.getInt(\"status\").intValue() == JudgeStatus.STATUS_RUNTIME_ERROR.getStatus()) {\n            if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {\n                result.set(\"code\", exitCode);\n            } else if (exitCode == SPJ_PC) {\n                result.set(\"code\", exitCode);\n                if (NumberUtil.isNumber(spjStdOut)) {\n                    double percentage = 0.0;\n                    percentage = Double.parseDouble(spjStdOut) / 100;\n                    if (percentage == 1) {\n                        result.set(\"code\", SPJ_AC);\n                    } else {\n                        result.set(\"percentage\", percentage);\n                    }\n                }\n            } else {\n                if (StrUtil.isNotEmpty(spjErrOut)) {\n                    // 适配testlib.h 根据错误信息前缀判断\n                    return parseTestLibErr(spjErrOut);\n                } else {\n                    result.set(\"code\", SPJ_ERROR);\n                }\n            }\n        } else {\n            result.set(\"code\", SPJ_ERROR);\n        }\n\n        return result;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/RemoteJudgeContext.java",
    "content": "package com.simplefanc.voj.judger.judge.remote;\n\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.pojo.dto.JudgeDTO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.judger.dao.JudgeEntityService;\nimport com.simplefanc.voj.judger.dao.ProblemEntityService;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccountRepository;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.querier.RemoteJudgeQuerier;\nimport com.simplefanc.voj.judger.judge.remote.submitter.RemoteJudgeSubmitter;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\n/**\n * @Author: chenfan\n * @Date: 2022/1/29 13:17\n * @Description:\n */\n@Service\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class RemoteJudgeContext {\n\n    private final RemoteJudgeSubmitter remoteJudgeSubmitter;\n\n    private final RemoteJudgeQuerier remoteJudgeQuerier;\n\n    private final RemoteAccountRepository remoteAccountRepository;\n\n    private final ProblemEntityService problemService;\n\n    private final JudgeEntityService judgeEntityService;\n\n    @Value(\"${voj-judge-server.name}\")\n    private String judgeServerName;\n\n//    @Async // 去掉 异步注解 否则直接返回主服务器 无法实现对判题机的负载均衡\n    public void judge(JudgeDTO toJudge) {\n        String[] source = toJudge.getRemoteJudgeProblem().split(\"-\");\n        // String remoteOj = source[0];\n        RemoteOj remoteOj = RemoteOj.getTypeByName(source[0]);\n        String remoteProblemId = source[1];\n        RemoteAccount account = remoteAccountRepository.getRemoteAccount(remoteOj, toJudge.getUsername(),\n                toJudge.getPassword());\n        final Judge judge = toJudge.getJudge();\n        if (remoteOj == RemoteOj.JSK) {\n            Problem problem = problemService.getById(judge.getPid());\n            remoteProblemId = problem.getInfo();\n        }\n        final SubmissionInfo submissionInfo = SubmissionInfo.builder().remoteOj(remoteOj).remotePid(remoteProblemId)\n                .remoteAccountId(toJudge.getUsername()).submitId(judge.getSubmitId()).uid(judge.getUid())\n                .cid(judge.getCid()).pid(judge.getPid()).language(judge.getLanguage()).userCode(judge.getCode())\n                .serverIp(toJudge.getJudgeServerIp()).serverPort(toJudge.getJudgeServerPort()).build();\n\n        judge.setJudger(judgeServerName);\n        judge.setStatus(JudgeStatus.STATUS_PENDING.getStatus());\n        judgeEntityService.updateById(judge);\n        // 调用远程判题\n        boolean isSubmitOk = remoteJudgeSubmitter.process(submissionInfo, account);\n        if (isSubmitOk) {\n            remoteJudgeQuerier.process(submissionInfo, account);\n        }\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/RemoteOjAware.java",
    "content": "package com.simplefanc.voj.judger.judge.remote;\n\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\n\npublic interface RemoteOjAware {\n\n    RemoteOjInfo getOjInfo();\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/account/RemoteAccount.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.account;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport lombok.Data;\nimport org.apache.http.client.CookieStore;\nimport org.apache.http.client.protocol.HttpClientContext;\nimport org.apache.http.impl.client.BasicCookieStore;\nimport org.apache.http.protocol.BasicHttpContext;\nimport org.apache.http.protocol.HttpContext;\n\nimport java.net.HttpCookie;\nimport java.util.List;\n\n@Data\npublic class RemoteAccount {\n\n    public final String accountId;\n\n    public final String password;\n\n    private final RemoteOj remoteOj;\n\n    private final HttpContext context;\n\n    /**\n     * 远程测评的csrfToken\n     */\n    private String csrfToken;\n\n    private List<HttpCookie> cookies;\n\n    public RemoteAccount(RemoteOj remoteOj, String accountId, String password) {\n        this.remoteOj = remoteOj;\n        this.accountId = accountId;\n        this.password = password;\n        this.context = getNewContext();\n    }\n\n    private HttpContext getNewContext() {\n        CookieStore cookieStore = new BasicCookieStore();\n        HttpContext context = new BasicHttpContext();\n        context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore);\n        return context;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/account/RemoteAccountRepository.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.account;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\npublic class RemoteAccountRepository {\n\n    /**\n     * accountId -> RemoteAccount\n     */\n    private Map<String, RemoteAccount> repo = new HashMap<>();\n\n    public RemoteAccount getRemoteAccount(RemoteOj remoteOj, String username, String password) {\n        final String key = remoteOj.getName() + \"-\" + username;\n        if (!repo.containsKey(key)) {\n            repo.put(key, new RemoteAccount(remoteOj, username, password));\n        }\n        return repo.get(key);\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/AnonymousHttpContextRepository.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\nimport org.apache.http.client.CookieStore;\nimport org.apache.http.client.protocol.HttpClientContext;\nimport org.apache.http.impl.client.BasicCookieStore;\nimport org.apache.http.protocol.BasicHttpContext;\nimport org.apache.http.protocol.HttpContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.Stack;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * HttpContext as reusable resource should be reused as more as possible to:<br>\n * 1. Decrease HttpContext instances created;<br>\n * 2. Avoid remote OJs creating new sessions for each request from Virtual Judge.<br>\n * <p>\n * HttpContext 作为可重用资源应尽可能多地重用： 1. 减少 HttpContext 实例的创建； 2. 避免远程 OJ 为来自 Virtual Judge\n * 的每个请求创建新会话。\n * <p>\n * <p>\n * 匿名 HttpContext 存储库\n *\n * @author chenfan\n */\n@Repository\npublic class AnonymousHttpContextRepository {\n\n    private final static Logger log = LoggerFactory.getLogger(AnonymousHttpContextRepository.class);\n\n    private final static String RESERVED_FLAG = \"trcnkq\";\n\n    private final int MAX_SIZE = 100;\n\n    private Stack<HttpContext> contexts = new Stack<>();\n\n    private ReentrantLock lock = new ReentrantLock();\n\n    public HttpContext acquire() {\n        lock.lock();\n        try {\n            if (contexts.isEmpty()) {\n                return build();\n            } else {\n                return contexts.pop();\n            }\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    public void release(HttpContext context) {\n        if (context.getAttribute(RESERVED_FLAG) != null && !contexts.contains(context)) {\n            lock.lock();\n            try {\n                contexts.push(context);\n                if (contexts.size() > MAX_SIZE) {\n                    slim();\n                }\n            } finally {\n                lock.unlock();\n            }\n        }\n    }\n\n    /**\n     * Decrease the size of contexts to half of MAX_SIZE\n     */\n    private void slim() {\n        log.info(\"Slimming AnonymousHttpContextRepository !\");\n\n        int halfSize = MAX_SIZE / 2;\n        Stack<HttpContext> temp = new Stack<HttpContext>();\n        while (contexts.size() > halfSize) {\n            temp.push(contexts.pop());\n        }\n        contexts.clear();\n        while (!temp.isEmpty()) {\n            contexts.push(temp.pop());\n        }\n    }\n\n    private HttpContext build() {\n        CookieStore cookieStore = new BasicCookieStore();\n        HttpContext context = new BasicHttpContext();\n        context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore);\n        context.setAttribute(RESERVED_FLAG, true);\n        return context;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/CookieUtil.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\nimport org.apache.http.client.CookieStore;\nimport org.apache.http.client.protocol.HttpClientContext;\nimport org.apache.http.cookie.Cookie;\n\npublic class CookieUtil {\n\n    public static String getCookieValue(DedicatedHttpClient client, String name) {\n        String value = null;\n        CookieStore cookieStore = (CookieStore) client.getContext().getAttribute(HttpClientContext.COOKIE_STORE);\n        for (Cookie cookie : cookieStore.getCookies()) {\n            if (cookie.getName().equals(name)) {\n                value = cookie.getValue();\n            }\n        }\n        return value;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/DedicatedHttpClient.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpHost;\nimport org.apache.http.HttpRequest;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.client.ResponseHandler;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.protocol.HttpContext;\n\n/**\n * 专用 Http 客户端\n */\npublic class DedicatedHttpClient {\n\n    protected HttpHost host;\n\n    protected HttpContext context;\n\n    protected String charset;\n\n    /**\n     * CloseableHttpClient\n     */\n    protected HttpClient client;\n\n    protected AnonymousHttpContextRepository contextRepository;\n\n    protected DedicatedHttpClient() {\n    }\n\n    // /////////////////////////////////////////////////////////\n\n    public <T> T execute(final HttpRequest request, final SimpleHttpResponseMapper<T> mapper) {\n        HttpContext _context = context != null ? context : contextRepository.acquire();\n        try {\n            // public <T> T execute(HttpHost target, HttpRequest request,\n            // ResponseHandler<? extends T> responseHandler, HttpContext context)\n            return client.execute(host, request, new ResponseHandler<T>() {\n                @Override\n                public T handleResponse(HttpResponse response) {\n                    try {\n                        SimpleHttpResponse simpleHttpResponse = SimpleHttpResponse.build(response, charset);\n                        return mapper.map(simpleHttpResponse);\n                    } catch (Exception e) {\n                        throw new RuntimeException(e);\n                    }\n                }\n            }, _context);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        } finally {\n            if (context == null && _context != null) {\n                contextRepository.release(_context);\n            }\n        }\n    }\n\n    public SimpleHttpResponse execute(final HttpRequest request, final SimpleHttpResponseValidator... validators) {\n        return execute(request, new SimpleHttpResponseMapper<SimpleHttpResponse>() {\n            @Override\n            public SimpleHttpResponse map(SimpleHttpResponse response) throws Exception {\n                request.setHeader(\"User-Agent\",\n                        \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36\");\n                request.setHeader(\"Accept-Language\", \"en-GB,en;q=0.8\");\n                for (SimpleHttpResponseValidator validator : validators) {\n                    if (validator != null) {\n                        validator.validate(response);\n                    }\n                }\n                return response;\n            }\n        });\n    }\n\n    public SimpleHttpResponse execute(final HttpRequest request) {\n        return execute(request, SimpleHttpResponseValidator.DUMMY_VALIDATOR);\n    }\n\n    public <T> T execute(final HttpRequest request, final ResponseHandler<T> handler) {\n        HttpContext _context = context != null ? context : contextRepository.acquire();\n        try {\n            return client.execute(host, request, handler, _context);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        } finally {\n            if (context == null && _context != null) {\n                contextRepository.release(_context);\n            }\n        }\n    }\n\n    // /////////////////////////////////////////////////////////\n\n    public <T> T get(String url, SimpleHttpResponseMapper<T> mapper) {\n        return execute(new HttpGet(url), mapper);\n    }\n\n    public SimpleHttpResponse get(String url, SimpleHttpResponseValidator... validators) {\n        return execute(new HttpGet(url), validators);\n    }\n\n    public SimpleHttpResponse get(String url) {\n        return get(url, SimpleHttpResponseValidator.DUMMY_VALIDATOR);\n    }\n\n    // /////////////////////////////////////////////////////////\n\n    public <T> T post(String url, SimpleHttpResponseMapper<T> mapper) {\n        return execute(new HttpPost(url), mapper);\n    }\n\n    public SimpleHttpResponse post(String url, SimpleHttpResponseValidator... validators) {\n        return execute(new HttpPost(url), validators);\n    }\n\n    public SimpleHttpResponse post(String url) {\n        return post(url, SimpleHttpResponseValidator.DUMMY_VALIDATOR);\n    }\n\n    // /////////////////////////////////////////////////////////\n\n    public <T> T post(String url, HttpEntity entity, SimpleHttpResponseMapper<T> mapper) {\n        HttpPost post = new HttpPost(url);\n        post.setEntity(entity);\n        return execute(post, mapper);\n    }\n\n    public SimpleHttpResponse post(String url, HttpEntity entity, SimpleHttpResponseValidator... validators) {\n        HttpPost post = new HttpPost(url);\n        post.setEntity(entity);\n        return execute(post, validators);\n    }\n\n    public SimpleHttpResponse post(String url, HttpEntity entity) {\n        return post(url, entity, SimpleHttpResponseValidator.DUMMY_VALIDATOR);\n    }\n\n    // ////////////////////////////////////////////////////////\n\n    public HttpContext getContext() {\n        return context;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/DedicatedHttpClientFactory.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpHost;\nimport org.apache.http.protocol.HttpContext;\nimport org.springframework.stereotype.Component;\n\n@Component\n@RequiredArgsConstructor\npublic class DedicatedHttpClientFactory {\n\n    private final AnonymousHttpContextRepository contextRepository;\n\n    public DedicatedHttpClient build(HttpHost host, HttpContext context, String charset) {\n        DedicatedHttpClient client = new DedicatedHttpClient();\n        client.host = host;\n        client.context = context;\n        client.charset = charset;\n        client.client = HttpClientUtil.getHttpClient();\n        client.contextRepository = contextRepository;\n        return client;\n    }\n\n    public DedicatedHttpClient build(HttpHost host, String charset) {\n        return build(host, null, charset);\n    }\n\n    public DedicatedHttpClient build(HttpHost host, HttpContext context) {\n        return build(host, context, \"UTF-8\");\n    }\n\n    public DedicatedHttpClient build(HttpHost host) {\n        return build(host, null, \"UTF-8\");\n    }\n\n    // private CloseableHttpClient getHttpClinet(\n    // int socketTimeout,\n    // int connectionTimeout,\n    // int maxConnTotal,\n    // int maxConnPerRoute,\n    // String userAgent) throws NoSuchAlgorithmException, KeyStoreException,\n    // KeyManagementException {\n    // SSLContextBuilder contextBuilder = SSLContexts.custom();\n    // contextBuilder.loadTrustMaterial(null, new TrustStrategy() {\n    // @Override\n    // public boolean isTrusted(X509Certificate[] chain, String authType) throws\n    // CertificateException {\n    // //忽略http校验\n    // return true;\n    // }\n    // });\n    // SSLConnectionSocketFactory sslConnectionSocketFactory = new\n    // SSLConnectionSocketFactory(contextBuilder.build(), new String[]{\"SSLv3\", \"TLSv1\",\n    // \"TLSv1.2\"}, null, null);\n    // //注册两种请求形式\n    // Registry<ConnectionSocketFactory> socketFactoryRegistry =\n    // RegistryBuilder.<ConnectionSocketFactory>create()\n    // .register(\"https\", sslConnectionSocketFactory)\n    // .register(\"http\", PlainConnectionSocketFactory.getSocketFactory())\n    // .build();\n    //\n    // PoolingHttpClientConnectionManager cm = new\n    // PoolingHttpClientConnectionManager(socketFactoryRegistry);\n    //\n    // RequestConfig config = RequestConfig.custom()\n    // // 配置服务器响应超时时间(连接上一个url，获取response的返回等待时间)\n    // .setSocketTimeout(socketTimeout)\n    // // 配置客户端连接服务器超时时间\n    // .setConnectTimeout(connectionTimeout)\n    // .build();\n    //\n    // return HttpClients.custom()\n    // // 设置连接管理方式-连接池\n    // .setConnectionManager(cm)\n    // // 最大连接数\n    // .setMaxConnTotal(maxConnTotal)\n    // // 最大并发数\n    // .setMaxConnPerRoute(maxConnPerRoute)\n    // .setUserAgent(userAgent)\n    // // 设置http请求规则\n    // .setDefaultRequestConfig(config)\n    // .build();\n    // }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/HttpBodyValidator.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\nimport cn.hutool.core.lang.Assert;\n\npublic class HttpBodyValidator implements SimpleHttpResponseValidator {\n\n    private String subString;\n\n    private boolean negate;\n\n    public HttpBodyValidator(String subString) {\n        this(subString, false);\n    }\n\n    public HttpBodyValidator(String subString, boolean negate) {\n        this.subString = subString;\n        this.negate = negate;\n    }\n\n    @Override\n    public void validate(SimpleHttpResponse response) throws Exception {\n        try {\n            Assert.isTrue(response.getBody().contains(subString) ^ negate);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/HttpClientUtil.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\nimport com.alibaba.nacos.common.utils.MapUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.http.*;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.entity.UrlEncodedFormEntity;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.config.Registry;\nimport org.apache.http.config.RegistryBuilder;\nimport org.apache.http.conn.ConnectionKeepAliveStrategy;\nimport org.apache.http.conn.socket.ConnectionSocketFactory;\nimport org.apache.http.conn.socket.PlainConnectionSocketFactory;\nimport org.apache.http.conn.ssl.*;\nimport org.apache.http.entity.StringEntity;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClients;\nimport org.apache.http.impl.conn.PoolingHttpClientConnectionManager;\nimport org.apache.http.message.BasicHeaderElementIterator;\nimport org.apache.http.message.BasicNameValuePair;\nimport org.apache.http.protocol.HTTP;\nimport org.apache.http.protocol.HttpContext;\nimport org.apache.http.util.EntityUtils;\n\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLException;\nimport javax.net.ssl.SSLSession;\nimport javax.net.ssl.SSLSocket;\nimport java.io.IOException;\nimport java.security.KeyManagementException;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * https://blog.csdn.net/qq_19642249/article/details/103817546\n */\n@Slf4j(topic = \"voj\")\npublic class HttpClientUtil {\n\n    private static final String _HTTP = \"http\";\n\n    private static final String _HTTPS = \"https\";\n\n    // 配置连接池获取超时时间\n    private static final int CONNECTION_REQUEST_TIMEOUT = 1 * 1000;\n\n    // 配置客户端连接服务器超时时间\n    private static final int CONNECT_TIMEOUT = 3 * 1000;\n\n    // 配置服务器响应超时时间\n    private static final int SOCKET_TIMEOUT = 20 * 1000;\n\n    private static final int MAX_CONN_TOTAL = 20;\n\n    private static final int MAX_CONN_PER_ROUTE = 4;\n\n    // 默认返回null串\n    private static String EMPTY_STR = \"\";\n\n    private static final String USER_AGENT = \"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36\";\n\n    private static SSLConnectionSocketFactory sslConnectionSocketFactory = null;\n\n    // 连接池管理类\n    private static PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = null;\n\n    // 管理Https连接的上下文类\n    private static SSLContextBuilder sslContextBuilder = null;\n\n    static {\n        try {\n            sslContextBuilder = SSLContexts.custom();\n            sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() {\n                @Override\n                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n                    return true;\n                }\n            });\n            SSLContext sslContext = sslContextBuilder.build();\n            sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {\n                @Override\n                public void verify(String host, SSLSocket ssl) throws IOException {\n                }\n\n                @Override\n                public void verify(String host, X509Certificate cert) throws SSLException {\n                }\n\n                @Override\n                public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {\n                }\n\n                @Override\n                public boolean verify(String s, SSLSession sslSession) {\n                    return true;\n                }\n            });\n\n            // 注册两种请求形式\n            Registry<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()\n                    .register(_HTTP, new PlainConnectionSocketFactory()).register(_HTTPS, sslConnectionSocketFactory)\n                    .build();\n            poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registryBuilder);\n            // 最大连接数\n            poolingHttpClientConnectionManager.setMaxTotal(MAX_CONN_TOTAL);\n            // 最大并发数\n            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(MAX_CONN_PER_ROUTE);\n        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * http初始化连接配置\n     */\n    private static RequestConfig getDefaultRequestConfig() {\n        return RequestConfig.custom()\n                /*\n                 * 从连接池中获取连接的超时时间，假设：连接池中已经使用的连接数等于setMaxTotal，新来的线程在等待1*1000\n                 * 后超时，错误内容：org.apache.http.conn.ConnectionPoolTimeoutException: Timeout\n                 * waiting for connection from pool\n                 */\n                .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)\n                /*\n                 * 这定义了通过网络与服务器建立连接的超时时间。\n                 * Httpclient包中通过一个异步线程去创建与服务器的socket连接，这就是该socket连接的超时时间，\n                 * 此处设置为2秒。假设：访问一个IP，192.168.10.100，这个IP不存在或者响应太慢，那么将会返回\n                 * java.net.SocketTimeoutException: connect timed out\n                 */\n                .setConnectTimeout(CONNECT_TIMEOUT)\n                /*\n                 * 指的是连接上一个url，获取response的返回等待时间，假设：url程序中存在阻塞、或者response\n                 * 返回的文件内容太大，在指定的时间内没有读完，则出现 java.net.SocketTimeoutException: Read timed\n                 * out\n                 */\n                .setSocketTimeout(SOCKET_TIMEOUT).build();\n    }\n\n    /**\n     * http初始化keep-Alive配置\n     */\n    public static ConnectionKeepAliveStrategy getKeepAliveStrategy() {\n        return new ConnectionKeepAliveStrategy() {\n            @Override\n            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {\n                HeaderElementIterator it = new BasicHeaderElementIterator(\n                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));\n                while (it.hasNext()) {\n                    HeaderElement he = it.nextElement();\n                    String param = he.getName();\n                    String value = he.getValue();\n                    if (value != null && \"timeout\".equalsIgnoreCase(param)) {\n                        return Long.parseLong(value) * 1000;\n                    }\n                }\n                // 如果没有约定，则默认定义时长为20s\n                return 20 * 1000;\n            }\n        };\n    }\n\n    /**\n     * 从池中获取获取httpclient连接\n     */\n    public static CloseableHttpClient getHttpClient() {\n        return HttpClients.custom()\n                // 设置ssl工厂\n                .setSSLSocketFactory(sslConnectionSocketFactory)\n                // 设置连接管理方式-连接池\n                .setConnectionManager(poolingHttpClientConnectionManager)\n                // 设置http请求规则\n                .setDefaultRequestConfig(getDefaultRequestConfig())\n                // 设置keep-Alive\n//                .setKeepAliveStrategy(getKeepAliveStrategy())\n                .setUserAgent(USER_AGENT).build();\n    }\n\n    /**\n     * post请求——JSON格式\n     */\n    public static String postJSON(String url, String json) {\n\n        HttpPost httpPost = new HttpPost(url);\n        // 解决中文乱码问题\n        StringEntity entity = new StringEntity(json, Consts.UTF_8);\n        entity.setContentType(\"application/json;charset=UTF-8\");\n        httpPost.setEntity(entity);\n        return getResult(httpPost);\n    }\n\n    /**\n     * post请求——form格式\n     */\n    public static String postForm(String url, Map<String, String> params) {\n\n        HttpPost httpPost = new HttpPost(url);\n        // 拼装参数，设置编码格式\n        if (MapUtils.isNotEmpty(params)) {\n            List<NameValuePair> paramList = new ArrayList<>();\n            for (Map.Entry<String, String> stringStringEntry : params.entrySet()) {\n                paramList.add(new BasicNameValuePair(stringStringEntry.getKey(), stringStringEntry.getValue()));\n            }\n            UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(paramList, Consts.UTF_8);\n            httpPost.setEntity(urlEncodedFormEntity);\n        }\n        return getResult(httpPost);\n    }\n\n    /**\n     * 通用版处理http请求\n     */\n    private static String getResult(HttpRequestBase request) {\n\n        /**\n         * 获取httpClient\n         */\n        CloseableHttpClient httpClient = null;\n        try {\n            // 获取httpClient\n            httpClient = getHttpClient();\n        } catch (Exception e) {\n            log.error(\"【新版http】获取httpClient失败:请求地址:{},异常信息：\", request.getURI(), e);\n            throw new RuntimeException(\"获取httpClient失败\");\n        }\n        /**\n         * 发起http请求,并处理响应结果\n         */\n        String resultStr = null;\n        CloseableHttpResponse httpResponse = null;\n        try {\n            // 发起http请求\n            httpResponse = httpClient.execute(request);\n            int statusCode = httpResponse.getStatusLine().getStatusCode();\n            // 响应成功\n            if (statusCode == HttpStatus.SC_OK) {\n                HttpEntity httpResponseEntity = httpResponse.getEntity();\n                resultStr = EntityUtils.toString(httpResponseEntity);\n                log.info(\"【新版http】请求正常,请求地址:{},响应结果:{}\", request.getURI(), resultStr);\n                return resultStr;\n            }\n            // 响应失败，打印http异常信息\n            StringBuffer stringBuffer = new StringBuffer();\n            HeaderIterator headerIterator = httpResponse.headerIterator();\n            while (headerIterator.hasNext()) {\n                stringBuffer.append(\"\\t\" + headerIterator.next());\n            }\n            log.info(\"【新版http】异常信息:请求地址:{},响应状态:{},请求返回结果:{}\", request.getURI(), statusCode, stringBuffer);\n        } catch (Exception e) {\n            log.error(\"【新版http】发生异常:请求地址:{},异常信息：\", request.getURI(), e);\n            throw new RuntimeException(\"http请求失败\");\n        } finally {\n            // 关闭httpResponse\n            if (httpResponse != null) {\n                try {\n                    httpResponse.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        return EMPTY_STR;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/HttpStatusValidator.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\nimport cn.hutool.core.lang.Assert;\nimport org.apache.http.HttpStatus;\n\nimport java.io.IOException;\n\npublic class HttpStatusValidator implements SimpleHttpResponseValidator {\n\n    public static HttpStatusValidator SC_OK = new HttpStatusValidator(HttpStatus.SC_OK);\n\n    public static HttpStatusValidator SC_MOVED_PERMANENTLY = new HttpStatusValidator(HttpStatus.SC_MOVED_PERMANENTLY);\n\n    public static HttpStatusValidator SC_MOVED_TEMPORARILY = new HttpStatusValidator(HttpStatus.SC_MOVED_TEMPORARILY);\n\n    /////////////////////////////////////////////////////////////////\n    private int httpStatusCode;\n\n    public HttpStatusValidator(int httpStatusCode) {\n        super();\n        this.httpStatusCode = httpStatusCode;\n    }\n\n    @Override\n    public void validate(SimpleHttpResponse response) throws IOException {\n        if (response.getStatusCode() != httpStatusCode) {\n            // FileTool.writeFile(response.getStatusCode() + \"-\" + httpStatusCode,\n            // response.getBody());\n        }\n        Assert.isTrue(response.getStatusCode() == httpStatusCode,\n                String.format(\"expected=%s, received=%s\", httpStatusCode, response.getStatusCode()));\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/Mapper.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\npublic interface Mapper<S, T> {\n\n    T map(S value) throws Exception;\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/SimpleHttpResponse.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\nimport org.apache.http.HttpResponse;\nimport org.apache.http.ParseException;\nimport org.apache.http.util.EntityUtils;\n\nimport java.io.IOException;\n\npublic class SimpleHttpResponse {\n\n    private String body;\n\n    private int statusCode;\n\n    private HttpResponse rawResponse;\n\n    public SimpleHttpResponse(String body, int statusCode, HttpResponse rawResponse) {\n        this.body = body;\n        this.statusCode = statusCode;\n        this.rawResponse = rawResponse;\n    }\n\n    public static SimpleHttpResponse build(HttpResponse response, String charset) throws ParseException, IOException {\n        String content = EntityUtils.toString(response.getEntity(), charset);\n        int statusCode = response.getStatusLine().getStatusCode();\n        return new SimpleHttpResponse(content, statusCode, response);\n    }\n\n    public String getBody() {\n        return body;\n    }\n\n    public int getStatusCode() {\n        return statusCode;\n    }\n\n    /**\n     * Note, when SimpleHttpResponse instance is ready, rawResponse has been disposed.\n     * Hence don't read the body of rawResponse.\n     *\n     * @return\n     */\n    public HttpResponse getRawResponse() {\n        return rawResponse;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/SimpleHttpResponseMapper.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\npublic interface SimpleHttpResponseMapper<T> extends Mapper<SimpleHttpResponse, T> {\n\n    T map(SimpleHttpResponse response) throws Exception;\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/SimpleHttpResponseValidator.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\npublic interface SimpleHttpResponseValidator {\n\n    SimpleHttpResponseValidator DUMMY_VALIDATOR = new SimpleHttpResponseValidator() {\n        @Override\n        public void validate(SimpleHttpResponse response) {\n            // Validate nothing. Pass all the time.\n        }\n    };\n\n    ///////////////////////////////////////////////////////////////\n\n    void validate(SimpleHttpResponse response) throws Exception;\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/httpclient/SimpleNameValueEntityFactory.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.httpclient;\n\nimport org.apache.http.NameValuePair;\nimport org.apache.http.client.entity.UrlEncodedFormEntity;\nimport org.apache.http.message.BasicNameValuePair;\n\nimport java.nio.charset.Charset;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SimpleNameValueEntityFactory {\n\n    /**\n     * @param keyValues if size is odd, the last one is charset\n     * @return\n     */\n    public static UrlEncodedFormEntity create(String... keyValues) {\n        List<NameValuePair> nvps = new ArrayList<>();\n        for (int i = 0; i + 1 < keyValues.length; i += 2) {\n            nvps.add(new BasicNameValuePair(keyValues[i], keyValues[i + 1]));\n        }\n        String charset = keyValues.length % 2 == 1 ? keyValues[keyValues.length - 1] : \"UTF-8\";\n        return new UrlEncodedFormEntity(nvps, Charset.forName(charset));\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/loginer/AbstractRetentiveLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.loginer;\n\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\n\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * 保持 登录\n * <p>\n * 未实现 RemoteOjAware.getOjInfo()\n */\npublic abstract class AbstractRetentiveLoginer implements Loginer {\n\n    /**\n     * 记录上次登录时间戳 httpContext hashCode -> last login epoch millisecond\n     */\n    private final static ConcurrentHashMap<Integer, Long> LAST_LOGIN_TIME_MAP = new ConcurrentHashMap<>();\n\n    @Override\n    public final void login(RemoteAccount account) throws Exception {\n        int contextHashCode = account.getContext().hashCode();\n        Long lastLoginTime = LAST_LOGIN_TIME_MAP.get(contextHashCode);\n        // 没有context 或者 超过5分钟 重新登录\n        if (lastLoginTime == null || now() - lastLoginTime > getOjInfo().maxInactiveInterval) {\n            loginEnforce(account);\n            LAST_LOGIN_TIME_MAP.put(contextHashCode, now());\n        }\n    }\n\n    private long now() {\n        return System.currentTimeMillis();\n    }\n\n    protected abstract void loginEnforce(RemoteAccount account) throws Exception;\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/loginer/Loginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.loginer;\n\nimport com.simplefanc.voj.judger.judge.remote.RemoteOjAware;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\n\npublic interface Loginer extends RemoteOjAware {\n\n    void login(RemoteAccount account) throws Exception;\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/loginer/LoginersHolder.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.loginer;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.utils.Tools;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n@Slf4j(topic = \"voj\")\npublic class LoginersHolder {\n\n    private static HashMap<RemoteOj, Loginer> loginers = new HashMap<>();\n\n    public static Loginer getLoginer(RemoteOj remoteOj) {\n        if (!loginers.containsKey(remoteOj)) {\n            synchronized (loginers) {\n                if (!loginers.containsKey(remoteOj)) {\n                    try {\n                        List<Class<? extends Loginer>> loginerClasses = Tools\n                                .findSubClasses(\"com.simplefanc.voj.judger.judge.remote\", Loginer.class);\n                        for (Class<? extends Loginer> loginerClass : loginerClasses) {\n                            Loginer loginer = SpringUtil.getBean(loginerClass);\n                            loginers.put(loginer.getOjInfo().remoteOj, loginer);\n                        }\n                    } catch (Throwable t) {\n                        log.error(\"Get Loginer Failed\", t);\n                    }\n                }\n            }\n        }\n        return loginers.get(remoteOj);\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/pojo/RemoteOjInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.pojo;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport org.apache.http.HttpHost;\n\n/**\n * Once initiated, don't modify it. I don't bother implementing an immutable one.\n *\n * @author chenfan\n */\npublic class RemoteOjInfo {\n\n    public RemoteOj remoteOj;\n\n\n    public HttpHost mainHost;\n\n    public String defaultCharset = \"UTF-8\";\n\n    /**\n     * In milliseconds\n     */\n    public long maxInactiveInterval = 300000L;\n\n    public RemoteOjInfo(RemoteOj remoteOj, HttpHost mainHost) {\n        this.remoteOj = remoteOj;\n        this.mainHost = mainHost;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/pojo/SubmissionInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.pojo;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport lombok.Builder;\n\nimport java.util.Date;\n\n/**\n * info.remoteOj = RemoteOj.POJ; info.remotePid = \"1000\"; info.language = \"4\";\n * info.userCode = \"#include <iostream>\\n\" + \"using namespace std;\\n\" + \"int main()\\n\" +\n * \"{\\n\" + \" int a,b;\\n\" + \" cin >> a >> b;\\n\" + \" cout << a+b << endl;\\n\" + \" return\n * 0;\\n\" + \"}\";\n */\n@Builder\npublic class SubmissionInfo {\n\n    public Long submitId;\n\n    public String uid;\n\n    public Long cid;\n\n    public Long pid;\n\n    public String serverIp;\n\n    public Integer serverPort;\n\n    public RemoteOj remoteOj;\n\n    /**\n     * 如CF的1540C1\n     */\n    public String remotePid;\n\n    public String remoteContestId;\n\n    public String remoteProblemIndex;\n\n    public String language;\n\n    public String userCode;\n\n    /**\n     * leave null if any public remote account is eligible. After submitting, it will set\n     * to the defacto account id. 提交后，它将设置为实际帐户id。\n     */\n    public String remoteAccountId;\n\n    public String remoteRunId;\n\n    public Date remoteSubmitTime;\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/pojo/SubmissionRemoteStatus.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.pojo;\n\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport lombok.Data;\n\nimport java.util.Date;\nimport java.util.List;\n\n@Data\npublic class SubmissionRemoteStatus {\n\n    public JudgeStatus statusType;\n\n    public String rawStatus;\n\n    /**\n     * millisecond\n     */\n    public int executionTime;\n\n    /**\n     * KiloBytes\n     */\n    public int executionMemory;\n\n    public String compilationErrorInfo;\n\n    public int failCase = -1;\n\n    public List<JudgeCase> judgeCaseList;\n\n    public Date queryTime = new Date();\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/atcoder/AtCoderInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.atcoder;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport org.apache.http.HttpHost;\n\npublic class AtCoderInfo {\n\n    public static final RemoteOjInfo INFO = new RemoteOjInfo(RemoteOj.AtCoder,\n            new HttpHost(\"atcoder.jp\", 443, \"https\"));\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/atcoder/AtCoderLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.atcoder;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpRequest;\nimport cn.hutool.http.HttpResponse;\nimport cn.hutool.http.HttpUtil;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.loginer.AbstractRetentiveLoginer;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpStatus;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\n\n@Component\n@RequiredArgsConstructor\npublic class AtCoderLoginer extends AbstractRetentiveLoginer {\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return AtCoderInfo.INFO;\n    }\n\n    @Override\n    protected void loginEnforce(RemoteAccount account) {\n//        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n//        final String body = client.get(\"/\").getBody();\n//        if (body.contains(\"Sign Out\")) {\n//            return;\n//        }\n//        String csrfToken = ReUtil.get(\"var csrfToken = \\\"([\\\\s\\\\S]*?)\\\"\", body, 1);\n//        account.setCsrfToken(csrfToken);\n//        HttpEntity entity = SimpleNameValueEntityFactory.create(\n//                \"csrf_token\", csrfToken,\n//                \"username\", account.getAccountId(),\n//                \"password\", account.getPassword()\n//        );\n////        final HttpPost post = new HttpPost(\"/login\");\n////        post.setEntity(entity);\n////        client.execute(post, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n//        client.post(\"/login\", entity, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n        HttpRequest.getCookieManager().getCookieStore().removeAll();\n        final String body = HttpUtil.createGet(\"https://atcoder.jp/login\").execute().body();\n        String csrfToken = ReUtil.get(\"var csrfToken = \\\"([\\\\s\\\\S]*?)\\\"\", body, 1);\n        final HttpResponse response = HttpUtil.createPost(\"https://atcoder.jp/login\").form(MapUtil.builder(new HashMap<String, Object>())\n                .put(\"username\", account.getAccountId())\n                .put(\"password\", account.getPassword())\n                .put(\"csrf_token\", csrfToken).map()).execute();\n        Assert.isTrue(response.getStatus() == HttpStatus.SC_MOVED_TEMPORARILY,\n                String.format(\"expected=%s, received=%s\", HttpStatus.SC_MOVED_TEMPORARILY, response.getStatus()));\n        account.setCsrfToken(csrfToken);\n        account.setCookies(response.getCookies());\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/atcoder/AtCoderQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.atcoder;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HtmlUtil;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.querier.Querier;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\n@RequiredArgsConstructor\npublic class AtCoderQuerier implements Querier {\n\n    public static final String SUBMISSION_RESULT_URL = \"/contests/%s/submissions/%s\";\n\n    private static final Map<String, JudgeStatus> STATUS_MAP = new HashMap<>() {{\n        put(\"CE\", JudgeStatus.STATUS_COMPILE_ERROR);\n        put(\"RE\", JudgeStatus.STATUS_RUNTIME_ERROR);\n        put(\"QLE\", JudgeStatus.STATUS_RUNTIME_ERROR);\n        put(\"OLE\", JudgeStatus.STATUS_RUNTIME_ERROR);\n        put(\"IE\", JudgeStatus.STATUS_RUNTIME_ERROR);\n        put(\"WA\", JudgeStatus.STATUS_WRONG_ANSWER);\n        put(\"AC\", JudgeStatus.STATUS_ACCEPTED);\n        put(\"TLE\", JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED);\n        put(\"MLE\", JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED);\n        put(\"WJ\", JudgeStatus.STATUS_JUDGING);\n        put(\"WR\", JudgeStatus.STATUS_JUDGING); // Waiting Rejudge\n        put(\"Judging\", JudgeStatus.STATUS_JUDGING); // Waiting Rejudge\n    }};\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return AtCoderInfo.INFO;\n    }\n\n    @Override\n    public SubmissionRemoteStatus query(SubmissionInfo info, RemoteAccount account) {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        SubmissionRemoteStatus status = new SubmissionRemoteStatus();\n        String url = String.format(SUBMISSION_RESULT_URL, info.remoteContestId, info.remoteRunId);\n        String body = client.get(url).getBody();\n        status.rawStatus = ReUtil.get(\"<th>Status</th>[\\\\s\\\\S]*?<td id=\\\"judge-status\\\" class=\\\"[\\\\s\\\\S]*?\\\"><span [\\\\s\\\\S]*?>([\\\\s\\\\S]*?)</span></td>\", body, 1);\n        status.statusType = STATUS_MAP.getOrDefault(status.rawStatus, JudgeStatus.STATUS_JUDGING);\n        if (status.statusType == JudgeStatus.STATUS_JUDGING) {\n            return status;\n        }\n        if (status.statusType == JudgeStatus.STATUS_COMPILE_ERROR) {\n            String ceInfo = ReUtil.get(\"<h4>Compile Error</h4>[\\\\s\\\\S]*?<pre>([\\\\s\\\\S]*?)</pre>\", body, 1);\n            status.compilationErrorInfo = HtmlUtil.unescape(ceInfo);\n            return status;\n        }\n        String time = ReUtil.get(\"<th>Exec Time</th>[\\\\s\\\\S]*?<td [\\\\s\\\\S]*?>([\\\\s\\\\S]*?) ms</td>\", body, 1);\n        String memory = ReUtil.get(\"<th>Memory</th>[\\\\s\\\\S]*?<td [\\\\s\\\\S]*?>([\\\\s\\\\S]*?) KB</td>\", body, 1);\n        status.executionTime = time == null ? 0 : Integer.parseInt(time);\n        status.executionMemory = memory == null ? 0 : Integer.parseInt(memory);\n        return status;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/atcoder/AtCoderSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.atcoder;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpRequest;\nimport cn.hutool.http.HttpResponse;\nimport cn.hutool.http.HttpUtil;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleHttpResponse;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.submitter.Submitter;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpStatus;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n@Component\n@RequiredArgsConstructor\n@Slf4j(topic = \"voj\")\npublic class AtCoderSubmitter implements Submitter {\n\n    public static final String SUBMIT_URL = \"/contests/%s/submit\";\n\n    private static final Map<String, String> LANGUAGE_MAP = new HashMap<>() {\n        {\n            put(\"C (GCC 9.2.1)\", \"4001\");\n            put(\"C (Clang 10.0.0)\", \"4002\");\n            put(\"C++ (GCC 9.2.1)\", \"4003\");\n            put(\"C++ (Clang 10.0.0)\", \"4004\");\n            put(\"Java (OpenJDK 11.0.6)\", \"4005\");\n            put(\"Python (3.8.2)\", \"4006\");\n            put(\"Bash (5.0.11)\", \"4007\");\n            put(\"bc (1.07.1)\", \"4008\");\n            put(\"Awk (GNU Awk 4.1.4)\", \"4009\");\n            put(\"C# (.NET Core 3.1.201)\", \"4010\");\n            put(\"C# (Mono-mcs 6.8.0.105)\", \"4011\");\n            put(\"C# (Mono-csc 3.5.0)\", \"4012\");\n            put(\"Clojure (1.10.1.536)\", \"4013\");\n            put(\"Crystal (0.33.0)\", \"4014\");\n            put(\"D (DMD 2.091.0)\", \"4015\");\n            put(\"D (GDC 9.2.1)\", \"4016\");\n            put(\"D (LDC 1.20.1)\", \"4017\");\n            put(\"Dart (2.7.2)\", \"4018\");\n            put(\"dc (1.4.1)\", \"4019\");\n            put(\"Erlang (22.3)\", \"4020\");\n            put(\"Elixir (1.10.2)\", \"4021\");\n            put(\"F# (.NET Core 3.1.201)\", \"4022\");\n            put(\"F# (Mono 10.2.3)\", \"4023\");\n            put(\"Forth (gforth 0.7.3)\", \"4024\");\n            put(\"Fortran (GNU Fortran 9.2.1)\", \"4025\");\n            put(\"Go (1.14.1)\", \"4026\");\n            put(\"Haskell (GHC 8.8.3)\", \"4027\");\n            put(\"Haxe (4.0.3); js\", \"4028\");\n            put(\"Haxe (4.0.3); Java\", \"4029\");\n            put(\"JavaScript (Node.js 12.16.1)\", \"4030\");\n            put(\"Julia (1.4.0)\", \"4031\");\n            put(\"Kotlin (1.3.71)\", \"4032\");\n            put(\"Lua (Lua 5.3.5)\", \"4033\");\n            put(\"Lua (LuaJIT 2.1.0)\", \"4034\");\n            put(\"Dash (0.5.8)\", \"4035\");\n            put(\"Nim (1.0.6)\", \"4036\");\n            put(\"Objective-C (Clang 10.0.0)\", \"4037\");\n            put(\"Common Lisp (SBCL 2.0.3)\", \"4038\");\n            put(\"OCaml (4.10.0)\", \"4039\");\n            put(\"Octave (5.2.0)\", \"4040\");\n            put(\"Pascal (FPC 3.0.4)\", \"4041\");\n            put(\"Perl (5.26.1)\", \"4042\");\n            put(\"Raku (Rakudo 2020.02.1)\", \"4043\");\n            put(\"PHP (7.4.4)\", \"4044\");\n            put(\"Prolog (SWI-Prolog 8.0.3)\", \"4045\");\n            put(\"PyPy2 (7.3.0)\", \"4046\");\n            put(\"PyPy3 (7.3.0)\", \"4047\");\n            put(\"Racket (7.6)\", \"4048\");\n            put(\"Ruby (2.7.1)\", \"4049\");\n            put(\"Rust (1.42.0)\", \"4050\");\n            put(\"Scala (2.13.1)\", \"4051\");\n            put(\"Java (OpenJDK 1.8.0)\", \"4052\");\n            put(\"Scheme (Gauche 0.9.9)\", \"4053\");\n            put(\"Standard ML (MLton 20130715)\", \"4054\");\n            put(\"Swift (5.2.1)\", \"4055\");\n            put(\"Text (cat 8.28)\", \"4056\");\n            put(\"TypeScript (3.8)\", \"4057\");\n            put(\"Visual Basic (.NET Core 3.1.101)\", \"4058\");\n            put(\"Zsh (5.4.2)\", \"4059\");\n            put(\"COBOL - Fixed (OpenCOBOL 1.1.0)\", \"4060\");\n            put(\"COBOL - Free (OpenCOBOL 1.1.0)\", \"4061\");\n            put(\"Brainfuck (bf 20041219)\", \"4062\");\n            put(\"Ada2012 (GNAT 9.2.1)\", \"4063\");\n            put(\"Unlambda (2.0.0)\", \"4064\");\n            put(\"Cython (0.29.16)\", \"4065\");\n            put(\"Sed (4.4)\", \"4066\");\n            put(\"Vim (8.2.0460)\", \"4067\");\n        }\n    };\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return AtCoderInfo.INFO;\n    }\n\n    protected String getRunId(DedicatedHttpClient client, SubmissionInfo info, String username) {\n        String body = client.get(String.format(\"/contests/%s/submissions?f.Task=%s&f.User=%s\", info.remoteContestId, info.remotePid, username)).getBody();\n        return ReUtil.get(\"<a href=\\\"/contests/\" + info.remoteContestId + \"/submissions/(\\\\d+)\\\">Detail</a>\", body, 1);\n    }\n\n    private String getRunId(SubmissionInfo info, String username) {\n        HttpRequest.getCookieManager().getCookieStore().removeAll();\n        String url = getOjInfo().mainHost + String.format(\"/contests/%s/submissions?f.Task=%s&f.User=%s\", info.remoteContestId, info.remotePid, username);\n        String body = HttpUtil.get(url);\n        return ReUtil.get(\"<a href=\\\"/contests/\" + info.remoteContestId + \"/submissions/(\\\\d+)\\\">Detail</a>\", body, 1);\n    }\n\n    private HttpResponse trySubmit(SubmissionInfo info, RemoteAccount account) {\n        String submitUrl = getOjInfo().mainHost + String.format(SUBMIT_URL, info.remoteContestId);\n        HttpRequest request = HttpUtil.createPost(submitUrl);\n        HttpRequest httpRequest = request.form(MapUtil.builder(new HashMap<String, Object>())\n                .put(\"data.TaskScreenName\", info.remotePid)\n                .put(\"data.LanguageId\", LANGUAGE_MAP.get(info.language))\n                .put(\"sourceCode\", info.userCode)\n                .put(\"csrf_token\", account.getCsrfToken()).map());\n        httpRequest.cookie(account.getCookies());\n        return httpRequest.execute();\n    }\n\n    @Override\n    public void submit(SubmissionInfo info, RemoteAccount account) throws Exception {\n//        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        String[] arr = info.remotePid.split(\"_\");\n        info.remoteContestId = arr[0];\n        info.remoteProblemIndex = arr[1];\n        HttpResponse response = trySubmit(info, account);\n        // 说明被限制提交频率了\n        if (response.getStatus() == HttpStatus.SC_OK) {\n            String timeStr = ReUtil.get(\"Wait for (\\\\d+) second to submit again.\", response.body(), 1);\n            if (timeStr != null) {\n                int time = Integer.parseInt(timeStr);\n                try {\n                    TimeUnit.SECONDS.sleep(time + 1);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n                response = trySubmit(info, account);\n            }\n        }\n\n        if (response.getStatus() != HttpStatus.SC_MOVED_TEMPORARILY) {\n            log.error(\"Submit to AtCoder failed, the response status:{}, It may be that the frequency of submission operation is too fast. Please try later\", response.getStatus());\n            throw new RuntimeException(\"[AtCoder] Failed to Submit, the response status:\" + response.getStatus());\n        }\n\n        // 停留3秒钟后再获取id，之后归还账号，避免提交频率过快\n        try {\n            TimeUnit.SECONDS.sleep(3);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n\n//        info.remoteRunId = getRunId(client, info, account.getAccountId());\n        info.remoteRunId = getRunId(info, account.getAccountId());\n    }\n\n//    @Override\n//    public void submit(SubmissionInfo info, RemoteAccount account) throws Exception {\n//        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n//        String[] arr = info.remotePid.split(\"_\");\n//        info.remoteContestId = arr[0];\n//        info.remoteProblemIndex = arr[1];\n//\n//        SimpleHttpResponse response = trySubmit(client, info, account);\n//        // 说明被限制提交频率了\n//        if (response.getStatusCode() == HttpStatus.SC_OK) {\n//            String timeStr = ReUtil.get(\"Wait for (\\\\d+) second to submit again.\", response.getBody(), 1);\n//            if (timeStr != null) {\n//                int time = Integer.parseInt(timeStr);\n//                try {\n//                    TimeUnit.SECONDS.sleep(time + 1);\n//                } catch (InterruptedException e) {\n//                    e.printStackTrace();\n//                }\n//                response = trySubmit(client, info, account);\n//            }\n//        }\n//\n//        if (response.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {\n//            log.error(\"Submit to AtCoder failed, the response status:{}, It may be that the frequency of submission operation is too fast. Please try later\", response.getStatusCode());\n//            throw new RuntimeException(\"[AtCoder] Failed to Submit, the response status:\" + response.getStatusCode());\n//        }\n//\n//        // 停留3秒钟后再获取id，之后归还账号，避免提交频率过快\n//        try {\n//            TimeUnit.SECONDS.sleep(3);\n//        } catch (InterruptedException e) {\n//            e.printStackTrace();\n//        }\n//\n//        info.remoteRunId = getRunId(client, info, account.getAccountId());\n//    }\n\n    @Deprecated\n    private SimpleHttpResponse trySubmit(DedicatedHttpClient client, SubmissionInfo info, RemoteAccount account) {\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\n                \"data.TaskScreenName\", info.remotePid,\n                \"data.LanguageId\", LANGUAGE_MAP.get(info.language),\n                \"sourceCode\", info.userCode,\n                \"csrf_token\", account.getCsrfToken()\n        );\n        return client.post(String.format(SUBMIT_URL, info.remoteContestId), entity);\n//        HttpPost post = new HttpPost(String.format(\"/contests/%s/submit\", contestId));\n//        post.setEntity(entity);\n//        client.execute(post, new ResponseHandler<Object>() {\n//            @Override\n//            public Object handleResponse(HttpResponse httpResponse) throws ClientProtocolException, IOException {\n//                return null;\n//            }\n//        });\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/codeforcesgym/GYMInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.codeforcesgym;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport org.apache.http.HttpHost;\n\npublic class GYMInfo {\n\n    public static final RemoteOjInfo INFO = new RemoteOjInfo(RemoteOj.GYM,\n            new HttpHost(\"codeforces.com\", 443, \"https\"));\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/codeforcesgym/GYMLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.codeforcesgym;\n\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces.AbstractCFStyleLoginer;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class GYMLoginer extends AbstractCFStyleLoginer {\n\n    public GYMLoginer(DedicatedHttpClientFactory dedicatedHttpClientFactory) {\n        super(dedicatedHttpClientFactory);\n    }\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return GYMInfo.INFO;\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/codeforcesgym/GYMQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.codeforcesgym;\n\nimport cn.hutool.core.lang.PatternPool;\nimport cn.hutool.core.text.UnicodeUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces.AbstractCFStyleQuerier;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n@Component\n@RequiredArgsConstructor\npublic class GYMQuerier extends AbstractCFStyleQuerier {\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n    private static final String SUBMISSION_BY_USERNAME = \"/submissions/%s\";\n    private static final String JUDGE_PROTOCOL = \"/data/judgeProtocol\";\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return GYMInfo.INFO;\n    }\n\n    @Override\n    public SubmissionRemoteStatus query(SubmissionInfo info, RemoteAccount account) {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n\n        String body = client.get(String.format(SUBMISSION_BY_USERNAME, account.accountId)).getBody();\n        String regex = \"<span .*? submissionId=\\\"\" + info.remoteRunId + \"\\\" submissionVerdict=\\\"(.*?)\\\" .*?>.*?</span>.*?<i .*?></i>[\\\\s]*?</td>[\\\\s]*?\" +\n                \"<td class=\\\"time.*?\\\">[\\\\s]*?(\\\\d+)&nbsp;ms[\\\\s]*?</td>[\\\\s]*?\" +\n                \"<td class=\\\"memory.*?\\\">[\\\\s]*?(\\\\d+)&nbsp;KB[\\\\s]*?</td>[\\\\s]*?</tr>\";\n        Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);\n        final Matcher matcher = pattern.matcher(body);\n        SubmissionRemoteStatus status = new SubmissionRemoteStatus();\n        status.statusType = JudgeStatus.STATUS_JUDGING;\n        if (matcher.find()) {\n            String statusStr = matcher.group(1);\n            status.statusType = STATUS_MAP.getOrDefault(statusStr, JudgeStatus.STATUS_JUDGING);\n            if (status.statusType == JudgeStatus.STATUS_JUDGING) {\n                return status;\n            }\n            String timeStr = matcher.group(2);\n            status.executionTime = StrUtil.isEmpty(timeStr) ? 0 : Integer.parseInt(timeStr);\n            String memoryStr = matcher.group(3);\n            status.executionMemory = StrUtil.isEmpty(memoryStr) ? 0 : Integer.parseInt(memoryStr);\n            if (status.statusType == JudgeStatus.STATUS_COMPILE_ERROR) {\n                HttpEntity entity = SimpleNameValueEntityFactory.create(\n                        \"csrf_token\", account.getCsrfToken(),\n                        \"submissionId\", info.remoteRunId\n                );\n                HttpPost post = new HttpPost(JUDGE_PROTOCOL);\n                post.setEntity(entity);\n                String ceInfo = client.execute(post, HttpStatusValidator.SC_OK).getBody();\n                status.compilationErrorInfo = UnicodeUtil.toString(ceInfo).replaceAll(\"(\\\\\\\\r)?\\\\\\\\n\", \"\\n\")\n                        .replaceAll(\"\\\\\\\\\\\\\\\\\", \"\\\\\\\\\");\n            }\n        }\n\n        return status;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/codeforcesgym/GYMSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.codeforcesgym;\n\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces.AbstractCFStyleSubmitter;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class GYMSubmitter extends AbstractCFStyleSubmitter {\n\n    public GYMSubmitter(DedicatedHttpClientFactory dedicatedHttpClientFactory) {\n        super(dedicatedHttpClientFactory);\n    }\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return GYMInfo.INFO;\n    }\n\n    protected String getSubmitUrl(String contestNum) {\n        return \"/gym/\" + contestNum + \"/submit\";\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/codefores/CFInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.codefores;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport org.apache.http.HttpHost;\n\npublic class CFInfo {\n\n    public static final RemoteOjInfo INFO = new RemoteOjInfo(RemoteOj.CF,\n            new HttpHost(\"codeforces.com\", 443, \"https\"));\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/codefores/CFLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.codefores;\n\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces.AbstractCFStyleLoginer;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class CFLoginer extends AbstractCFStyleLoginer {\n\n    public CFLoginer(DedicatedHttpClientFactory dedicatedHttpClientFactory) {\n        super(dedicatedHttpClientFactory);\n    }\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return CFInfo.INFO;\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/codefores/CFQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.codefores;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.http.HttpUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces.AbstractCFStyleQuerier;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\n@Component\n@RequiredArgsConstructor\npublic class CFQuerier extends AbstractCFStyleQuerier {\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n    public static final String HOST = \"https://codeforces.com\";\n    private static final String CE_INFO_URL = \"/data/submitSource\";\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return CFInfo.INFO;\n    }\n\n    @Override\n    public SubmissionRemoteStatus query(SubmissionInfo info, RemoteAccount account) {\n//        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n//        HttpEntity entity = SimpleNameValueEntityFactory.create(\n//                \"csrf_token\", account.getCsrfToken(),\n//                \"submissionId\", info.remoteRunId\n//        );\n//        HttpPost post = new HttpPost(CE_INFO_URL);\n//        post.setEntity(entity);\n//        String body = client.execute(post, HttpStatusValidator.SC_OK).getBody();\n        String homePage = HttpUtil.createGet(HOST).execute().body();\n        String csrfToken = ReUtil.get(\"data-csrf='(\\\\w+)'\", homePage, 1);\n\n        String body = HttpUtil.createPost(HOST + CE_INFO_URL)\n                .header(\"Origin\", HOST)\n                .header(\"Referer\", HOST)\n                .form(MapUtil\n                        .builder(new HashMap<String, Object>())\n                        .put(\"csrf_token\", csrfToken)\n                        .put(\"submissionId\", info.remoteRunId).map()).execute().body();\n\n        SubmissionRemoteStatus status = new SubmissionRemoteStatus();\n\n        JSONObject submissionInfoJson = JSONUtil.parseObj(body);\n        String compilationError = submissionInfoJson.getStr(\"compilationError\");\n        if (\"true\".equals(compilationError)) {\n            status.executionMemory = 0;\n            status.executionTime = 0;\n            status.statusType = JudgeStatus.STATUS_COMPILE_ERROR;\n            String ceMsg = submissionInfoJson.getStr(\"checkerStdoutAndStderr#1\");\n            if (StrUtil.isEmpty(ceMsg)) {\n                status.compilationErrorInfo = \"Oops! Because Codeforces does not provide compilation details, it is unable to provide the reason for compilation failure!\";\n            } else {\n                status.compilationErrorInfo = ceMsg;\n            }\n            return status;\n        }\n        if (\"true\".equals(submissionInfoJson.getStr(\"waiting\"))) {\n            status.statusType = JudgeStatus.STATUS_JUDGING;\n            return status;\n        }\n\n        int maxTime = 0;\n        int maxMemory = 0;\n        List<JudgeCase> judgeCaseList = new ArrayList<>();\n        int testCount = Integer.parseInt(submissionInfoJson.getStr(\"testCount\"));\n        for (int testcaseNum = 1; testcaseNum <= testCount; testcaseNum++) {\n            String verdict = submissionInfoJson.getStr(\"verdict#\" + testcaseNum);\n            if (StrUtil.isEmpty(verdict)) {\n                continue;\n            }\n            JudgeStatus judgeRes = STATUS_MAP.get(verdict);\n            int time = Integer.parseInt(submissionInfoJson.getStr(\"timeConsumed#\" + testcaseNum));\n            int memory = Integer.parseInt(submissionInfoJson.getStr(\"memoryConsumed#\" + testcaseNum)) / 1024;\n            String msg = submissionInfoJson.getStr(\"checkerStdoutAndStderr#\" + testcaseNum);\n\n            judgeCaseList.add(new JudgeCase()\n                    .setSubmitId(info.submitId)\n                    .setPid(info.pid)\n                    .setUid(info.uid)\n                    .setTime(time)\n                    .setMemory(memory)\n                    .setStatus(judgeRes.getStatus())\n                    .setUserOutput(msg));\n            maxTime = Math.max(maxTime, time);\n            maxMemory = Math.max(maxMemory, memory);\n        }\n\n        status.executionMemory = maxMemory;\n        status.executionTime = maxTime;\n        status.judgeCaseList = judgeCaseList;\n        status.statusType = STATUS_MAP.get(submissionInfoJson.getStr(\"verdict#\" + testCount));\n\n        return status;\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/codefores/CFSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.codefores;\n\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces.AbstractCFStyleSubmitter;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class CFSubmitter extends AbstractCFStyleSubmitter {\n\n    public static final String SUBMIT_URL = \"/contest/%s/submit\";\n\n    public CFSubmitter(DedicatedHttpClientFactory dedicatedHttpClientFactory) {\n        super(dedicatedHttpClientFactory);\n    }\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return CFInfo.INFO;\n    }\n\n    protected String getSubmitUrl(String contestNum) {\n        return String.format(SUBMIT_URL, contestNum);\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/eoj/EojInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.eoj;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport org.apache.http.HttpHost;\n\n/**\n * @author chenfan\n * @date 2022/1/29 20:40\n **/\npublic class EojInfo {\n\n    public static final RemoteOjInfo INFO = new RemoteOjInfo(RemoteOj.EOJ,\n            new HttpHost(\"acm.ecnu.edu.cn\", 443, \"https\"));\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/eoj/EojLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.eoj;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.http.HttpUtil;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.loginer.AbstractRetentiveLoginer;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.ClientProtocolException;\nimport org.apache.http.client.ResponseHandler;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\n\n@Component\n@RequiredArgsConstructor\npublic class EojLoginer extends AbstractRetentiveLoginer {\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return EojInfo.INFO;\n    }\n\n    @Override\n    protected void loginEnforce(RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        final String body = client.get(\"/login/\").getBody();\n        if (body.contains(\"logout\")) {\n            return;\n        }\n        String csrfmiddlewaretoken = ReUtil.getGroup1(\"name='csrfmiddlewaretoken' value='([\\\\s\\\\S]*?)'\", body);\n        String publicKey = ReUtil\n                .getGroup1(\"name=\\\"public_key\\\" placeholder=\\\"\\\" type=\\\"hidden\\\" value=\\\"([\\\\s\\\\S]*?)\\\"\", body);\n        final String captcha_0 = ReUtil.getGroup1(\"name=\\\"captcha_0\\\" value=\\\"([\\\\s\\\\S]*?)\\\"\", body);\n        final String ciphertext = HttpUtil.post(\"127.0.0.1:9898/rsa\", MapUtil.builder(new HashMap<String, Object>())\n                .put(\"publicKey\", publicKey).put(\"password\", account.password).build());\n        HttpPost post = new HttpPost(\"/login\");\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\"csrfmiddlewaretoken\", csrfmiddlewaretoken, \"next\",\n                \"/login/\", \"username\", account.accountId, \"password\", ciphertext, \"captcha_0\", captcha_0, \"captcha_1\",\n                getCaptcha(client, \"/captcha/image/\" + captcha_0 + \"/\"), \"public_key\", publicKey);\n        post.setEntity(entity);\n        // post.setHeader(\"\", \"\");\n        // client.execute(post, HttpStatusValidator.SC_MOVED_TEMPORARILY, new\n        // HttpBodyValidator(\"success\"));\n        client.execute(post, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n    }\n\n    private String getCaptcha(DedicatedHttpClient client, String url) throws ClientProtocolException, IOException {\n        HttpGet get = new HttpGet(url);\n        InputStream imgBytes = client.execute(get, new ResponseHandler<InputStream>() {\n            @Override\n            public InputStream handleResponse(HttpResponse response) throws ClientProtocolException, IOException {\n                return response.getEntity().getContent();\n            }\n        });\n        return HttpUtil.post(\"127.0.0.1:9898/ocr\",\n                MapUtil.builder(new HashMap<String, Object>()).put(\"image\", imgBytes).build());\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/hdu/HDUInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.hdu;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport org.apache.http.HttpHost;\n\npublic class HDUInfo {\n\n    public static final RemoteOjInfo INFO = new RemoteOjInfo(\n            RemoteOj.HDU,\n            new HttpHost(\"acm.hdu.edu.cn\")\n    );\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/hdu/HDULoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.hdu;\n\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.loginer.Loginer;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.springframework.stereotype.Component;\n\n@Component\n@RequiredArgsConstructor\npublic class HDULoginer implements Loginer {\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return HDUInfo.INFO;\n    }\n\n    @Override\n    public void login(RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        if (client.get(\"/\").getBody().contains(\"href=\\\"/userloginex.php?action=logout\\\"\")) {\n            return;\n        }\n\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\n                \"username\", account.getAccountId(),\n                \"userpass\", account.getPassword()\n        );\n        client.post(\n                \"/userloginex.php?action=login&cid=0&notice=0\",\n                entity,\n                HttpStatusValidator.SC_MOVED_TEMPORARILY);\n    }\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/hdu/HDUQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.hdu;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ReUtil;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.querier.Querier;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n@Component\n@RequiredArgsConstructor\npublic class HDUQuerier implements Querier {\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return HDUInfo.INFO;\n    }\n\n    @Override\n    public SubmissionRemoteStatus query(SubmissionInfo info, RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, null, getOjInfo().defaultCharset);\n\n        String html = client.get(\"/status.php?first=\" + info.remoteRunId).getBody();\n        Pattern pattern = Pattern.compile(\">\" + info.remoteRunId + \"</td><td>[\\\\s\\\\S]*?</td><td>([\\\\s\\\\S]*?)</td><td>[\\\\s\\\\S]*?</td><td>(\\\\d*?)MS</td><td>(\\\\d*?)K</td>\");\n        Matcher matcher = pattern.matcher(html);\n        Assert.isTrue(matcher.find());\n\n        SubmissionRemoteStatus status = new SubmissionRemoteStatus();\n        status.rawStatus = matcher.group(1).replaceAll(\"<[\\\\s\\\\S]*?>\", \"\").trim();\n        status.statusType = STATUS_MAP.getOrDefault(status.rawStatus, JudgeStatus.STATUS_JUDGING);\n        if (status.statusType == JudgeStatus.STATUS_ACCEPTED) {\n            status.executionTime = Integer.parseInt(matcher.group(2));\n            status.executionMemory = Integer.parseInt(matcher.group(3));\n        } else if (status.statusType == JudgeStatus.STATUS_COMPILE_ERROR) {\n            html = client.get(\"/viewerror.php?rid=\" + info.remoteRunId).getBody();\n            status.compilationErrorInfo = ReUtil.get(\"<pre>([\\\\s\\\\S]*?)</pre>\", html, 1);\n        }\n        return status;\n    }\n\n    private static final Map<String, JudgeStatus> STATUS_MAP = new HashMap<>() {\n        {\n            put(\"Submitted\", JudgeStatus.STATUS_JUDGING);\n            put(\"Accepted\", JudgeStatus.STATUS_ACCEPTED);\n            put(\"Wrong Answer\", JudgeStatus.STATUS_WRONG_ANSWER);\n            put(\"Compilation Error\", JudgeStatus.STATUS_COMPILE_ERROR);\n            put(\"Queuing\", JudgeStatus.STATUS_JUDGING);\n            put(\"Running\", JudgeStatus.STATUS_JUDGING);\n            put(\"Compiling\", JudgeStatus.STATUS_COMPILING);\n            put(\"Runtime Error\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"Time Limit Exceeded\", JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED);\n            put(\"Memory Limit Exceeded\", JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED);\n            put(\"Output Limit Exceeded\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"Presentation Error\", JudgeStatus.STATUS_PRESENTATION_ERROR);\n        }\n    };\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/hdu/HDUSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.hdu;\n\nimport cn.hutool.core.codec.Base64;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.*;\nimport com.simplefanc.voj.judger.judge.remote.submitter.Submitter;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpStatus;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\nimport java.net.URLEncoder;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n@Component\n@RequiredArgsConstructor\npublic class HDUSubmitter implements Submitter {\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return HDUInfo.INFO;\n    }\n\n    protected String getMaxRunId(SubmissionInfo info, DedicatedHttpClient client) {\n        String html = client.get(\"/status.php?user=\" + info.remoteAccountId + \"&pid=\" + info.remotePid).getBody();\n        return ReUtil.get(\"<td height=22px>(\\\\d+)\", html, 1);\n    }\n\n    @Override\n    public void submit(SubmissionInfo info, RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\n                \"_usercode\", Base64.encode(URLEncoder.encode(info.userCode, \"utf-8\").getBytes(\"utf-8\")),\n                \"check\", \"0\",\n                \"language\", LANGUAGE_MAP.get(info.language),\n                \"problemid\", info.remotePid\n        );\n        final HttpPost post = new HttpPost(\"/submit.php?action=submit\");\n        post.setEntity(entity);\n        SimpleHttpResponse response = client.execute(post);\n        // 提交频率限制了 等待5秒再次提交\n        if (response.getStatusCode() == HttpStatus.SC_OK && response.getBody() != null && response.getBody().contains(\"Please don't re-submit\")) {\n            try {\n                TimeUnit.SECONDS.sleep(5);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n            client.execute(post, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n        } else if (response.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {\n            String log = String.format(\"[HDU] [%s]: Failed to submit code, the http response status is [%s].\", info.remotePid, response.getStatusCode());\n            throw new RuntimeException(log);\n        }\n        info.remoteRunId = getMaxRunId(info, client);\n        // 等待2s再次查询，如果还是失败，则表明提交失败了\n        if (StrUtil.isEmpty(info.remoteRunId)) {\n            try {\n                TimeUnit.SECONDS.sleep(2);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n            info.remoteRunId = getMaxRunId(info, client);\n        }\n    }\n\n    private static final Map<String, String> LANGUAGE_MAP = new HashMap<>() {\n        {\n            put(\"G++\", \"0\");\n            put(\"GCC\", \"1\");\n            put(\"C++\", \"2\");\n            put(\"C\", \"3\");\n            put(\"Pascal\", \"4\");\n            put(\"Java\", \"5\");\n            put(\"C#\", \"6\");\n        }\n    };\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/jsk/JSKInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.jsk;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport org.apache.http.HttpHost;\n\npublic class JSKInfo {\n\n    public static final RemoteOjInfo INFO = new RemoteOjInfo(RemoteOj.JSK,\n            new HttpHost(\"www.jisuanke.com\", 443, \"https\"));\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/jsk/JSKLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.jsk;\n\nimport cn.hutool.crypto.SecureUtil;\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.*;\nimport com.simplefanc.voj.judger.judge.remote.loginer.AbstractRetentiveLoginer;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.entity.StringEntity;\nimport org.springframework.stereotype.Component;\n\nimport java.nio.charset.StandardCharsets;\n\n@Component\n@RequiredArgsConstructor\npublic class JSKLoginer extends AbstractRetentiveLoginer {\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return JSKInfo.INFO;\n    }\n\n    @Override\n    protected void loginEnforce(RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        client.get(\"/\");\n\n        HttpPost post = new HttpPost(\"/api/auth/sign-in\");\n        JSONObject json = new JSONObject();\n        json.set(\"account\", account.accountId);\n        json.set(\"password\", SecureUtil.md5(account.password));\n        json.set(\"verification\", \"\");\n        json.set(\"keepOnline\", true);\n        post.setEntity(new StringEntity(json.toString(), StandardCharsets.UTF_8));\n        post.setHeader(\"Content-Type\", \"application/json;charset=UTF-8\");\n        post.setHeader(\"X-XSRF-TOKEN\", CookieUtil.getCookieValue(client, \"XSRF-TOKEN\"));\n        client.execute(post, HttpStatusValidator.SC_OK);\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/jsk/JSKQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.jsk;\n\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport com.simplefanc.voj.judger.judge.remote.querier.Querier;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.http.client.methods.HttpGet;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class JSKQuerier implements Querier {\n\n    /**\n     * 统计计蒜客的所有静态原生状态 --> unkown error 暂时未统计\n     */\n    private static final Map<String, JudgeStatus> STATUS_MAP = new HashMap<>() {\n        {\n            put(\"AC\", JudgeStatus.STATUS_ACCEPTED);\n            put(\"PE\", JudgeStatus.STATUS_PRESENTATION_ERROR);\n            put(\"WA\", JudgeStatus.STATUS_WRONG_ANSWER);\n            put(\"TL\", JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED);\n            put(\"ML\", JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED);\n            put(\"OL\", JudgeStatus.STATUS_OUTPUT_LIMIT_EXCEEDED);\n            put(\"RE\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"RE_SEGV\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"RE_ABRT\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"RE_FPE\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"RE_SYS\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"SF\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"AE\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"CE\", JudgeStatus.STATUS_COMPILE_ERROR);\n            put(\"pending\", JudgeStatus.STATUS_JUDGING);\n            put(\"fail\", JudgeStatus.STATUS_SUBMITTED_FAILED);\n        }\n    };\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return JSKInfo.INFO;\n    }\n\n    @Override\n    public SubmissionRemoteStatus query(SubmissionInfo info, RemoteAccount account) {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        // 执行时间和内存\n        HttpGet get = new HttpGet(\"/api/problem/solve/result?solveResultKey=\" + info.remoteRunId);\n        String result = client.execute(get, HttpStatusValidator.SC_OK).getBody();\n        JSONObject data = JSONUtil.parseObj(result);\n        SubmissionRemoteStatus status = new SubmissionRemoteStatus();\n        status.rawStatus = data.getStr(\"status\");\n        status.statusType = STATUS_MAP.computeIfAbsent(status.rawStatus, k -> {\n            if (k.startsWith(\"RE\")) {\n                return JudgeStatus.STATUS_RUNTIME_ERROR;\n            }\n            return JudgeStatus.STATUS_JUDGING;\n        });\n//        status.statusType = STATUS_MAP.getOrDefault(status.rawStatus, JudgeStatus.STATUS_JUDGING);\n        if (status.statusType != JudgeStatus.STATUS_JUDGING) {\n            status.executionMemory = data.getInt(\"usedMemory\");\n            status.executionTime = data.getInt(\"usedTime\");\n        }\n        if (status.statusType == JudgeStatus.STATUS_COMPILE_ERROR) {\n            // 编译错误信息\n            status.compilationErrorInfo = data.getStr(\"compileError\");\n        }\n        return status;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/jsk/JSKSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.jsk;\n\nimport cn.hutool.json.JSONArray;\nimport cn.hutool.json.JSONObject;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.CookieUtil;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.submitter.Submitter;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.entity.StringEntity;\nimport org.springframework.stereotype.Component;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\n@RequiredArgsConstructor\npublic class JSKSubmitter implements Submitter {\n\n    private static final Map<String, String> FILE_MAP = new HashMap<>() {\n        {\n            put(\"FAILED\", \"Failed\");\n            put(\"c\", \"main.c\");\n            put(\"c_noi\", \"main.c\");\n            put(\"c++\", \"main.cpp\");\n            put(\"c++14\", \"main.cpp\");\n            put(\"c++_noi\", \"main.cpp\");\n            put(\"java\", \"Main.java\");\n            put(\"python\", \"main.py\");\n            put(\"python3\", \"main.py\");\n            put(\"ruby\", \"main.rb\");\n            put(\"blockly\", \"main.bl\");\n            put(\"octave\", \"main.m\");\n            put(\"pascal\", \"main.pas\");\n            put(\"go\", \"main.go\");\n        }\n    };\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return JSKInfo.INFO;\n    }\n\n    @Override\n    public void submit(SubmissionInfo info, RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        HttpPost post = new HttpPost(\"/api/problem/solve/submit\");\n        JSONObject json = getJsonObject(info);\n        post.setEntity(new StringEntity(json.toString(), StandardCharsets.UTF_8));\n        post.setHeader(\"Content-Type\", \"application/json;charset=UTF-8\");\n        post.setHeader(\"X-XSRF-TOKEN\", CookieUtil.getCookieValue(client, \"XSRF-TOKEN\"));\n        String result = client.execute(post, HttpStatusValidator.SC_OK).getBody();\n\n        info.remoteRunId = result.replaceAll(\"\\\"\", \"\");\n    }\n\n    private JSONObject getJsonObject(SubmissionInfo info) {\n        JSONObject json = new JSONObject();\n        json.set(\"problemId\", Integer.parseInt(info.remotePid));\n        json.set(\"language\", info.language);\n        final JSONArray files = new JSONArray();\n        final JSONObject fileObj = new JSONObject();\n        fileObj.set(\"name\", FILE_MAP.get(info.language));\n        fileObj.set(\"code\", info.userCode);\n        fileObj.set(\"locks\", new JSONArray());\n        files.add(fileObj);\n        json.set(\"files\", files);\n        return json;\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/mxt/MXTCaptchaRecognizer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.mxt;\n\nimport cn.hutool.core.img.gif.GifDecoder;\n\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class MXTCaptchaRecognizer {\n\n    private static String[][] digitals = new String[][]{{\"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"................#.............\",\n                    \".............#########........\", \"...........#####.######.......\",\n                    \"...........###.....####.......\", \"..........###.......####......\",\n                    \".........####.......####......\", \"..........##........####......\",\n                    \"....................###.......\", \"...................####.......\",\n                    \"...................###........\", \"..................###.........\",\n                    \"................####..........\", \"...............####...........\",\n                    \"..............####............\", \"............####..............\",\n                    \"...........####...............\", \".........####.................\",\n                    \"........####..................\", \".......###############........\",\n                    \".......##############.........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"................#.............\",\n                    \".............#########........\", \"...........#####..#####.......\",\n                    \"..........####.....####.......\", \"..........###.......####......\",\n                    \".........#..#.......###.......\", \"....................###.......\",\n                    \"...................####.......\", \"..................####........\",\n                    \".............#######..........\", \".............########.........\",\n                    \"............#.....####........\", \"...................####.......\",\n                    \"...................####.......\", \"...................####.......\",\n                    \".......###.........###........\", \".......####.......####........\",\n                    \"........###......####.........\", \"........############..........\",\n                    \"..........########............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..................####........\", \".................#####........\",\n                    \"................######........\", \"...............######.........\",\n                    \"..............###.###.........\", \".............###..###.........\",\n                    \"............####..###.........\", \"............###..####.........\",\n                    \"...........###...####.........\", \"..........###....###..........\",\n                    \".........###.....###..........\", \"........####.....###..........\",\n                    \".......####......###..........\", \".......################.......\",\n                    \".......################.......\", \"................###...........\",\n                    \"................###...........\", \"................###...........\",\n                    \"...............####...........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........#############......\", \"...........############.......\",\n                    \"...........##.................\", \"..........###.................\",\n                    \"..........###.................\", \"..........###.................\",\n                    \"..........##.######...........\", \".........############.........\",\n                    \".........#####....####........\", \".........####......###........\",\n                    \"..........##.......####.......\", \"...................####.......\",\n                    \"...................####.......\", \"...................###........\",\n                    \"........##.........###........\", \".......####.......###.........\",\n                    \"........####.....####.........\", \"........############..........\",\n                    \"..........########............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............########........\", \"............###########.......\",\n                    \"...........####.....####......\", \"..........####......####......\",\n                    \"..........###.................\", \".........###..................\",\n                    \".........###....##............\", \"........####.########.........\",\n                    \"........###.###..#####........\", \"........#####......###........\",\n                    \"........####.......####.......\", \"........####.......####.......\",\n                    \"........###........####.......\", \"........###........###........\",\n                    \"........###........###........\", \"........###.......####........\",\n                    \".........###.....####.........\", \".........###########..........\",\n                    \"..........########............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..........##############......\", \"..........##############......\",\n                    \"....................###.......\", \"...................###........\",\n                    \"..................###.........\", \".................###..........\",\n                    \"................####..........\", \"................###...........\",\n                    \"...............###............\", \"..............####............\",\n                    \"..............###.............\", \".............####.............\",\n                    \".............###..............\", \"............####..............\",\n                    \"............###...............\", \"...........####...............\",\n                    \"...........####...............\", \"...........###................\",\n                    \"...........###................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"................#.............\",\n                    \".............#########........\", \"...........#####..#####.......\",\n                    \"..........####......####......\", \"..........###.......####......\",\n                    \".........####.......####......\", \".........####.......###.......\",\n                    \"..........####.....####.......\", \"..........#####..#####........\",\n                    \"...........#########..........\", \"...........#########..........\",\n                    \".........#####..######........\", \"........####......####........\",\n                    \".......####........####.......\", \".......###.........####.......\",\n                    \".......###.........####.......\", \".......###.........###........\",\n                    \".......####.......####........\", \"........############..........\",\n                    \"..........#########...........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"...............#..............\",\n                    \".............########.........\", \"...........#####.#####........\",\n                    \"..........####.....####.......\", \".........####.......###.......\",\n                    \".........###........###.......\", \".........###........####......\",\n                    \".........###........####......\", \"........####.......####.......\",\n                    \".........###.......####.......\", \".........####.....#####.......\",\n                    \"..........#########.###.......\", \"...........#######.####.......\",\n                    \"...................###........\", \"...................###........\",\n                    \".........#........###.........\", \".......####......####.........\",\n                    \"........###.....####..........\", \"........###########...........\",\n                    \".........########.............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},};\n\n    private static String[][] upper = new String[][]{{\"..............................\",\n            \"..............................\", \"..............................\", \"...............####...........\",\n            \"..............#####...........\", \"..............#####...........\", \".............######...........\",\n            \".............######...........\", \"............###.####..........\", \"............###.####..........\",\n            \"...........###..####..........\", \"...........###..####..........\", \"..........###....###..........\",\n            \"..........###....###..........\", \".........###.....###..........\", \".........############.........\",\n            \"........###......####.........\", \"........##.......####.........\", \".......###.......####.........\",\n            \".......###........###.........\", \"......###.........###.........\", \".....####.........####........\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........###########........\", \"..........#############.......\",\n                    \"..........####......####......\", \"..........###........###......\",\n                    \"..........###........###......\", \"..........###.......####......\",\n                    \".........####.......###.......\", \".........####......###........\",\n                    \".........############.........\", \".........############.........\",\n                    \".........###.......###........\", \".........###.......####.......\",\n                    \"........####........###.......\", \"........####........###.......\",\n                    \"........###........####.......\", \"........###........###........\",\n                    \"........###......#####........\", \"........#############.........\",\n                    \"........###########...........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \".................#............\",\n                    \".............#########........\", \"............###########.......\",\n                    \"...........####.....####......\", \"..........####......####......\",\n                    \"..........###........###......\", \".........####........###......\",\n                    \".........###..................\", \"........####..................\",\n                    \"........####..................\", \"........###...................\",\n                    \"........###...................\", \"........###...................\",\n                    \"........###.........#.........\", \"........###.........###.......\",\n                    \"........####.......###........\", \"........####......####........\",\n                    \".........####....####.........\", \"..........##########..........\",\n                    \"...........#######............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........###########........\", \"..........#############.......\",\n                    \"..........####......####......\", \"..........###........####.....\",\n                    \"..........###........####.....\", \"..........###.........###.....\",\n                    \"..........###.........###.....\", \".........####.........###.....\",\n                    \".........####.........###.....\", \".........###..........###.....\",\n                    \".........###.........####.....\", \".........###.........####.....\",\n                    \".........###.........###......\", \"........####........####......\",\n                    \"........####.......####.......\", \"........###.......####........\",\n                    \"........###......####.........\", \"........############..........\",\n                    \".......###########............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........#############......\", \"..........##############......\",\n                    \"..........####................\", \"..........###.................\",\n                    \"..........###.................\", \"..........###.................\",\n                    \"..........###.................\", \".........####.................\",\n                    \".........###########..........\", \".........###########..........\",\n                    \".........###..................\", \".........###..................\",\n                    \".........###..................\", \"........####..................\",\n                    \"........####..................\", \"........###...................\",\n                    \"........###...................\", \"........#############.........\",\n                    \".......##############.........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........############.......\", \"..........#############.......\",\n                    \"..........####................\", \"..........###.................\",\n                    \"..........###.................\", \"..........###.................\",\n                    \".........####.................\", \".........####.................\",\n                    \".........###########..........\", \".........###########..........\",\n                    \".........###..................\", \".........###..................\",\n                    \"........####..................\", \"........####..................\",\n                    \"........###...................\", \"........###...................\",\n                    \"........###...................\", \"........###...................\",\n                    \".......####...................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \".................#............\",\n                    \"..............#########.......\", \"............############......\",\n                    \"...........####.....####......\", \"...........###.......####.....\",\n                    \"..........###.........###.....\", \".........####.........#.......\",\n                    \".........###..................\", \"........####..................\",\n                    \"........####..................\", \"........####....#########.....\",\n                    \"........###.....########......\", \"........###..........###......\",\n                    \"........###.........####......\", \"........####........####......\",\n                    \"........####........####......\", \"........####.......####.......\",\n                    \".........####.....#####.......\", \"..........#############.......\",\n                    \"...........########.###.......\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........###........###.....\", \"..........####........###.....\",\n                    \"..........####.......####.....\", \"..........###........####.....\",\n                    \"..........###........####.....\", \"..........###........###......\",\n                    \".........####........###......\", \".........####........###......\",\n                    \".........###############......\", \".........###############......\",\n                    \".........###........###.......\", \".........###........###.......\",\n                    \"........####........###.......\", \"........####........###.......\",\n                    \"........###........####.......\", \"........###........####.......\",\n                    \"........###........###........\", \"........###........###........\",\n                    \".......####........###........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........###.......####.....\", \"..........####......####......\",\n                    \"..........####.....####.......\", \"..........####....####........\",\n                    \"..........###....####.........\", \"..........###...####..........\",\n                    \"..........###..####...........\", \".........####..####...........\",\n                    \".........####.######..........\", \".........###.#######..........\",\n                    \".........######..####.........\", \".........#####...####.........\",\n                    \".........####.....###.........\", \"........####......####........\",\n                    \"........####......####........\", \"........###........###........\",\n                    \"........###........####.......\", \"........###........####.......\",\n                    \".......####.........####......\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..........#####..........#####\", \"..........######.........#####\",\n                    \"..........######........######\", \"..........######........######\",\n                    \"..........##.###.......##.###.\", \"..........##.###.......##.###.\",\n                    \".........###.####.....##..###.\", \".........###.####....###.####.\",\n                    \".........###..###....##..####.\", \".........##...###...###..###..\",\n                    \".........##...###...##...###..\", \".........##...####.###...###..\",\n                    \"........###...####.##....###..\", \"........###....######...####..\",\n                    \"........###....#####....####..\", \"........##.....#####....###...\",\n                    \"........##.....####.....###...\", \"........##.....####.....###...\",\n                    \".......###.....###.....####...\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........####........###....\", \"..........#####.......###.....\",\n                    \"..........######......###.....\", \"..........######......###.....\",\n                    \"..........#######.....###.....\", \"..........###.###.....###.....\",\n                    \".........###..###.....###.....\", \".........###..####...###......\",\n                    \".........###...###...###......\", \".........###...####..###......\",\n                    \".........###....###..###......\", \".........###....###..###......\",\n                    \"........###.....####.###......\", \"........###......######.......\",\n                    \"........###......######.......\", \"........###.......#####.......\",\n                    \"........###.......#####.......\", \"........###.......#####.......\",\n                    \".......###.........####.......\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..........###########.........\", \"..........############........\",\n                    \"..........###......####.......\", \"..........###.......###.......\",\n                    \".........####.......###.......\", \".........####.......###.......\",\n                    \".........###.......####.......\", \".........###.......####.......\",\n                    \".........###......####........\", \"........#############.........\",\n                    \"........###########...........\", \"........####..................\",\n                    \"........###...................\", \"........###...................\",\n                    \"........###...................\", \".......####...................\",\n                    \".......####...................\", \".......###....................\",\n                    \".......###....................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \".................#............\",\n                    \"..............########........\", \"............###########.......\",\n                    \"...........####.....####......\", \"..........####.......###......\",\n                    \"..........###........####.....\", \".........####........####.....\",\n                    \".........###.........####.....\", \"........####.........####.....\",\n                    \"........####.........####.....\", \"........###..........####.....\",\n                    \"........###..........####.....\", \"........###..........###......\",\n                    \"........###.........####......\", \"........###.........####......\",\n                    \"........###........####.......\", \"........####......####........\",\n                    \".........####....####.........\", \".........###########..........\",\n                    \"...........#######............\", \".............#####............\",\n                    \"..............###.............\", \"..............#######.........\",\n                    \"...............######.........\", \".................##...........\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........###########........\", \"..........##############......\",\n                    \"..........####......####......\", \"..........###........###......\",\n                    \"..........###........###......\", \"..........###........###......\",\n                    \".........####.......####......\", \".........####.......###.......\",\n                    \".........####.....#####.......\", \".........############.........\",\n                    \".........###########..........\", \".........###.....###..........\",\n                    \"........####.....###..........\", \"........####.....####.........\",\n                    \"........###......####.........\", \"........###.......###.........\",\n                    \"........###.......###.........\", \"........###.......####........\",\n                    \".......####.......####........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"................#.............\",\n                    \".............#########........\", \"...........#####..#####.......\",\n                    \"...........###......####......\", \"..........###.......####......\",\n                    \"..........###........##.......\", \"..........###.................\",\n                    \"..........####................\", \"..........######..............\",\n                    \"...........#########..........\", \".............#########........\",\n                    \"................#######.......\", \"...................####.......\",\n                    \"...................####.......\", \".........#..........###.......\",\n                    \".......####........####.......\", \"........###........###........\",\n                    \"........####......####........\", \".........############.........\",\n                    \"..........#########...........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \".........##############.......\", \"........###############.......\",\n                    \"..............###.............\", \".............####.............\",\n                    \".............####.............\", \".............####.............\",\n                    \".............###..............\", \".............###..............\",\n                    \".............###..............\", \"............####..............\",\n                    \"............####..............\", \"............###...............\",\n                    \"............###...............\", \"............###...............\",\n                    \"............###...............\", \"...........####...............\",\n                    \"...........####...............\", \"...........###................\",\n                    \"...........###................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...........###........###.....\", \"..........####........###.....\",\n                    \"..........####.......###......\", \"..........###........###......\",\n                    \"..........###........###......\", \"..........###........###......\",\n                    \".........####........###......\", \".........####........###......\",\n                    \".........####.......###.......\", \".........###........###.......\",\n                    \".........###........###.......\", \".........###........###.......\",\n                    \"........####........###.......\", \"........####.......###........\",\n                    \"........####.......###........\", \"........####......####........\",\n                    \".........####....####.........\", \".........###########..........\",\n                    \"..........########............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \".........###.........####.....\", \".........###.........###......\",\n                    \".........####.......####......\", \".........####.......###.......\",\n                    \".........####......####.......\", \"..........###......###........\",\n                    \"..........###.....###.........\", \"..........###.....###.........\",\n                    \"..........####...###..........\", \"..........####...###..........\",\n                    \"..........####..###...........\", \"...........###..###...........\",\n                    \"...........###.###............\", \"...........###.###............\",\n                    \"...........######.............\", \"...........######.............\",\n                    \"...........#####..............\", \"............####..............\",\n                    \"............###...............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \".........###.......###.......#\", \".........###......####......##\",\n                    \".........###......####......##\", \".........###......####......##\",\n                    \".........###.....######....###\", \".........####....######....###\",\n                    \".........####....######....##.\", \".........####...#######...###.\",\n                    \".........####...##..###...##..\", \".........####..###..###...##..\",\n                    \"..........###..##...###..###..\", \"..........###..##...###..##...\",\n                    \"..........###.##....###..##...\", \"..........###.##....###.##....\",\n                    \"..........###.##....######....\", \"..........#####.....######....\",\n                    \"..........#####.....#####.....\", \"..........####.......####.....\",\n                    \"..........####.......###......\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \".........####.......####......\", \"..........####.....###........\",\n                    \"..........####....####........\", \"...........####...###.........\",\n                    \"...........####..###..........\", \"............###.###...........\",\n                    \"............######............\", \".............#####............\",\n                    \".............####.............\", \".............####.............\",\n                    \"............######............\", \"...........#######............\",\n                    \"..........####.###............\", \"..........###..####...........\",\n                    \".........###...####...........\", \"........###.....####..........\",\n                    \".......###......####..........\", \"......####.......###..........\",\n                    \".....####........####.........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \".........####........####.....\", \".........####.......####......\",\n                    \"..........####......###.......\", \"..........####.....###........\",\n                    \"...........###....###.........\", \"...........####..####.........\",\n                    \"...........####..###..........\", \"............#######...........\",\n                    \"............######............\", \".............#####............\",\n                    \".............####.............\", \".............###..............\",\n                    \".............###..............\", \".............###..............\",\n                    \".............###..............\", \"............####..............\",\n                    \"............####..............\", \"............###...............\",\n                    \"............###...............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \".........##############.......\", \".........##############.......\",\n                    \"..................####........\", \".................####.........\",\n                    \".................###..........\", \"................###...........\",\n                    \"...............###............\", \"..............####............\",\n                    \".............####.............\", \"............####..............\",\n                    \"...........####...............\", \"...........###................\",\n                    \"..........###.................\", \".........###..................\",\n                    \"........####..................\", \".......####...................\",\n                    \"......####....................\", \"......##############..........\",\n                    \".....###############..........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"}};\n\n    private static String[][] lower = new String[][]{{\"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............####............\", \"...........#########..........\", \"..........####...####.........\",\n            \".........###......###.........\", \"..........##......###.........\", \"..................###.........\",\n            \"............#########.........\", \"..........###########.........\", \"........####......###.........\",\n            \"........###......####.........\", \".......####......###..........\", \".......####.....####..........\",\n            \".......####....#####..........\", \"........####.#######..........\", \".........#######.###..........\",\n            \"..............................\", \"..............................\", \"..............................\",\n            \"..............................\", \"..............................\",},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..........###.................\", \"..........###.................\",\n                    \"..........###.................\", \".........####.................\",\n                    \".........###...##.............\", \".........###.#######..........\",\n                    \".........#####..#####.........\", \".........####....####.........\",\n                    \".........###......###.........\", \"........####......###.........\",\n                    \"........###.......###.........\", \"........###.......###.........\",\n                    \"........###.......###.........\", \"........###......####.........\",\n                    \"........###......###..........\", \".......####.....####..........\",\n                    \".......#####....###...........\", \".......##.########............\",\n                    \".......##.#######.............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \".............###..............\", \"...........########...........\",\n                    \"..........###...####..........\", \".........###.....###..........\",\n                    \"........####.....###..........\", \"........###......##...........\",\n                    \"........###...................\", \".......####...................\",\n                    \".......###....................\", \".......###....................\",\n                    \".......###......###...........\", \".......####.....###...........\",\n                    \"........###....###............\", \"........#########.............\",\n                    \".........#######..............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"...................####.......\", \"...................###........\",\n                    \"...................###........\", \"...................###........\",\n                    \".............####..###........\", \"...........###########........\",\n                    \"..........###...#####.........\", \".........###.....####.........\",\n                    \"........####......###.........\", \"........###.......###.........\",\n                    \"........###.......###.........\", \".......####......####.........\",\n                    \".......####......###..........\", \".......####......###..........\",\n                    \".......####......###..........\", \".......####.....####..........\",\n                    \"........###....#####..........\", \"........########.##...........\",\n                    \".........######.###...........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............###.............\", \"...........########...........\",\n                    \"..........####...###..........\", \".........####.....###.........\",\n                    \"........####......###.........\", \"........###.......###.........\",\n                    \"........#############.........\", \".......##############.........\",\n                    \".......####...................\", \".......####...................\",\n                    \".......####.....#.............\", \".......####......###..........\",\n                    \"........###.....###...........\", \"........##########............\",\n                    \".........########.............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"...............#..............\",\n                    \"............#######...........\", \"...........####..#............\",\n                    \"...........###................\", \"...........###................\",\n                    \"..........####................\", \"........#########.............\",\n                    \".......#########..............\", \"..........###.................\",\n                    \"..........###.................\", \".........####.................\",\n                    \".........###..................\", \".........###..................\",\n                    \".........###..................\", \".........###..................\",\n                    \".........###..................\", \"........####..................\",\n                    \"........###...................\", \"........###...................\",\n                    \"........###...................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..................####........\",\n                    \".............#######..........\", \"..........#########...........\",\n                    \".........####..####...........\", \".........###....####..........\",\n                    \"........###......###..........\", \"........###.....####..........\",\n                    \"........###.....###...........\", \"........####...####...........\",\n                    \".........#########............\", \"........########..............\",\n                    \".......###....................\", \".......###....................\",\n                    \".......####...................\", \".......############...........\",\n                    \"......##############..........\", \".....####.......####..........\",\n                    \".....###.........###..........\", \".....###........###...........\",\n                    \".....######...#####...........\", \"......###########.............\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..........####................\", \"..........###.................\",\n                    \"..........###.................\", \"..........###.................\",\n                    \"..........###..###............\", \".........###########..........\",\n                    \".........###.##..####.........\", \".........####....####.........\",\n                    \".........####.....###.........\", \".........###......###.........\",\n                    \"........####.....####.........\", \"........###......###..........\",\n                    \"........###......###..........\", \"........###......###..........\",\n                    \"........###......###..........\", \"........###......###..........\",\n                    \".......####.....####..........\", \".......###......###...........\",\n                    \".......###......###...........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..........####................\", \"..........####................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..........###.................\",\n                    \".........####.................\", \".........###..................\",\n                    \".........###..................\", \".........###..................\",\n                    \".........###..................\", \".........###..................\",\n                    \"........####..................\", \"........###...................\",\n                    \"........###...................\", \"........###...................\",\n                    \"........###...................\", \"........###...................\",\n                    \".......####...................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..........####................\", \"..........###.................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \".........###..................\",\n                    \".........###..................\", \".........###..................\",\n                    \".........###..................\", \"........####..................\",\n                    \"........###...................\", \"........###...................\",\n                    \"........###...................\", \"........###...................\",\n                    \"........###...................\", \".......####...................\",\n                    \".......###....................\", \".......###....................\",\n                    \".......###....................\", \".......###....................\",\n                    \"......####....................\", \"......###.....................\",\n                    \"..#######.....................\", \"..#####.......................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..........####................\", \"..........###.................\",\n                    \"..........###.................\", \"..........###.................\",\n                    \"..........###.................\", \".........####.....####........\",\n                    \".........###.....###..........\", \".........###...####...........\",\n                    \".........###..####............\", \".........###.####.............\",\n                    \".........########.............\", \"........#########.............\",\n                    \"........#####.###.............\", \"........####...###............\",\n                    \"........###....###............\", \"........###....###............\",\n                    \"........###.....###...........\", \".......####.....###...........\",\n                    \".......###......####..........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"...............##......###....\", \".........###.######..#######..\",\n                    \".........#####..#######..####.\", \".........####....#####...####.\",\n                    \".........###.....####.....###.\", \".........###.....###......###.\",\n                    \"........####.....###.....####.\", \"........###......###.....###..\",\n                    \"........###......###.....###..\", \"........###.....####.....###..\",\n                    \"........###.....###......###..\", \"........###.....###.....####..\",\n                    \".......####.....###.....####..\", \".......###......###.....###...\",\n                    \".......###.....####.....###...\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"...............###............\", \".........###.#######..........\",\n                    \".........######..####.........\", \".........####.....###.........\",\n                    \".........####.....###.........\", \".........###......###.........\",\n                    \"........####.....####.........\", \"........###......####.........\",\n                    \"........###......###..........\", \"........###......###..........\",\n                    \"........###......###..........\", \"........###......###..........\",\n                    \".......####.....####..........\", \".......###......###...........\",\n                    \".......###......###...........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"...............###............\", \"..........##########..........\",\n                    \".........###.##.#####.........\", \".........#####....###.........\",\n                    \".........####.....####........\", \".........###......####........\",\n                    \".........###......####........\", \"........####......####........\",\n                    \"........####......###.........\", \"........###.......###.........\",\n                    \"........###......####.........\", \"........####.....###..........\",\n                    \"........####....####..........\", \".......############...........\",\n                    \".......###.######.............\", \".......###....................\",\n                    \".......###....................\", \".......###....................\",\n                    \".......###....................\", \"......####....................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \".............###..............\", \"...........#######.###........\",\n                    \"..........####..#####.........\", \".........####....####.........\",\n                    \"........####.....####.........\", \"........###.......###.........\",\n                    \"........###......####.........\", \".......####......####.........\",\n                    \".......####......###..........\", \".......####......###..........\",\n                    \".......####.....####..........\", \".......####.....####..........\",\n                    \"........###....#####..........\", \"........###########...........\",\n                    \".........######.###...........\", \"................###...........\",\n                    \"................###...........\", \"................###...........\",\n                    \"...............####...........\", \"...............###............\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"...............###............\", \".........###.####.............\",\n                    \".........########.............\", \".........#####................\",\n                    \".........####.................\", \".........###..................\",\n                    \".........###..................\", \"........####..................\",\n                    \"........###...................\", \"........###...................\",\n                    \"........###...................\", \"........###...................\",\n                    \".......####...................\", \".......###....................\",\n                    \".......###....................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \".............###..............\", \"...........########...........\",\n                    \".........####...####..........\", \".........###.....####.........\",\n                    \".........###.....#............\", \"........####..................\",\n                    \".........######...............\", \".........##########...........\",\n                    \"............########..........\", \"................####..........\",\n                    \".................###..........\", \".......###.......###..........\",\n                    \".......####.....###...........\", \"........###########...........\",\n                    \".........########.............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"...........###................\",\n                    \"..........####................\", \"..........###.................\",\n                    \"..........###.................\", \".......#########..............\",\n                    \"..........###.................\", \"..........###.................\",\n                    \".........####.................\", \".........###..................\",\n                    \".........###..................\", \".........###..................\",\n                    \".........###..................\", \".........###..................\",\n                    \"........####..................\", \"........###...................\",\n                    \"........####..................\", \".........###.##...............\",\n                    \".........######...............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \".........####.....####........\",\n                    \".........###......###.........\", \".........###......###.........\",\n                    \".........###......###.........\", \".........###......###.........\",\n                    \"........####.....####.........\", \"........###......###..........\",\n                    \"........###......###..........\", \"........###......###..........\",\n                    \"........###.....####..........\", \"........###.....####..........\",\n                    \"........###....#####..........\", \"........###########...........\",\n                    \".........######.###...........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"........###.......###.........\",\n                    \"........###......###..........\", \"........###......##...........\",\n                    \"........####....###...........\", \"........####...###............\",\n                    \".........###...###............\", \".........###..###.............\",\n                    \".........###..###.............\", \".........#######..............\",\n                    \".........#######..............\", \"..........#####...............\",\n                    \"..........#####...............\", \"..........####................\",\n                    \"..........####................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"........###.....###.....###...\",\n                    \"........###....####....###....\", \"........###....####....##.....\",\n                    \"........###...#####...###.....\", \"........###...#####...##......\",\n                    \"........###..######..###......\", \"........####.######..###......\",\n                    \".........######.###.###.......\", \".........######..######.......\",\n                    \".........#####...#####........\", \".........#####...#####........\",\n                    \".........####....####.........\", \".........####....####.........\",\n                    \".........###.....###..........\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"........####....####..........\",\n                    \".........###...####...........\", \".........####..###............\",\n                    \"..........###.###.............\", \"..........######..............\",\n                    \"...........####...............\", \"...........####...............\",\n                    \"...........####...............\", \"..........######..............\",\n                    \".........###.###..............\", \"........###..####.............\",\n                    \".......###....###.............\", \"......####....####............\",\n                    \".....####.....####............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"........###......###..........\",\n                    \"........###......##...........\", \"........###.....###...........\",\n                    \"........####....##............\", \"........####...###............\",\n                    \".........###..###.............\", \".........###..###.............\",\n                    \".........###.###..............\", \".........###.###..............\",\n                    \".........######...............\", \".........######...............\",\n                    \"..........####................\", \"..........####................\",\n                    \"..........###.................\", \"..........###.................\",\n                    \".........###..................\", \"........###...................\",\n                    \".....######...................\", \".....####.....................\"},\n            {\"..............................\", \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \".........###########..........\",\n                    \".........##########...........\", \"...............###............\",\n                    \"..............###.............\", \".............###..............\",\n                    \"............####..............\", \"...........####...............\",\n                    \"..........####................\", \".........####.................\",\n                    \".........###..................\", \"........###...................\",\n                    \".......###....................\", \"......############............\",\n                    \"......###########.............\", \"..............................\",\n                    \"..............................\", \"..............................\",\n                    \"..............................\", \"..............................\"}\n\n    };\n\n    /**\n     * @param colorInt 像素点的RGB值\n     * @return\n     */\n    private static boolean isBlack(int colorInt) {\n        Color color = new Color(colorInt);\n        if (color.getRed() + color.getGreen() + color.getBlue() <= 500) {\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * @param image 需要打印的图像\n     * @throws IOException\n     */\n    private static void printImage(BufferedImage image) {\n        int h = image.getHeight();\n        int w = image.getWidth();\n\n        // 矩阵打印\n        for (int y = 0; y < h; y++) {\n            System.out.printf(\"\\\"\");\n            for (int x = 0; x < w; x++) {\n                if (isBlack(image.getRGB(x, y))) {\n                    System.out.print(\"#\");\n                } else {\n                    System.out.print(\".\");\n                }\n            }\n            System.out.printf(\"%s\", y == h - 1 ? \"\\\"\" : \"\\\",\");\n            System.out.println();\n        }\n    }\n\n    /**\n     * @param file 需要被分割的gif文件\n     * @throws Exception\n     */\n    private static List<BufferedImage> splitGif(File file) throws IOException {\n        InputStream in = new FileInputStream(file);\n        GifDecoder d = new GifDecoder();\n        d.read(in);\n        int num = d.getFrameCount();\n        List<BufferedImage> subImgs = new ArrayList<>();\n        for (int i = 0; i < num; i++) {\n            // 分割每一帧图片，进行识别\n            // 3 2 1 0\n            BufferedImage frame = d.getFrame(num - i - 1);\n            // printImage(frame);\n            subImgs.add(frame.getSubimage(7 + i * 36, 5, 30, 27));\n        }\n        in.close();\n        return subImgs;\n    }\n\n    /**\n     * @param image 待识别的符号图片\n     * @return\n     */\n    private static char recognizeSymbol(BufferedImage image) {\n        int h = image.getHeight();\n        int w = image.getWidth();\n\n        int minDiff = 999999;\n        char symAns = 0;\n        // 对于某个给定数值\n        for (int i = 0; i < 10; i++) {\n            int curDiff = 0;\n            for (int y = 0; y < h; y++) {\n                for (int x = 0; x < w; x++) {\n                    boolean pixel1 = digitals[i][y].charAt(x) == '#';\n                    boolean pixel2 = isBlack(image.getRGB(x, y));\n                    if (pixel1 != pixel2) {\n                        ++curDiff;\n                    }\n                }\n            }\n            if (curDiff < minDiff) {\n                minDiff = curDiff;\n                symAns = (char) ('0' + i);\n            }\n            if (minDiff < 5) {\n                return symAns;\n            }\n        }\n\n        // 对于某个给定大写字母\n        for (int i = 0; i < 26; i++) {\n            int curDiff = 0;\n            for (int y = 0; y < h; y++) {\n                for (int x = 0; x < w; x++) {\n                    boolean pixel1 = upper[i][y].charAt(x) == '#';\n                    boolean pixel2 = isBlack(image.getRGB(x, y));\n                    if (pixel1 != pixel2) {\n                        ++curDiff;\n                    }\n                }\n            }\n            if (curDiff < minDiff) {\n                minDiff = curDiff;\n                symAns = (char) ('A' + i);\n            }\n            if (minDiff < 5) {\n                return symAns;\n            }\n        }\n\n        // 对于某个给定小写字母\n        for (int i = 0; i < 26; i++) {\n            int curDiff = 0;\n            for (int y = 0; y < h; y++) {\n                for (int x = 0; x < w; x++) {\n                    boolean pixel1 = lower[i][y].charAt(x) == '#';\n                    boolean pixel2 = isBlack(image.getRGB(x, y));\n                    if (pixel1 != pixel2) {\n                        ++curDiff;\n                    }\n                }\n            }\n            if (curDiff < minDiff) {\n                minDiff = curDiff;\n                symAns = (char) ('a' + i);\n            }\n            if (minDiff < 5) {\n                return symAns;\n            }\n        }\n\n        return symAns;\n    }\n\n    /**\n     * @param file 待识别的验证码\n     * @return\n     */\n    public static String recognize(File file) throws IOException {\n        StringBuilder ans = new StringBuilder();\n\n        List<BufferedImage> subImgs = splitGif(file);\n        for (BufferedImage subImg : subImgs) {\n            ans.append(recognizeSymbol(subImg));\n        }\n        return ans.toString();\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/mxt/MXTInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.mxt;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport org.apache.http.HttpHost;\n\npublic class MXTInfo {\n\n    public static final RemoteOjInfo INFO = new RemoteOjInfo(RemoteOj.MXT,\n            new HttpHost(\"mxt.cn\", 443, \"https\"));\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/mxt/MXTLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.mxt;\n\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.loginer.AbstractRetentiveLoginer;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.ClientProtocolException;\nimport org.apache.http.client.ResponseHandler;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\n@Component\n@RequiredArgsConstructor\npublic class MXTLoginer extends AbstractRetentiveLoginer {\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return MXTInfo.INFO;\n    }\n\n    private String getCaptcha(DedicatedHttpClient client) throws ClientProtocolException, IOException {\n        HttpGet get = new HttpGet(\"/code\");\n        File gif = client.execute(get, new ResponseHandler<File>() {\n            @Override\n            public File handleResponse(HttpResponse response) throws ClientProtocolException, IOException {\n                File captchaGif = File.createTempFile(\"mxt\", \".gif\");\n                try (FileOutputStream fos = new FileOutputStream(captchaGif)) {\n                    response.getEntity().writeTo(fos);\n                    return captchaGif;\n                }\n            }\n        });\n        return MXTCaptchaRecognizer.recognize(gif);\n    }\n\n    @Override\n    protected void loginEnforce(RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        // 获得SHRIOSESSIONID\n        client.get(\"/login\");\n        if (client.execute(new HttpPost(\"/islogin\"), HttpStatusValidator.SC_OK).getBody().contains(\"1\")) {\n            return;\n        }\n        final String accessJson = client.post(\"/access.json\").getBody();\n        final String publicKey = JSONUtil.parseObj(accessJson).getStr(\"msg\");\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\n                \"username\", account.getAccountId(),\n                \"password\", RsaUtil.encrypt(account.getPassword(), publicKey),\n                \"valiCode\", getCaptcha(client));\n\n        HttpPost post = new HttpPost(\"/login\");\n        post.setEntity(entity);\n        client.execute(post, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/mxt/MXTQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.mxt;\n\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.querier.Querier;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\n@RequiredArgsConstructor\npublic class MXTQuerier implements Querier {\n\n    private static final Map<Integer, JudgeStatus> STATUS_MAP = new HashMap<>() {\n        {\n            put(0, JudgeStatus.STATUS_JUDGING);\n            put(1, JudgeStatus.STATUS_JUDGING);\n            put(2, JudgeStatus.STATUS_COMPILING);\n            put(3, JudgeStatus.STATUS_JUDGING);\n            put(4, JudgeStatus.STATUS_ACCEPTED);\n            put(5, JudgeStatus.STATUS_PRESENTATION_ERROR);\n            put(6, JudgeStatus.STATUS_WRONG_ANSWER);\n            put(7, JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED);\n            put(8, JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED);\n            put(9, JudgeStatus.STATUS_OUTPUT_LIMIT_EXCEEDED);\n            put(10, JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(11, JudgeStatus.STATUS_COMPILE_ERROR);\n            put(12, JudgeStatus.STATUS_JUDGING);\n            put(13, JudgeStatus.STATUS_JUDGING);\n            put(14, JudgeStatus.STATUS_JUDGING);\n        }\n    };\n\n    private static final String[] STATUS_ARRAY = new String[]{\n            // 0\n            \"Pending\",\n            // 1\n            \"Pending Rejudging\",\n            // 2\n            \"Compiling\",\n            // 3\n            \"Running & Judging\",\n            // 4\n            \"Accepted\",\n            // 5\n            \"Presentation Error\",\n            // 6\n            \"Wrong Answer\",\n            // 7\n            \"Time Limit Exceed\",\n            // 8\n            \"Memory Limit Exceed\",\n            // 9\n            \"Output Limit Exceed\",\n            // 10\n            \"Runtime Error\",\n            // 11\n            \"Compile Error\",\n            // 12\n            \"Compile OK\",\n            // 13\n            \"Test Running Done\",\n            // 14\n            \"Read\"};\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return MXTInfo.INFO;\n    }\n\n    @Override\n    public SubmissionRemoteStatus query(SubmissionInfo info, RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        HttpPost post = new HttpPost(\"/submit/solution/\" + info.remoteRunId + \"/\");\n        String body = client.execute(post, HttpStatusValidator.SC_OK).getBody();\n        final JSONObject jsonObject = JSONUtil.parseObj(body);\n        SubmissionRemoteStatus status = new SubmissionRemoteStatus();\n        Integer result = jsonObject.getInt(\"result\");\n        status.rawStatus = STATUS_ARRAY[result];\n        // 3ms\n        status.executionTime = jsonObject.getInt(\"time\");\n        // 1156B = 1.13KB\n        status.executionMemory = jsonObject.getInt(\"memory\") / 1024;\n        // 从原生状态映射到统一状态\n        status.statusType = STATUS_MAP.getOrDefault(result, JudgeStatus.STATUS_JUDGING);\n        if (status.statusType == JudgeStatus.STATUS_COMPILE_ERROR) {\n            String compileinfo = client.post(\"/compileinfo/\" + info.remoteRunId + \"/\").getBody();\n            status.compilationErrorInfo = JSONUtil.parseObj(compileinfo).getStr(\"error\");\n        }\n        status.failCase = (int) (Double.parseDouble(jsonObject.getStr(\"pass_rate\")) * 100);\n        return status;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/mxt/MXTSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.mxt;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.submitter.Submitter;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n@Component\n@RequiredArgsConstructor\npublic class MXTSubmitter implements Submitter {\n\n    private static final Map<String, String> LANGUAGE_MAP = new HashMap<String, String>() {\n        {\n            put(\"C\", \"0\");\n            put(\"C++\", \"1\");\n            put(\"Pascal\", \"2\");\n            put(\"Java\", \"3\");\n            put(\"Ruby\", \"4\");\n            put(\"Bash\", \"5\");\n            put(\"Python\", \"6\");\n            put(\"PHP\", \"7\");\n            put(\"Perl\", \"8\");\n            put(\"C#\", \"9\");\n            put(\"Obj-C\", \"10\");\n            put(\"FreeBasic\", \"11\");\n            put(\"Scheme\", \"12\");\n            put(\"Clang\", \"13\");\n            put(\"Clang++\", \"14\");\n            put(\"Lua\", \"15\");\n            put(\"JavaScript\", \"16\");\n            put(\"Go\", \"17\");\n            put(\"SQL(sqlite3)\", \"18\");\n            put(\"Fortran\", \"19\");\n            put(\"Matlab(Octave)\", \"20\");\n        }\n    };\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return MXTInfo.INFO;\n    }\n\n    @Override\n    public void submit(SubmissionInfo info, RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\"language\", LANGUAGE_MAP.get(info.language), \"notes_id\",\n                \"0\", \"source\", info.userCode);\n        HttpPost post = new HttpPost(\"/submit/7/\" + info.remotePid + \"/\");\n        post.setEntity(entity);\n        info.remoteRunId = getRunId(client, post);\n    }\n\n    private String getRunId(DedicatedHttpClient client, HttpPost post) throws InterruptedException {\n        String result = client.execute(post, HttpStatusValidator.SC_OK).getBody();\n        JSONObject jsonObject = JSONUtil.parseObj(result);\n        if (!jsonObject.getBool(\"success\")) {\n            int interval = Integer.parseInt(ReUtil.getGroup1(\"(\\\\d+)秒\", jsonObject.getStr(\"msg\")));\n            TimeUnit.SECONDS.sleep(interval);\n            result = client.execute(post, HttpStatusValidator.SC_OK).getBody();\n            jsonObject = JSONUtil.parseObj(result);\n        }\n        return jsonObject.getStr(\"solution_id\");\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/mxt/RsaUtil.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.mxt;\n\nimport cn.hutool.crypto.asymmetric.KeyType;\nimport cn.hutool.crypto.asymmetric.RSA;\n\n/**\n * @author chenfan\n * @date 2022/10/16 16:13\n **/\npublic class RsaUtil {\n    /**\n     * 公钥加密\n     *\n     * @param content   要加密的内容\n     * @param publicKey 公钥\n     */\n    public static String encrypt(String content, String publicKey) {\n        try {\n            RSA rsa = new RSA(null, publicKey);\n            return rsa.encryptBase64(content, KeyType.PublicKey);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**\n     * 私钥解密\n     *\n     * @param content    要解密的内容\n     * @param privateKey 私钥\n     */\n    public static String decrypt(String content, String privateKey) {\n        try {\n            RSA rsa = new RSA(privateKey, null);\n            return rsa.decryptStr(content, KeyType.PrivateKey);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/poj/POJInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.poj;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport org.apache.http.HttpHost;\n\npublic class POJInfo {\n\n    public static final RemoteOjInfo INFO = new RemoteOjInfo(RemoteOj.POJ, new HttpHost(\"poj.org\"));\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/poj/POJLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.poj;\n\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.loginer.AbstractRetentiveLoginer;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.springframework.stereotype.Component;\n\n@Component\n@RequiredArgsConstructor\npublic class POJLoginer extends AbstractRetentiveLoginer {\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return POJInfo.INFO;\n    }\n\n    @Override\n    protected void loginEnforce(RemoteAccount account) {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        if (client.get(\"/\").getBody().contains(\">Log Out</a>\")) {\n            return;\n        }\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\"B1\", \"login\", \"password1\", account.getPassword(),\n                \"url\", \"%2F\", \"user_id1\", account.getAccountId());\n        client.post(\"/login\", entity, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/poj/POJQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.poj;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ReUtil;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpBodyValidator;\nimport com.simplefanc.voj.judger.judge.remote.querier.Querier;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\n@RequiredArgsConstructor\npublic class POJQuerier implements Querier {\n\n    private static final Map<String, JudgeStatus> STATUS_MAP = new HashMap<>() {\n        {\n            put(\"Compiling\", JudgeStatus.STATUS_COMPILING);\n            put(\"Accepted\", JudgeStatus.STATUS_ACCEPTED);\n            put(\"Running & Judging\", JudgeStatus.STATUS_JUDGING);\n            put(\"Presentation Error\", JudgeStatus.STATUS_PRESENTATION_ERROR);\n            put(\"Time Limit Exceeded\", JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED);\n            put(\"Memory Limit Exceeded\", JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED);\n            put(\"Wrong Answer\", JudgeStatus.STATUS_WRONG_ANSWER);\n            put(\"Runtime Error\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"Output Limit Exceeded\", JudgeStatus.STATUS_OUTPUT_LIMIT_EXCEEDED);\n            put(\"Compile Error\", JudgeStatus.STATUS_COMPILE_ERROR);\n        }\n    };\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return POJInfo.INFO;\n    }\n\n    @Override\n    public SubmissionRemoteStatus query(SubmissionInfo info, RemoteAccount account) {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        // 需要登录\n        String html = client\n                .get(\"/showsource?solution_id=\" + info.remoteRunId, new HttpBodyValidator(\"<title>Error</title>\", true))\n                .getBody();\n\n        SubmissionRemoteStatus status = new SubmissionRemoteStatus();\n        status.rawStatus = ReUtil.getGroup1(\"<b>Result:</b>(.+?)</td>\", html).replaceAll(\"<.*?>\", \"\").trim();\n        status.statusType = STATUS_MAP.getOrDefault(status.rawStatus, JudgeStatus.STATUS_JUDGING);\n        if (status.statusType == JudgeStatus.STATUS_ACCEPTED) {\n            status.executionMemory = Integer.parseInt(ReUtil.getGroup1(\"<b>Memory:</b> ([-\\\\d]+)\", html));\n            status.executionTime = Integer.parseInt(ReUtil.getGroup1(\"<b>Time:</b> ([-\\\\d]+)\", html));\n        } else if (status.statusType == JudgeStatus.STATUS_COMPILE_ERROR) {\n            html = client.get(\"/showcompileinfo?solution_id=\" + info.remoteRunId).getBody();\n            Assert.isTrue(html.contains(\"Compile Error\"));\n            status.compilationErrorInfo = ReUtil.getGroup1(\"(<pre>[\\\\s\\\\S]*?</pre>)\", html);\n        }\n        return status;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/poj/POJSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.poj;\n\nimport cn.hutool.core.util.ReUtil;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.submitter.Submitter;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.commons.codec.binary.Base64;\nimport org.apache.http.HttpEntity;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n@Component\n@RequiredArgsConstructor\npublic class POJSubmitter implements Submitter {\n\n    private static final Map<String, String> LANGUAGE_MAP = new HashMap<String, String>() {\n        {\n            put(\"G++\", \"0\");\n            put(\"GCC\", \"1\");\n            put(\"Java\", \"2\");\n            put(\"Pascal\", \"3\");\n            put(\"C++\", \"4\");\n            put(\"C\", \"5\");\n            put(\"Fortran\", \"6\");\n        }\n    };\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return POJInfo.INFO;\n    }\n\n    protected String getRunId(SubmissionInfo info, DedicatedHttpClient client) {\n        String html = client.get(\"/status?user_id=\" + info.remoteAccountId + \"&problem_id=\" + info.remotePid).getBody();\n        return ReUtil.getGroup1(\"<tr align=center><td>(\\\\d+)\", html);\n    }\n\n    @Override\n    public void submit(SubmissionInfo info, RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\"language\", LANGUAGE_MAP.get(info.language),\n                \"problem_id\", info.remotePid, \"source\", new String(Base64.encodeBase64(info.userCode.getBytes())),\n                \"encoded\", \"1\", \"submit\", \"Submit\");\n        client.post(\"/submit\", entity, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n        if ((info.remoteRunId = getRunId(info, client)) == null) {\n            // 等待2s再次查询，如果还是失败，则表明提交失败了\n            TimeUnit.SECONDS.sleep(2);\n            info.remoteRunId = getRunId(info, client);\n        }\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/shared/codeforces/AbstractCFStyleLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces;\n\nimport cn.hutool.core.util.ReUtil;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.loginer.AbstractRetentiveLoginer;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\n@Component\n@RequiredArgsConstructor\npublic abstract class AbstractCFStyleLoginer extends AbstractRetentiveLoginer {\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    protected void loginEnforce(RemoteAccount account) {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        final String homePage = client.get(\"/\").getBody();\n        if (homePage.contains(\"/logout\\\">\") && homePage.contains(\"<a href=\\\"/profile/\" + account.getAccountId() + \"\\\"\")) {\n            return;\n        }\n        String csrfToken = ReUtil.get(\"data-csrf='(\\\\w+)'\", homePage, 1);\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\n                \"csrf_token\", csrfToken,\n                \"action\", \"enter\",\n                \"ftaa\", \"\",\n                \"bfaa\", \"\",\n                \"handleOrEmail\", account.getAccountId(),\n                \"password\", account.getPassword(),\n                \"remember\", \"on\",\n                \"_tta\", \"\"\n        );\n        HttpPost post = new HttpPost(\"/enter\");\n        post.setEntity(entity);\n        client.execute(post, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/shared/codeforces/AbstractCFStyleQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces;\n\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.judge.remote.querier.Querier;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\n@Slf4j(topic = \"voj\")\npublic abstract class AbstractCFStyleQuerier implements Querier {\n\n    protected static final Map<String, JudgeStatus> STATUS_MAP = new HashMap<>() {\n        {\n            put(\"FAILED\", JudgeStatus.STATUS_SUBMITTED_FAILED);\n            put(\"OK\", JudgeStatus.STATUS_ACCEPTED);\n            put(\"PARTIAL\", JudgeStatus.STATUS_PARTIAL_ACCEPTED);\n            put(\"COMPILATION_ERROR\", JudgeStatus.STATUS_COMPILE_ERROR);\n            put(\"RUNTIME_ERROR\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"WRONG_ANSWER\", JudgeStatus.STATUS_WRONG_ANSWER);\n            put(\"PRESENTATION_ERROR\", JudgeStatus.STATUS_PRESENTATION_ERROR);\n            put(\"TIME_LIMIT_EXCEEDED\", JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED);\n            put(\"MEMORY_LIMIT_EXCEEDED\", JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED);\n            put(\"IDLENESS_LIMIT_EXCEEDED\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"SECURITY_VIOLATED\", JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(\"CRASHED\", JudgeStatus.STATUS_SYSTEM_ERROR);\n            put(\"INPUT_PREPARATION_CRASHED\", JudgeStatus.STATUS_SYSTEM_ERROR);\n            put(\"CHALLENGED\", JudgeStatus.STATUS_SYSTEM_ERROR);\n            put(\"SKIPPED\", JudgeStatus.STATUS_SYSTEM_ERROR);\n            put(\"TESTING\", JudgeStatus.STATUS_JUDGING);\n            put(\"REJECTED\", JudgeStatus.STATUS_SYSTEM_ERROR);\n            put(\"RUNNING & JUDGING\", JudgeStatus.STATUS_JUDGING);\n        }\n    };\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/shared/codeforces/AbstractCFStyleSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces;\n\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.submitter.Submitter;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Random;\n\n@Component\n@RequiredArgsConstructor\n@Slf4j(topic = \"voj\")\npublic abstract class AbstractCFStyleSubmitter implements Submitter {\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    private static final String SUBMISSION_BY_USERNAME = \"/submissions/%s\";\n\n    @Override\n    public void submit(SubmissionInfo info, RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        submitCode(client, info, account);\n        // 获取提交的题目id\n        info.remoteRunId = getMaxIdByParseHtml(client, info, account);\n    }\n\n    protected abstract String getSubmitUrl(String contestNum);\n\n    private void submitCode(DedicatedHttpClient client, SubmissionInfo info, RemoteAccount account) {\n        processProblemId(info);\n        String body = client.get(getSubmitUrl(info.remoteContestId)).getBody();\n        String csrfToken = ReUtil.get(\"data-csrf='(\\\\w+)'\", body, 1);\n        account.setCsrfToken(csrfToken);\n\n        HttpPost post = new HttpPost(getSubmitUrl(info.remoteContestId) + \"?csrf_token=\" + csrfToken);\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\n                \"csrf_token\", csrfToken,\n                \"_tta\", CFUtil.getTTA(client),\n                \"bfaa\", \"\",\n                \"ftaa\", \"\",\n                \"action\", \"submitSolutionFormSubmitted\",\n                \"submittedProblemIndex\", info.remoteProblemIndex,\n                \"contestId\", info.remoteContestId,\n                \"programTypeId\", getLanguage(info.language),\n                \"tabsize\", \"4\",\n                \"source\", info.userCode + getRandomBlankString(),\n                \"sourceCodeConfirmed\", \"true\",\n                \"doNotShowWarningAgain\", \"on\"\n        );\n        post.setEntity(entity);\n        client.execute(post, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n    }\n\n    private void processProblemId(SubmissionInfo info) {\n        if (NumberUtil.isInteger(info.remotePid)) {\n            info.remoteContestId = ReUtil.get(\"([0-9]+)[0-9]{2}\", info.remotePid, 1);\n            // 后两位\n            info.remoteProblemIndex = ReUtil.get(\"[0-9]+([0-9]{2})\", info.remotePid, 1);\n        } else {// 1238G\n            // 1238\n            info.remoteContestId = ReUtil.get(\"([0-9]+)[A-Z]{1}[0-9]{0,1}\", info.remotePid, 1);\n            // G\n            info.remoteProblemIndex = ReUtil.get(\"[0-9]+([A-Z]{1}[0-9]{0,1})\", info.remotePid, 1);\n        }\n    }\n\n    private String getMaxIdByParseHtml(DedicatedHttpClient client, SubmissionInfo info, RemoteAccount account) {\n        final String body = client.get(String.format(SUBMISSION_BY_USERNAME, account.getAccountId())).getBody();\n        String maxRunIdStr = ReUtil.get(\"data-submission-id=\\\"(\\\\d+)\\\"\", body, 1);\n        if (StrUtil.isEmpty(maxRunIdStr)) {\n            // log.error(\"[Codeforces] Failed to parse submission html:{}\", body);\n            String log = String.format(\"[Codeforces] Failed to parse html to get run id for problem: [%s]\", info.remotePid);\n            throw new RuntimeException(log);\n        } else {\n            return maxRunIdStr;\n        }\n    }\n\n    private String getRandomBlankString() {\n        StringBuilder string = new StringBuilder(\"\\n\");\n        int random = new Random().nextInt(Integer.MAX_VALUE);\n        while (random > 0) {\n            string.append(random % 2 == 0 ? ' ' : '\\t');\n            random /= 2;\n        }\n        return string.toString();\n    }\n\n    private String getLanguage(String language) {\n        if (language.startsWith(\"GNU GCC C11\")) {\n            return \"43\";\n        } else if (language.startsWith(\"Clang++17 Diagnostics\")) {\n            return \"52\";\n        } else if (language.startsWith(\"GNU G++11\")) {\n            return \"50\";\n        } else if (language.startsWith(\"GNU G++14\")) {\n            return \"50\";\n        } else if (language.startsWith(\"GNU G++17\")) {\n            return \"54\";\n        } else if (language.startsWith(\"GNU G++20\")) {\n            return \"73\";\n        } else if (language.startsWith(\"Microsoft Visual C++ 2017\")) {\n            return \"59\";\n        } else if (language.startsWith(\"C# 8, .NET Core\")) {\n            return \"65\";\n        } else if (language.startsWith(\"C# Mono\")) {\n            return \"9\";\n        } else if (language.startsWith(\"D DMD32\")) {\n            return \"28\";\n        } else if (language.startsWith(\"Go\")) {\n            return \"32\";\n        } else if (language.startsWith(\"Haskell GHC\")) {\n            return \"12\";\n        } else if (language.startsWith(\"Java 11\")) {\n            return \"60\";\n        } else if (language.startsWith(\"Java 1.8\")) {\n            return \"36\";\n        } else if (language.startsWith(\"Kotlin\")) {\n            return \"48\";\n        } else if (language.startsWith(\"OCaml\")) {\n            return \"19\";\n        } else if (language.startsWith(\"Delphi\")) {\n            return \"3\";\n        } else if (language.startsWith(\"Free Pascal\")) {\n            return \"4\";\n        } else if (language.startsWith(\"PascalABC.NET\")) {\n            return \"51\";\n        } else if (language.startsWith(\"Perl\")) {\n            return \"13\";\n        } else if (language.startsWith(\"PHP\")) {\n            return \"6\";\n        } else if (language.startsWith(\"Python 2\")) {\n            return \"7\";\n        } else if (language.startsWith(\"Python 3\")) {\n            return \"31\";\n        } else if (language.startsWith(\"PyPy 2\")) {\n            return \"40\";\n        } else if (language.startsWith(\"PyPy 3\")) {\n            return \"41\";\n        } else if (language.startsWith(\"Ruby\")) {\n            return \"67\";\n        } else if (language.startsWith(\"Rust\")) {\n            return \"49\";\n        } else if (language.startsWith(\"Scala\")) {\n            return \"20\";\n        } else if (language.startsWith(\"JavaScript\")) {\n            return \"34\";\n        } else if (language.startsWith(\"Node.js\")) {\n            return \"55\";\n        } else {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/shared/codeforces/CFUtil.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.shared.codeforces;\n\nimport com.simplefanc.voj.judger.judge.remote.httpclient.CookieUtil;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport lombok.experimental.UtilityClass;\n\n/**\n * @author chenfan\n * @date 2022/7/25 10:24\n **/\n@UtilityClass\npublic class CFUtil {\n\n    public String getTTA(DedicatedHttpClient client) {\n        String _39ce7 = CookieUtil.getCookieValue(client, \"39ce7\");\n        int _tta = 0;\n        for (int c = 0; c < _39ce7.length(); c++) {\n            _tta = (_tta + (c + 1) * (c + 2) * _39ce7.charAt(c)) % 1009;\n            if (c % 3 == 0) {\n                _tta++;\n            }\n            if (c % 2 == 0) {\n                _tta *= 2;\n            }\n            if (c > 0) {\n                _tta -= (_39ce7.charAt(c / 2) / 2) * (_tta % 5);\n            }\n            while (_tta < 0) {\n                _tta += 1009;\n            }\n            while (_tta >= 1009) {\n                _tta -= 1009;\n            }\n        }\n        return Integer.toString(_tta);\n    }\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/tkoj/TKOJCaptchaRecognizer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.tkoj;\n\nimport java.awt.image.BufferedImage;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\npublic class TKOJCaptchaRecognizer {\n\n    private static String[][] digitals = new String[][]{\n            {\"..#####...\", \".#######..\", \"#########.\", \"###...###.\", \"###...###.\", \"###...###.\", \"###....##.\",\n                    \"###.##.##.\", \"###.##.##.\", \"###...###.\", \"###...###.\", \"###...###.\", \"#########.\", \".#######..\",\n                    \"..#####...\"},\n            {\"..#####...\", \"..#####...\", \"..#####...\", \".....##...\", \".....##...\", \".....##...\", \".....##...\",\n                    \".....##...\", \".....##...\", \".....##...\", \".....##...\", \".....##...\", \".....##...\", \"..########\",\n                    \"..########\"},\n            {\"########..\", \"#########.\", \"###..####.\", \"......###.\", \".......###\", \"......####\", \"......###.\",\n                    \".....####.\", \"....####..\", \"...####...\", \"..####....\", \".####.....\", \"####......\", \"##########\",\n                    \"##########\"},\n            {\"########..\", \"#########.\", \"###..####.\", \"......###.\", \"......###.\", \".....####.\", \"...#####..\",\n                    \"...#####..\", \".....####.\", \"......###.\", \".......##.\", \"......###.\", \"###.#####.\", \"#########.\",\n                    \"########..\"},\n            {\".....###..\", \"....####..\", \"....####..\", \"...#####..\", \"...##.##..\", \"..###.##..\", \".###..##..\",\n                    \".###..##..\", \"###...##..\", \"###...##..\", \"##########\", \"##########\", \"......##..\", \"......##..\",\n                    \"......##..\"},\n            {\".#######..\", \".#######..\", \".##.......\", \".##.......\", \".##.......\", \".#######..\", \".#######..\",\n                    \".##.#####.\", \"......###.\", \"......###.\", \"......###.\", \"......###.\", \"###.#####.\", \"########..\",\n                    \"#######...\"},\n            {\"..######..\", \".#######..\", \".####.##..\", \"####......\", \"###.......\", \"###.......\", \"########..\",\n                    \"#########.\", \"####.####.\", \"###...###.\", \"##.....##.\", \"###...###.\", \"####.####.\", \".########.\",\n                    \"..######..\"},\n            {\"#########.\", \"#########.\", \"......###.\", \"......###.\", \".....###..\", \".....###..\", \"....####..\",\n                    \"....###...\", \"....###...\", \"...###....\", \"...###....\", \"...###....\", \"..###.....\", \"..###.....\",\n                    \"..###.....\"},\n            {\".#######..\", \"#########.\", \"####.####.\", \"###...###.\", \"###...###.\", \"####.####.\", \".#######..\",\n                    \".#######..\", \"####.####.\", \"###...###.\", \"##.....##.\", \"###...###.\", \"####.####.\", \"#########.\",\n                    \".#######..\"},\n            {\".######...\", \"########..\", \"####.####.\", \"###...###.\", \"##.....##.\", \"###...###.\", \"####.####.\",\n                    \"#########.\", \".########.\", \"......###.\", \"......###.\", \".....####.\", \".##.####..\", \".#######..\",\n                    \".######...\"}};\n\n    /**\n     * @param image 需要被分割的验证码\n     * @return\n     */\n    private static List<BufferedImage> splitImage(BufferedImage image) {\n        List<BufferedImage> subImgs = new ArrayList<BufferedImage>();\n        subImgs.add(image.getSubimage(5, 5, 10, 15));\n        subImgs.add(image.getSubimage(17, 5, 10, 15));\n        subImgs.add(image.getSubimage(29, 5, 10, 15));\n        subImgs.add(image.getSubimage(41, 5, 10, 15));\n        subImgs.add(image.getSubimage(53, 5, 5, 15));\n        return subImgs;\n    }\n\n    /**\n     * @param rgb 像素点RGB\n     * @return 像素点灰度\n     */\n    private static int calGray(int rgb) {\n        int argb = 0xff000000 | rgb;\n        int r = (int) (((argb >> 16) & 0xFF));\n        int g = (int) (((argb >> 8) & 0xFF));\n        int b = (int) (((argb >> 0) & 0xFF));\n        return r + g + b;\n    }\n\n    /**\n     * @param image 目标图片\n     * @return 灰度集\n     */\n    private static Set<Integer> getGraySet(BufferedImage image) {\n        int h = image.getHeight();\n        int w = image.getWidth();\n        // 灰度统计\n        Set<Integer> hashSet = new HashSet<Integer>();\n        for (int x = 0; x < w; x++) {\n            for (int y = 0; y < h; y++) {\n                hashSet.add(calGray(image.getRGB(x, y)));\n            }\n        }\n        return hashSet;\n    }\n\n    /**\n     * @param image   需要打印的图像\n     * @param graySet 背景灰度集\n     */\n    private static void printImage(BufferedImage image, Set<Integer> graySet) {\n        int h = image.getHeight();\n        int w = image.getWidth();\n\n        // 矩阵打印\n        for (int y = 0; y < h; y++) {\n            System.out.printf(\"\\\"\");\n            for (int x = 0; x < w; x++) {\n                if (graySet.contains(calGray(image.getRGB(x, y)))) {\n                    System.out.print(\".\");\n                } else {\n                    System.out.print(\"#\");\n                }\n            }\n            System.out.printf(\"%s\", y == h - 1 ? \"\\\"\" : \"\\\",\");\n            System.out.println();\n        }\n    }\n\n    /**\n     * @param image   待识别图片\n     * @param graySet 背景灰度集\n     * @return 符号\n     */\n    private static char recognizeSymbol(BufferedImage image, Set<Integer> graySet) {\n        int h = image.getHeight();\n        int w = image.getWidth();\n\n        int minDiff = 999999;\n        char symAns = 0;\n        // 对于某个给定数值\n        for (int i = 0; i < 10; i++) {\n            int curDiff = 0;\n            for (int y = 0; y < h; y++) {\n                for (int x = 0; x < w; x++) {\n                    boolean pixel1 = digitals[i][y].charAt(x) == '#';\n                    boolean pixel2 = !graySet.contains(calGray(image.getRGB(x, y)));\n                    if (pixel1 != pixel2) {\n                        ++curDiff;\n                    }\n                }\n            }\n            if (curDiff < minDiff) {\n                minDiff = curDiff;\n                symAns = (char) ('0' + i);\n            }\n            if (minDiff == 0) {\n                return symAns;\n            }\n        }\n\n        return symAns;\n    }\n\n    /**\n     * @param image 待识别的验证码\n     * @return\n     */\n    public static String recognize(BufferedImage image) {\n        StringBuilder ans = new StringBuilder();\n        List<BufferedImage> subImgs = splitImage(image);\n        Set<Integer> graySet = getGraySet(subImgs.get(subImgs.size() - 1));\n\n        // printImage(image, graySet);\n        for (int i = 0; i < subImgs.size() - 1; i++) {\n            // 根据特征色，依次识别子图片\n            ans.append(recognizeSymbol(subImgs.get(i), graySet));\n        }\n        return ans.toString();\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/tkoj/TKOJInfo.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.tkoj;\n\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport org.apache.http.HttpHost;\n\npublic class TKOJInfo {\n\n    public static final RemoteOjInfo INFO = new RemoteOjInfo(RemoteOj.TKOJ, new HttpHost(\"tk.hustoj.com\"));\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/tkoj/TKOJLoginer.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.tkoj;\n\nimport cn.hutool.crypto.SecureUtil;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.*;\nimport com.simplefanc.voj.judger.judge.remote.loginer.AbstractRetentiveLoginer;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\n@Component\n@RequiredArgsConstructor\npublic class TKOJLoginer extends AbstractRetentiveLoginer {\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return TKOJInfo.INFO;\n    }\n\n    @Override\n    protected void loginEnforce(RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        if (client.get(\"/loginpage.php\", HttpStatusValidator.SC_OK).getBody().contains(\"Please logout First!\")) {\n            return;\n        }\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\"user_id\", account.getAccountId(), \"password\",\n                SecureUtil.md5(account.getPassword()), \"csrf\", TKOJVerifyUtil.getCsrf(client), \"vcode\",\n                TKOJVerifyUtil.getCaptcha(client));\n        HttpPost post = new HttpPost(\"/login.php\");\n        post.setEntity(entity);\n        client.execute(post, HttpStatusValidator.SC_OK, new HttpBodyValidator(\"history.go(-2);\"));\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/tkoj/TKOJQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.tkoj;\n\nimport cn.hutool.core.util.ReUtil;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.querier.Querier;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\n@RequiredArgsConstructor\npublic class TKOJQuerier implements Querier {\n\n    private static final String[] STATUS_ARRAY = new String[]{\"Pending\", \"Pending Rejudging\", \"Compiling\",\n            \"Running Judging\", \"Accepted\", \"Presentation Error\", \"Wrong Answer\", \"Time Limit Exceed\",\n            \"Memory Limit Exceed\", \"Output Limit Exceed\", \"Runtime Error\", \"Compile Error\", \"Compile OK\",\n            \"Runtime Finish\"};\n\n    private static final Map<Integer, JudgeStatus> STATUS_MAP = new HashMap<>() {\n        {\n            put(0, JudgeStatus.STATUS_JUDGING);\n            put(1, JudgeStatus.STATUS_JUDGING);\n            put(2, JudgeStatus.STATUS_COMPILING);\n            put(3, JudgeStatus.STATUS_JUDGING);\n            put(4, JudgeStatus.STATUS_ACCEPTED);\n            put(5, JudgeStatus.STATUS_PRESENTATION_ERROR);\n            put(6, JudgeStatus.STATUS_WRONG_ANSWER);\n            put(7, JudgeStatus.STATUS_TIME_LIMIT_EXCEEDED);\n            put(8, JudgeStatus.STATUS_MEMORY_LIMIT_EXCEEDED);\n            put(9, JudgeStatus.STATUS_OUTPUT_LIMIT_EXCEEDED);\n            put(10, JudgeStatus.STATUS_RUNTIME_ERROR);\n            put(11, JudgeStatus.STATUS_COMPILE_ERROR);\n            put(12, JudgeStatus.STATUS_JUDGING);\n            put(13, JudgeStatus.STATUS_JUDGING);\n        }\n    };\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return TKOJInfo.INFO;\n    }\n\n    @Override\n    public SubmissionRemoteStatus query(SubmissionInfo info, RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        String result = client.get(\"/status-ajax.php?solution_id=\" + info.remoteRunId, HttpStatusValidator.SC_OK)\n                .getBody();\n        String[] results = result.split(\",\");\n        SubmissionRemoteStatus status = new SubmissionRemoteStatus();\n        status.rawStatus = STATUS_ARRAY[Integer.parseInt(results[0])];\n        status.statusType = STATUS_MAP.getOrDefault(Integer.parseInt(results[0]), JudgeStatus.STATUS_JUDGING);\n        status.executionMemory = Integer.parseInt(results[1]);\n        status.executionTime = Integer.parseInt(results[2]);\n\n        if (status.statusType == JudgeStatus.STATUS_COMPILE_ERROR) {\n            String ceinfo = client.get(\"/ceinfo.php?sid=\" + info.remoteRunId).getBody();\n            status.compilationErrorInfo = \"<pre>\" + ReUtil.getGroup1(\"id='errtxt'\\\\s*?>([\\\\s\\\\S]*?</pre>)\", ceinfo);\n        }\n        return status;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/tkoj/TKOJSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.tkoj;\n\nimport cn.hutool.core.util.ReUtil;\nimport com.simplefanc.voj.judger.judge.remote.pojo.RemoteOjInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClientFactory;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.SimpleNameValueEntityFactory;\nimport com.simplefanc.voj.judger.judge.remote.submitter.Submitter;\nimport lombok.RequiredArgsConstructor;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.methods.HttpPost;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n@Component\n@RequiredArgsConstructor\npublic class TKOJSubmitter implements Submitter {\n\n    private static final Map<String, String> LANGUAGE_MAP = new HashMap<String, String>() {\n        {\n            put(\"C\", \"0\");\n            put(\"C++\", \"1\");\n            put(\"Pascal\", \"2\");\n            put(\"Java\", \"3\");\n            put(\"Ruby\", \"4\");\n            put(\"Bash\", \"5\");\n            put(\"Python\", \"6\");\n            put(\"PHP\", \"7\");\n            put(\"Perl\", \"8\");\n            put(\"C#\", \"9\");\n            put(\"Obj-C\", \"10\");\n            put(\"FreeBasic\", \"11\");\n            put(\"Scheme\", \"12\");\n            put(\"Clang\", \"13\");\n            put(\"Clang++\", \"14\");\n            put(\"Lua\", \"15\");\n            put(\"JavaScript\", \"16\");\n            put(\"Go\", \"17\");\n            put(\"SQL(sqlite3)\", \"18\");\n        }\n    };\n\n    private final DedicatedHttpClientFactory dedicatedHttpClientFactory;\n\n    @Override\n    public RemoteOjInfo getOjInfo() {\n        return TKOJInfo.INFO;\n    }\n\n    protected String getRunId(SubmissionInfo info, DedicatedHttpClient client) {\n        String html = client.get(\"/status.php?problem_id=\" + info.remotePid + \"&user_id=\" + info.remoteAccountId,\n                HttpStatusValidator.SC_OK).getBody();\n        return ReUtil.getGroup1(\"prevtop=(\\\\d+)\", html);\n    }\n\n    @Override\n    public void submit(SubmissionInfo info, RemoteAccount account) throws Exception {\n        DedicatedHttpClient client = dedicatedHttpClientFactory.build(getOjInfo().mainHost, account.getContext());\n        // 进行代码提交\n        HttpEntity entity = SimpleNameValueEntityFactory.create(\"id\", info.remotePid, \"language\", info.language,\n                \"source\", info.userCode, \"csrf\", TKOJVerifyUtil.getCsrf(client), \"vcode\",\n                TKOJVerifyUtil.getCaptcha(client));\n        HttpPost post = new HttpPost(\"/submit.php\");\n        post.setEntity(entity);\n        client.execute(post, HttpStatusValidator.SC_MOVED_TEMPORARILY);\n        if ((info.remoteRunId = getRunId(info, client)) == null) {\n            // 等待2s再次查询，如果还是失败，则表明提交失败了\n            TimeUnit.SECONDS.sleep(2);\n            info.remoteRunId = getRunId(info, client);\n        }\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/provider/tkoj/TKOJVerifyUtil.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.provider.tkoj;\n\nimport cn.hutool.core.util.ReUtil;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.DedicatedHttpClient;\nimport com.simplefanc.voj.judger.judge.remote.httpclient.HttpStatusValidator;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.ClientProtocolException;\nimport org.apache.http.client.ResponseHandler;\nimport org.apache.http.client.methods.HttpGet;\n\nimport javax.imageio.ImageIO;\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\npublic class TKOJVerifyUtil {\n\n    public static String getCsrf(DedicatedHttpClient client) {\n        String result = client.get(\"/csrf.php\", HttpStatusValidator.SC_OK).getBody();\n        return ReUtil.getGroup1(\"value=\\\"([\\\\s\\\\S]*?)\\\"\", result);\n    }\n\n    public static String getCaptcha(DedicatedHttpClient client) throws ClientProtocolException, IOException {\n        HttpGet get = new HttpGet(\"/vcode.php\");\n        BufferedImage image = client.execute(get, new ResponseHandler<BufferedImage>() {\n            @Override\n            public BufferedImage handleResponse(HttpResponse response) throws ClientProtocolException, IOException {\n                FileOutputStream fos = null;\n                try {\n                    File captchaImg = File.createTempFile(\"TKOJ\", \".png\");\n                    fos = new FileOutputStream(captchaImg);\n                    response.getEntity().writeTo(fos);\n                    return ImageIO.read(captchaImg);\n                } finally {\n                    fos.close();\n                }\n            }\n        });\n        return TKOJCaptchaRecognizer.recognize(image);\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/querier/Querier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.querier;\n\nimport com.simplefanc.voj.judger.judge.remote.RemoteOjAware;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\n\npublic interface Querier extends RemoteOjAware {\n\n    SubmissionRemoteStatus query(SubmissionInfo info, RemoteAccount account) throws Exception;\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/querier/QueriersHolder.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.querier;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.utils.Tools;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n@Slf4j(topic = \"voj\")\npublic class QueriersHolder {\n\n    private static final HashMap<RemoteOj, Querier> QUERIERS = new HashMap<>();\n\n    public static Querier getQuerier(RemoteOj remoteOj) {\n        if (!QUERIERS.containsKey(remoteOj)) {\n            synchronized (QUERIERS) {\n                if (!QUERIERS.containsKey(remoteOj)) {\n                    try {\n                        List<Class<? extends Querier>> querierClasses = Tools\n                                .findSubClasses(\"com.simplefanc.voj.judger.judge.remote\", Querier.class);\n                        for (Class<? extends Querier> querierClass : querierClasses) {\n                            Querier querier = SpringUtil.getBean(querierClass);\n                            QUERIERS.put(querier.getOjInfo().remoteOj, querier);\n                        }\n                    } catch (Throwable t) {\n                        log.error(\"Get Querier Failed\", t);\n                    }\n                }\n            }\n        }\n        return QUERIERS.get(remoteOj);\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/querier/RemoteJudgeQuerier.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.querier;\n\nimport cn.hutool.core.lang.UUID;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport com.simplefanc.voj.judger.dao.ContestRecordEntityService;\nimport com.simplefanc.voj.judger.dao.JudgeCaseEntityService;\nimport com.simplefanc.voj.judger.dao.JudgeEntityService;\nimport com.simplefanc.voj.judger.dao.UserAcproblemEntityService;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionRemoteStatus;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.Map;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author chenfan\n */\n@Slf4j(topic = \"voj\")\n@Component\n@RequiredArgsConstructor\npublic class RemoteJudgeQuerier {\n\n    private final static ScheduledExecutorService SCHEDULER = Executors\n            .newScheduledThreadPool(Runtime.getRuntime().availableProcessors() * 2);\n\n    private final static Map<String, ScheduledFuture<?>> FUTURE_TASK_MAP = new ConcurrentHashMap<>(\n            Runtime.getRuntime().availableProcessors() * 2);\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final JudgeCaseEntityService judgeCaseEntityService;\n\n    private final UserAcproblemEntityService userAcproblemEntityService;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    public void process(SubmissionInfo info, RemoteAccount account) {\n        String key = UUID.randomUUID().toString() + info.submitId;\n\n        ScheduledFuture<?> scheduledFuture = SCHEDULER.scheduleWithFixedDelay(new QueryTask(info, account, key), 0, 3,\n                TimeUnit.SECONDS);\n        FUTURE_TASK_MAP.put(key, scheduledFuture);\n    }\n\n    class QueryTask implements Runnable {\n\n        AtomicInteger count = new AtomicInteger(0);\n\n        SubmissionInfo info;\n\n        RemoteAccount account;\n\n        String key;\n\n        public QueryTask(SubmissionInfo info, RemoteAccount account, String key) {\n            this.info = info;\n            this.account = account;\n            this.key = key;\n        }\n\n        @Override\n        public void run() {\n            // 超过20次失败则判为提交失败\n            if (count.get() > 20) {\n                handleQueryFailure();\n                return;\n            }\n\n            count.getAndIncrement();\n            try {\n                Querier querier = QueriersHolder.getQuerier(info.remoteOj);\n                final SubmissionRemoteStatus result = querier.query(info, account);\n                checkSubmissionResult(result);\n            } catch (Exception e) {\n                log.error(\"The Error of getting the `remote judge` result:\", e);\n            }\n        }\n\n        private void checkSubmissionResult(SubmissionRemoteStatus result) {\n            JudgeStatus status = result.getStatusType() != null ? result.getStatusType()\n                    : JudgeStatus.STATUS_SYSTEM_ERROR;\n            if (status == JudgeStatus.STATUS_COMPILING || status == JudgeStatus.STATUS_JUDGING) {\n                recordMidResult(status);\n            } else {\n                log.info(\"[{}] Get Result Successfully! Status:[{}]\", info.remoteOj, status);\n\n                // 保留各个测试点的结果数据\n                if (!CollectionUtils.isEmpty(result.getJudgeCaseList())) {\n                    judgeCaseEntityService.saveBatch(result.getJudgeCaseList());\n                }\n\n                Judge judge = wrapResultToJudge(result, status);\n                // 写回数据库\n                judgeEntityService.updateById(judge);\n                // 同步其它表\n                // 非比赛提交\n                if (judge.getCid() == 0) {\n                    // 如果是AC，就更新 user_acproblem表\n                    if (JudgeStatus.STATUS_ACCEPTED.getStatus().equals(judge.getStatus())) {\n                        userAcproblemEntityService.saveOrUpdate(new UserAcproblem().setPid(judge.getPid())\n                                .setUid(judge.getUid()).setSubmitId(judge.getSubmitId()));\n                    }\n                } else {\n                    // 如果是比赛提交\n                    contestRecordEntityService.updateContestRecord(judge);\n                }\n                cancelFutureTask();\n            }\n        }\n\n        private Judge wrapResultToJudge(SubmissionRemoteStatus result, JudgeStatus status) {\n            Judge judge = new Judge();\n            judge.setSubmitId(info.submitId).setCid(info.cid).setUid(info.uid).setPid(info.pid)\n                    .setStatus(status.getStatus()).setTime(result.getExecutionTime())\n                    .setMemory(result.getExecutionMemory());\n\n            if (status == JudgeStatus.STATUS_COMPILE_ERROR) {\n                judge.setErrorMessage(result.getCompilationErrorInfo());\n            } else if (status == JudgeStatus.STATUS_SYSTEM_ERROR) {\n                judge.setErrorMessage(\n                        \"There is something wrong with the \" + info.remoteOj + \", please try again later\");\n            }\n\n            // 如果是比赛题目，需要特别适配OI比赛的得分 除AC给100 其它结果给0分\n            if (info.cid != 0) {\n                judge.setScore(status == JudgeStatus.STATUS_ACCEPTED ? 100 : 0);\n            }\n            return judge;\n        }\n\n        private void recordMidResult(JudgeStatus status) {\n            Judge judge = new Judge();\n            judge.setSubmitId(info.submitId).setStatus(status.getStatus());\n            // 写回数据库\n            judgeEntityService.updateById(judge);\n        }\n\n        private void handleQueryFailure() {\n            // 更新此次提交状态为提交失败！\n            UpdateWrapper<Judge> judgeUpdateWrapper = new UpdateWrapper<>();\n            judgeUpdateWrapper.set(\"status\", JudgeStatus.STATUS_SUBMITTED_FAILED.getStatus()).set(\"error_message\",\n                    \"Waiting for remote judge result exceeds the maximum number of times, please try submitting again!\")\n                    .eq(\"submit_id\", info.submitId);\n            judgeEntityService.update(judgeUpdateWrapper);\n\n            log.error(\"[{}] Get Result Failed!\", info.remoteOj);\n            cancelFutureTask();\n        }\n\n        private void cancelFutureTask() {\n            ScheduledFuture<?> future = FUTURE_TASK_MAP.get(key);\n            if (future != null) {\n                boolean isCanceled = future.cancel(true);\n                if (isCanceled) {\n                    FUTURE_TASK_MAP.remove(key);\n                }\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/submitter/RemoteJudgeSubmitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.submitter;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.judger.dao.JudgeEntityService;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\nimport com.simplefanc.voj.judger.judge.remote.loginer.LoginersHolder;\nimport com.simplefanc.voj.judger.service.JudgeService;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author chenfan\n */\n@Component\n@Slf4j(topic = \"voj\")\n@RequiredArgsConstructor\npublic class RemoteJudgeSubmitter {\n\n    private final JudgeEntityService judgeEntityService;\n\n    public boolean process(SubmissionInfo info, RemoteAccount account) {\n        log.info(\n                \"Ready Send Task to RemoteJudge[{}] => submit_id: [{}], uid: [{}],\"\n                        + \" pid: [{}], vjudge_username: [{}], vjudge_password: [{}]\",\n                info.remoteOj, info.submitId, info.uid, info.pid, account.accountId, account.password);\n\n        String errLog = null;\n        try {\n            // account的context存储了相关cookie等\n            LoginersHolder.getLoginer(info.remoteOj).login(account);\n            SubmittersHolder.getSubmitter(info.remoteOj).submit(info, account);\n        } catch (Exception e) {\n            log.error(\"Submit Failed! Error:\", e);\n            errLog = e.getMessage();\n        }\n\n        // 提交失败 前端手动按按钮再次提交 修改状态 STATUS_SUBMITTED_FAILED\n        if (StrUtil.isBlank(info.remoteRunId)) {\n            log.error(\"[{}] Submit Failed! The submit_id of remote judge is blank.\", info.remoteOj);\n\n            // 更新此次提交状态为提交失败！\n            UpdateWrapper<Judge> judgeUpdateWrapper = new UpdateWrapper<>();\n            judgeUpdateWrapper.set(\"status\", JudgeStatus.STATUS_SUBMITTED_FAILED.getStatus())\n                    .set(\"error_message\", errLog).eq(\"submit_id\", info.submitId);\n            judgeEntityService.update(judgeUpdateWrapper);\n            // 更新其它表\n//            judgeService.updateOtherTable(\n//                    new Judge().setSubmitId(info.submitId).setStatus(JudgeStatus.STATUS_SYSTEM_ERROR.getStatus())\n//                            .setCid(info.cid).setUid(info.uid).setPid(info.pid));\n            return false;\n        }\n\n        // 提交成功顺便更新状态为-->STATUS_JUDGING 判题中...\n        judgeEntityService.updateById(new Judge().setSubmitId(info.submitId)\n                .setStatus(JudgeStatus.STATUS_JUDGING.getStatus()).setVjudgeSubmitId(info.remoteRunId)\n                .setVjudgeUsername(account.accountId).setVjudgePassword(account.password));\n\n        log.info(\"[{}] Submit Successfully! The submit_id of remote judge is [{}]. Waiting the result of the task!\",\n                info.remoteRunId, info.remoteOj);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/submitter/Submitter.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.submitter;\n\nimport com.simplefanc.voj.judger.judge.remote.RemoteOjAware;\nimport com.simplefanc.voj.judger.judge.remote.pojo.SubmissionInfo;\nimport com.simplefanc.voj.judger.judge.remote.account.RemoteAccount;\n\npublic interface Submitter extends RemoteOjAware {\n\n    void submit(SubmissionInfo info, RemoteAccount account) throws Exception;\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/judge/remote/submitter/SubmittersHolder.java",
    "content": "package com.simplefanc.voj.judger.judge.remote.submitter;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport com.simplefanc.voj.common.constants.RemoteOj;\nimport com.simplefanc.voj.common.utils.Tools;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.HashMap;\nimport java.util.List;\n\n@Slf4j(topic = \"voj\")\npublic class SubmittersHolder {\n\n    private static final HashMap<RemoteOj, Submitter> SUBMITTERS = new HashMap<>();\n\n    public static Submitter getSubmitter(RemoteOj remoteOj) {\n        if (!SUBMITTERS.containsKey(remoteOj)) {\n            synchronized (SUBMITTERS) {\n                if (!SUBMITTERS.containsKey(remoteOj)) {\n                    try {\n                        List<Class<? extends Submitter>> submitterClasses = Tools\n                                .findSubClasses(\"com.simplefanc.voj.judger.judge.remote\", Submitter.class);\n                        for (Class<? extends Submitter> submitterClass : submitterClasses) {\n                            Submitter submitter = SpringUtil.getBean(submitterClass);\n                            SUBMITTERS.put(submitter.getOjInfo().remoteOj, submitter);\n                        }\n                    } catch (Throwable t) {\n                        log.error(\"Get Submitter Failed\", t);\n                    }\n                }\n            }\n        }\n        return SUBMITTERS.get(remoteOj);\n    }\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/ContestMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.contest.Contest;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface ContestMapper extends BaseMapper<Contest> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/ContestRecordMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.contest.ContestRecord;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface ContestRecordMapper extends BaseMapper<ContestRecord> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/JudgeCaseMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeCase;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface JudgeCaseMapper extends BaseMapper<JudgeCase> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/JudgeMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface JudgeMapper extends BaseMapper<Judge> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/JudgeServerMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.JudgeServer;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface JudgeServerMapper extends BaseMapper<JudgeServer> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/ProblemCaseMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.ProblemCase;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/14 19:57\n * @Description:\n */\n@Mapper\npublic interface ProblemCaseMapper extends BaseMapper<ProblemCase> {\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/ProblemMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface ProblemMapper extends BaseMapper<Problem> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/RemoteJudgeAccountMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.judge.RemoteJudgeAccount;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface RemoteJudgeAccountMapper extends BaseMapper<RemoteJudgeAccount> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/UserAcproblemMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface UserAcproblemMapper extends BaseMapper<UserAcproblem> {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/mapper/UserRecordMapper.java",
    "content": "package com.simplefanc.voj.judger.mapper;\n\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * <p>\n * Mapper 接口\n * </p>\n *\n * @author chenfan\n * @since 2021-10-23\n */\n@Mapper\npublic interface UserRecordMapper {\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/service/JudgeService.java",
    "content": "package com.simplefanc.voj.judger.service;\n\nimport com.simplefanc.voj.common.pojo.dto.JudgeDTO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\n\nimport java.util.HashMap;\n\npublic interface JudgeService {\n\n    void localJudge(Judge judge);\n\n    void remoteJudge(JudgeDTO toJudge);\n\n    Boolean compileSpj(String code, Long pid, String spjLanguage, HashMap<String, String> extraFiles)\n            throws SystemException;\n\n    Boolean compileInteractive(String code, Long pid, String interactiveLanguage, HashMap<String, String> extraFiles)\n            throws SystemException;\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/service/SystemConfigService.java",
    "content": "package com.simplefanc.voj.judger.service;\n\nimport java.util.HashMap;\n\npublic interface SystemConfigService {\n\n    HashMap<String, Object> getSystemConfig();\n\n    HashMap<String, Object> getJudgeServerInfo();\n\n}\n"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/service/impl/JudgeServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.service.impl;\n\nimport com.simplefanc.voj.common.constants.JudgeStatus;\nimport com.simplefanc.voj.common.pojo.dto.JudgeDTO;\nimport com.simplefanc.voj.common.pojo.entity.judge.Judge;\nimport com.simplefanc.voj.common.pojo.entity.problem.Problem;\nimport com.simplefanc.voj.common.pojo.entity.user.UserAcproblem;\nimport com.simplefanc.voj.judger.common.exception.SystemException;\nimport com.simplefanc.voj.judger.dao.ContestRecordEntityService;\nimport com.simplefanc.voj.judger.dao.JudgeEntityService;\nimport com.simplefanc.voj.judger.dao.ProblemEntityService;\nimport com.simplefanc.voj.judger.dao.UserAcproblemEntityService;\nimport com.simplefanc.voj.judger.judge.local.JudgeContext;\nimport com.simplefanc.voj.judger.judge.remote.RemoteJudgeContext;\nimport com.simplefanc.voj.judger.service.JudgeService;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2022/3/12 15:54\n * @Description:\n */\n@Service\n@RequiredArgsConstructor\npublic class JudgeServiceImpl implements JudgeService {\n\n    private final JudgeEntityService judgeEntityService;\n\n    private final ProblemEntityService problemEntityService;\n\n    private final UserAcproblemEntityService userAcproblemEntityService;\n\n    private final ContestRecordEntityService contestRecordEntityService;\n\n    private final JudgeContext judgeContext;\n\n    private final RemoteJudgeContext remoteJudgeContext;\n\n    @Override\n    public void localJudge(Judge judge) {\n        Problem problem = problemEntityService.getById(judge.getPid());\n        // 【进行判题操作】！！！\n        judgeContext.judge(judge, problem);\n\n        // 更新该次提交\n        judgeEntityService.updateById(judge);\n\n        if (judge.getStatus().intValue() != JudgeStatus.STATUS_SUBMITTED_FAILED.getStatus()) {\n            // 更新其它表\n            // 非比赛提交\n            if (judge.getCid() == 0) {\n                // 如果是AC，就更新 user_acproblem表\n                if (JudgeStatus.STATUS_ACCEPTED.getStatus().equals(judge.getStatus())) {\n                    userAcproblemEntityService.saveOrUpdate(new UserAcproblem().setPid(judge.getPid())\n                            .setUid(judge.getUid()).setSubmitId(judge.getSubmitId()));\n                }\n            } else {\n                // 如果是比赛提交\n                contestRecordEntityService.updateContestRecord(judge);\n            }\n        }\n    }\n\n    @Override\n    public void remoteJudge(JudgeDTO toJudge) {\n        remoteJudgeContext.judge(toJudge);\n    }\n\n    @Override\n    public Boolean compileSpj(String code, Long pid, String spjLanguage, HashMap<String, String> extraFiles)\n            throws SystemException {\n        return judgeContext.compileSpj(code, pid, spjLanguage, extraFiles);\n    }\n\n    @Override\n    public Boolean compileInteractive(String code, Long pid, String interactiveLanguage,\n                                      HashMap<String, String> extraFiles) throws SystemException {\n        return judgeContext.compileInteractive(code, pid, interactiveLanguage, extraFiles);\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/java/com/simplefanc/voj/judger/service/impl/SystemConfigServiceImpl.java",
    "content": "package com.simplefanc.voj.judger.service.impl;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.json.JSONUtil;\nimport cn.hutool.system.oshi.OshiUtil;\nimport com.simplefanc.voj.judger.common.constants.JudgeServerConstant;\nimport com.simplefanc.voj.judger.judge.local.SandboxRun;\nimport com.simplefanc.voj.judger.service.SystemConfigService;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Date;\nimport java.util.HashMap;\n\n/**\n * @Author: chenfan\n * @Date: 2021/12/3 20:15\n * @Description:\n */\n@Service\npublic class SystemConfigServiceImpl implements SystemConfigService {\n    @Value(\"${voj-judge-server.max-task-num}\")\n    private Integer maxTaskNum;\n\n    @Value(\"${voj-judge-server.remote-judge.open}\")\n    private Boolean isOpenRemoteJudge;\n\n    @Value(\"${voj-judge-server.remote-judge.max-task-num}\")\n    private Integer remoteJudgeMaxTaskNum;\n\n    @Value(\"${voj-judge-server.name}\")\n    private String judgeServerName;\n\n    @Value(\"${voj-judge-server.version}\")\n    private String judgeServerVersion;\n\n    @Override\n    public HashMap<String, Object> getSystemConfig() {\n        HashMap<String, Object> result = new HashMap<>();\n        // cpu核数\n        int cpuCores = Runtime.getRuntime().availableProcessors();\n\n        double cpuLoad = 100 - OshiUtil.getCpuInfo().getFree();\n        // cpu使用率\n        String percentCpuLoad = String.format(\"%.2f\", cpuLoad) + \"%\";\n        // 总内存\n        double totalVirtualMemory = OshiUtil.getMemory().getTotal();\n        // 空闲内存\n        double freePhysicalMemorySize = OshiUtil.getMemory().getAvailable();\n        double value = freePhysicalMemorySize / totalVirtualMemory;\n        // 内存使用率\n        String percentMemoryLoad = String.format(\"%.2f\", (1 - value) * 100) + \"%\";\n\n        result.put(\"cpuCores\", cpuCores);\n        result.put(\"percentCpuLoad\", percentCpuLoad);\n        result.put(\"percentMemoryLoad\", percentMemoryLoad);\n        return result;\n    }\n\n    @Override\n    public HashMap<String, Object> getJudgeServerInfo() {\n        HashMap<String, Object> res = new HashMap<>();\n        res.put(\"version\", judgeServerVersion);\n        res.put(\"currentTime\", new Date());\n        res.put(\"judgeServerName\", judgeServerName);\n        res.put(\"cpu\", Runtime.getRuntime().availableProcessors());\n        res.put(\"languages\", JudgeServerConstant.LANGUAGE_LIST);\n\n        if (maxTaskNum == -1) {\n            res.put(\"maxTaskNum\", Runtime.getRuntime().availableProcessors() + 1);\n        } else {\n            res.put(\"maxTaskNum\", maxTaskNum);\n        }\n        if (isOpenRemoteJudge) {\n            res.put(\"isOpenRemoteJudge\", true);\n            if (remoteJudgeMaxTaskNum == -1) {\n                res.put(\"remoteJudgeMaxTaskNum\", Runtime.getRuntime().availableProcessors() * 2 + 1);\n            } else {\n                res.put(\"remoteJudgeMaxTaskNum\", remoteJudgeMaxTaskNum);\n            }\n        }\n\n        String versionResp;\n        try {\n            versionResp = SandboxRun.getRestTemplate().getForObject(SandboxRun.getSandboxBaseUrl() + \"/version\",\n                    String.class);\n        } catch (Exception e) {\n            res.put(\"SandBoxMsg\", MapUtil.builder().put(\"error\", e.getMessage()).map());\n            return res;\n        }\n\n        res.put(\"SandBoxMsg\", JSONUtil.parseObj(versionResp));\n        return res;\n    }\n\n}"
  },
  {
    "path": "voj-judger/src/main/resources/application.yml",
    "content": "voj-judge-server:\n  version: 2023/10/14\n  name: ${JUDGE_SERVER_NAME:voj-judger-1} # 判题机名字 唯一不可重复！！！\n  ip: ${JUDGE_SERVER_IP:127.0.0.1} # -1表示使用默认本地ipv4，若是部署其它服务器，务必使用公网ip\n  port: ${JUDGE_SERVER_PORT:8080}\n  nacos-url: ${NACOS_URL:127.0.0.1:8848}  # nacos地址\n  max-task-num: ${MAX_TASK_NUM:-1} # -1表示最大并行任务数为cpu核心数+1\n  remote-judge:\n    open: ${REMOTE_JUDGE_OPEN:true} # 当前判题服务器是否开启远程虚拟判题功能\n    max-task-num: ${REMOTE_JUDGE_MAX_TASK_NUM:-1}  # -1表示最大并行任务数为cpu核心数*2+1\n\nserver:\n  port: ${voj-judge-server.port}\n\nspring:\n  application:\n    name: @artifactId@\n  cloud:\n    nacos:\n      discovery:\n        server-addr: ${NACOS_URL:127.0.0.1:8848}\n        username: ${NACOS_USERNAME:nacos}\n        password: ${NACOS_PASSWORD:nacos}\n      config:\n        group: DEFAULT_GROUP\n        server-addr: ${spring.cloud.nacos.discovery.server-addr}\n        username: ${NACOS_USERNAME:nacos}\n        password: ${NACOS_PASSWORD:nacos}\n  config:\n    import:\n      - nacos:voj-${spring.profiles.active}.yml\n  profiles:\n    active: @profiles.active@\n\n  datasource:\n    username: ${voj.db.username}\n    password: ${voj.db.password}\n    url: jdbc:mysql://${voj.db.public-host}:${voj.db.port}/${voj.db.name}?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true\n    driver-class-name: com.mysql.cj.jdbc.Driver\n    type: com.alibaba.druid.pool.DruidDataSource\n    initial-size: 10 # 初始化时建立物理连接的个数。初始化发生在显示调用init方法，或者第一次getConnection时\n    min-idle: 20 # 最小连接池数量\n    maxActive: 40 # 最大连接池数量\n    maxWait: 60000 # 获取连接时最大等待时间，单位毫秒。配置了maxWait之后，缺省启用公平锁，并发效率会有所下降，如果需要可以通过配置\n    timeBetweenEvictionRunsMillis: 60000 # 关闭空闲连接的检测时间间隔.Destroy线程会检测连接的间隔时间，如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。\n    minEvictableIdleTimeMillis: 300000 # 连接的最小生存时间.连接保持空闲而不被驱逐的最小时间\n    validationQuery: SELECT 1 FROM DUAL # 验证数据库服务可用性的sql.用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL\n    testWhileIdle: true # 申请连接时检测空闲时间，根据空闲时间再检测连接是否有效.建议配置为true，不影响性能，并且保证安全性。申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRun\n    testOnBorrow: false # 申请连接时直接检测连接是否有效.申请连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。\n    testOnReturn: false # 归还连接时检测连接是否有效.归还连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。\n    poolPreparedStatements: true # 开启PSCache\n    maxPoolPreparedStatementPerConnectionSize: 20 #设置PSCache值\n    connectionErrorRetryAttempts: 3 # 连接出错后再尝试连接三次\n    breakAfterAcquireFailure: true # 数据库服务宕机自动重连机制\n    timeBetweenConnectErrorMillis: 300000 # 连接出错后重试时间间隔\n    asyncInit: true # 异步初始化策略\n    remove-abandoned: true # 是否自动回收超时连接\n    remove-abandoned-timeout: 1800 # 超时时间(以秒数为单位)\n    transaction-query-timeout: 10000 # 事务超时时间\n    filters: stat,wall,log4j #数据库日志\n    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500\n\nmybatis-plus:\n  mapper-locations: classpath*:com/simplefanc/voj/judger/mapper/xml/**Mapper.xml\n  type-aliases-package: com.simplefanc.voj.common.pojo.entity\n  # 关闭打印 mybatis-plus 的 LOGO\n  global-config:\n    banner: false\n\nlogging:\n  level:\n    com.alibaba.nacos: error\n    root: error\n  #  config: classpath:logback-spring.xml\n  file:\n    path: ./log\n\n# 暴露监控\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: '*'\n\n---\nspring:\n  config:\n    activate:\n      on-profile: dev\nlogging:\n  level:\n    com.alibaba.nacos: error\n    root: info\n  file:\n    path: ./log"
  },
  {
    "path": "voj-judger/src/main/resources/banner.txt",
    "content": "${AnsiColor.BRIGHT_YELLOW}\n\n ___      ___ ________        ___\n|\\  \\    /  /|\\   __  \\      |\\  \\\n\\ \\  \\  /  / | \\  \\|\\  \\     \\ \\  \\\n \\ \\  \\/  / / \\ \\  \\\\\\  \\  __ \\ \\  \\\n  \\ \\    / /   \\ \\  \\\\\\  \\|\\  \\\\_\\  \\\n   \\ \\__/ /     \\ \\_______\\ \\________\\\n    \\|__|/       \\|_______|\\|________|\n    Virtual Online Judge(VOJ) - Judger\n           @Author chenfan\n           @Latest Update ${voj-judge-server.version}\n\n"
  },
  {
    "path": "voj-judger/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<configuration>\n    <!-- 属性文件:在properties文件中找到对应的配置项 -->\n    <springProperty scope=\"context\" name=\"logging.file.path\" source=\"logging.file.path\"/>\n    <springProperty scope=\"context\" name=\"spring.application.name\" source=\"spring.application.name\"/>\n\n    <!--控制台打印-->\n    <appender name=\"consoleLog\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder class=\"ch.qos.logback.classic.encoder.PatternLayoutEncoder\">\n            <!--格式化输出（配色）：%d表示日期，%thread表示线程名，%-5level：级别从左显示5个字符宽度%msg：日志消息，%n是换行符-->\n            <pattern>\n                %yellow(%d{yyyy-MM-dd HH:mm:ss}) %red([%thread]) %highlight(%-5level) %cyan(%logger{50}) - %magenta(%msg) %n\n            </pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n    </appender>\n\n    <!--根据日志级别分离日志，分别输出到不同的文件-->\n    <appender name=\"fileInfoLog\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>error</level>\n            <onMatch>DENY</onMatch>\n            <onMismatch>ACCEPT</onMismatch>\n        </filter>\n        <encoder class=\"ch.qos.logback.classic.encoder.PatternLayoutEncoder\">\n            <pattern>\n                %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n\n            </pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n        <!--滚动策略-->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <!--按时间保存日志 修改格式可以按小时、按天、月来保存-->\n            <fileNamePattern>${logging.file.path}/${spring.application.name}.info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <!--保存时长-->\n            <maxHistory>7</maxHistory>\n            <!-- 单个文件最大-->\n            <maxFileSize>200MB</maxFileSize>\n            <!--总大小-->\n            <totalSizeCap>1GB</totalSizeCap>\n        </rollingPolicy>\n    </appender>\n\n    <appender name=\"fileErrorLog\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>error</level>\n        </filter>\n        <encoder>\n            <pattern>\n                %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n\n            </pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n        <!--滚动策略-->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <!--路径-->\n            <fileNamePattern>${logging.file.path}/${spring.application.name}.error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <!--保存时长-->\n            <maxHistory>7</maxHistory>\n            <!-- 单个文件最大-->\n            <maxFileSize>200MB</maxFileSize>\n            <!--总大小-->\n            <totalSizeCap>1GB</totalSizeCap>\n        </rollingPolicy>\n    </appender>\n\n    <root level=\"info\">\n        <appender-ref ref=\"consoleLog\"/>\n        <appender-ref ref=\"fileInfoLog\"/>\n        <appender-ref ref=\"fileErrorLog\"/>\n    </root>\n\n    <root level=\"error\">\n        <appender-ref ref=\"consoleLog\"/>\n        <appender-ref ref=\"fileErrorLog\"/>\n    </root>\n\n</configuration>"
  }
]