Vue + Koa строит ACM OJ

Redis задняя часть внешний интерфейс Vue.js

Мне потребовалось более двух месяцев, чтобыlazzzisЗавершена вторая версия Putong OJ. Из-за напряженного весеннего набора и выпускного дизайна проект был официально запущен недавно.

Онлайн-адрес проекта:acm.cjlu.edu.cn/

Адрес фронтенда проекта:GitHub.com/ACM309/PU для…

Адрес Backenc проекта:GitHub.com/ACM309/PU для…

Пожалуйста, попросите звезду здесь (^o^)/~

Интерфейсная архитектура этого OJ — Vue2.5 + vue-router + vuex + axios + iview + stylus + webpack3.6. Бэкенд-архитектура — Koa2 + MongoDB + Redis.

История развития

Наш школьный acm начал поздно, самый ранний OJ был модифицирован из Hust OJ, и интерфейс был относительно грубым. Два года назад капитан acm того сеанса изначально решил использовать Vue + Go для переписывания OJ, но по каким-то причинам он убежал и, в конце концов, создал только форк OJ с открытым исходным кодом. год назад,lazzzisНачал рефакторинг OJ, внедрил узел Vue + и разработал первую версию Putong OJ. В этом году, в связи с возросшими функциональными требованиями преподавателя, некоторыми изменениями в внутренней структуре данных и неудовлетворенностью первой версией, я провел рефакторинг с помощью lazzis и разработал версию Putong OJ V2.

Технический отбор

Я думал об использовании React для разработки (ну, если честно, я не знал React, когда писал OJ), но Vue прост в освоении и богат китайскими ресурсами, поэтому я решил использовать ведро семейства Vue. Первоначально vue-resource официально рекомендовался в vue 1.0. Позже, в vue 2.0, vue-resource больше не рекомендовался официально, но axios был рекомендован для взаимодействия с интерфейсом и сервером. В начале разработки в качестве UI-библиотеки vue использовался element, а затем iview. На самом деле, эти две библиотеки пользовательского интерфейса очень похожи, обе выполнены в стиле муравьиного дизайна, а API относительно непротиворечивы.На мой взгляд, большая разница в том, что компонент элемента больше, а iview меньше (визуальный размер, element small Это примерно тот же размер, что и у iview по умолчанию).

На самом деле, нам не очень нравится Java в серверной части, поэтому в итоге мы использовали узел, который легковесен и удобен. База данных использует MongoDB, в основном для облегчения операций js, а также использует Redis для кэширования данных и простой очереди сообщений.

предварительный просмотр

В качестве основного цвета выбран фиолетовый, который особенно нравится Лазззису.

реализовать функцию

OJ делится на веб-сторону и сторону оценки вопросов.Здесь мы в основном анализируем веб-сторону и сторону оценки вопросов.Конец суда AcdreamМагия изменилась. Веб-терминал имеет семь модулей: модуль сообщений, тематический модуль, модуль обсуждения, модуль статуса, модуль ранжирования, модуль соревнований и модуль администратора. Этот OJ предоставляет два типа пользователей: обычные пользователи и пользователи-администраторы. Как следует из названия, обычные пользователи могут только отвечать на вопросы, участвовать в конкурсах, публиковать сообщения, просматривать информацию и т. д. Пользователи-администраторы имеют право добавлять, удалять, изменять и искать информацию, вопросы и конкурсы.

  • модуль сообщений Это домашняя страница OJ, включающая страницу списка и страницу сведений о сообщениях, в основном сообщения, опубликованные администратором.
  • тематический модуль Один из основных модулей OJ, включая страницу списка тем и страницу сведений о теме, на странице сведений о теме есть 6 вкладок, описание темы, отправка, моя отправка, статистика, редактирование, тестовые данные. Вкладки «Редактировать» и «Проверить данные» видны только администраторам.
  • дискуссионный модуль По сути, это дискуссионная площадка, где пользователи могут оставлять комментарии.
  • Модуль состояния Результат оценки вопроса, заданного пользователем.
  • модуль ранжирования Рейтинг пользователей, с функцией группировки, учителям удобно подсчитывать результаты
  • Модуль соревнований Один из основных модулей, включая список соревнований и страницу сведений о соревнованиях, страница сведений о соревнованиях имеет 6 вкладок: обзор, название, представление, статус, рейтинг, редактирование. Страница редактирования видна только администраторам.
  • модуль администратора Один из основных модулей, он содержит четыре функциональные страницы для создания сообщений, создания тем, создания конкурсов и управления пользователями.

Я заранее зарегистрировал для всех учетную запись обычного пользователя, номер учетной записи 123456 и пароль 123456. Добро пожаловать на пробу.

внешний интерфейс

Давайте сначала взглянем на структуру внешнего интерфейса проекта, построенную с помощью шаблона vue-cli.

├── dist // 生成打包好的文件
│   ├── static
│   │   ├── css
│   │   ├── fonts
│   │   ├── img
│   │   └── js  
│   └── index.html
└── src
    ├── main.js // 项目入口
    ├── router // 路由文件,说明了各个路由将会使用的组件
    │   ├── index.js // router 的配置以及引用组件
    │   └── routes.js // 定义各个路由
    ├── assets // 网站 logo 图资源
    ├── components // 一些小组件
    ├── store // vuex 文件
    │   └── modules // 子模块
    ├── utils // js 工具方法
    └── views // 路由对应的组件 (这些组件在 router.js 中都被引入)
        ├── Admin
        ├── Contest
        ├── News
        └── Problem

На фронтенде более 30 страниц, но большинство из них — это только графики, а логика страниц не сложная. iview загружается по запросу, уменьшая размер внешнего пакета. Чтобы обеспечить скорость загрузки первого экрана, на некоторых маршрутах выполняется ленивая загрузка.

// 路由懒加载
const ProblemStatistics = r => require.ensure([], () => r(require('@/views/Problem/Statistics')), 'statistics')
const ProblemEdit = r => require.ensure([], () => r(require('@/views/Problem/ProblemEdit')), 'admin')
const Testcase = r => require.ensure([], () => r(require('@/views/Problem/Testcase')), 'admin')
const ContestEdit = r => require.ensure([], () => r(require('@/views/Contest/ContestEdit')), 'admin')
const NewsEdit = r => require.ensure([], () => r(require('@/views/News/NewsEdit')), 'admin')
const ProblemCreate = r => require.ensure([], () => r(require('@/views/Admin/ProblemCreate')), 'admin')
const ContestCreate = r => require.ensure([], () => r(require('@/views/Admin/ContestCreate')), 'admin')
const NewsCreate = r => require.ensure([], () => r(require('@/views/Admin/NewsCreate')), 'admin')
const UserManage = r => require.ensure([], () => r(require('@/views/Admin/UserManage/Usermanage')), 'admin')
const UserEdit = r => require.ensure([], () => r(require('@/views/Admin/UserManage/UserEdit')), 'admin')
const GroupEdit = r => require.ensure([], () => r(require('@/views/Admin/UserManage/GroupEdit')), 'admin')
const AdminEdit = r => require.ensure([], () => r(require('@/views/Admin/UserManage/AdminEdit')), 'admin')
const TagEdit = r => require.ensure([], () => r(require('@/views/Admin/UserManage/TagEdit')), 'admin')

В то же время интерфейс использует множество сторонних компонентов для достижения небольших требований.

  • vue-echarts: Основанный на компоненте Vue Echarts, он используется в проекте для отображения диаграммы статистического анализа представленных результатов.
  • vue2-editor: Богатый текстовый редактор на основе Vue, используемый для редактирования заголовка содержимого и поддерживающих основных функций, таких как загрузка изображений.
  • Vue.Draggable: Компонент перетаскивания на основе Vue, с помощью которого администраторам удобно вносить изменения в порядок тем конкурса.
  • vue-clipboard2: буфер обмена на основе Vue, удобный для копирования кода пользователями.
  • vuex-router-sync: сделать $route vue-router доступным в состоянии в vuex.
  • highlight.js: Код на странице выделен.

задняя часть

├── config // 项目配置(数据库等)
├── model // 数据库 model
├── routes // 后端路由
├── controllers // 主要功能实现
├── services // 主要服务(判题、邮件提醒、更新)
├── utils // js 工具函数
├── test // 测试
├── app.js
└── manage.js

Бэкенд разработан с использованием koa2, с использованием async/await вместо обратных вызовов, чтобы избежать ада обратных вызовов.Основные данные хранятся в MongoDB с использованием пакета mongoose узла. Чтобы избежать проблемы с высокой степенью параллелизма, вызванной одновременной отправкой вопросов несколькими людьми, интерфейс соответствует дизайну RESTful и использует Redis для кэширования вопросов суждения. Вопросы, отправленные пользователем, будут поступать в Redis, а затем всплывать в очереди один за другим и передавать их терминалу суждения для обработки. В последний час обычного конкурса ACM рейтинг будет закрыт (рейтинг и вопросы ac больше не будут обновляться, но будет обновляться количество заявок от пользователей).Redis также используется здесь для обновления рейтинга конкурса, и только данные будут сохраняться во время соревнований.В редисе и реализуй бан,и сохраняй всю инфу игры в монго после игры.

// 比赛时返回比赛排行榜
const ranklist = async (ctx) => {
  const contest = ctx.state.contest
  const ranklist = ctx.state.contest.ranklist
  let res
  const deadline = 60 * 60 * 1000
  await Promise.all(Object.keys(ranklist).map((uid) =>
    User
      .findOne({ uid })
      .exec()
      .then(user => { ranklist[user.uid].nick = user.nick })))

  if (Date.now() + deadline < contest.end) {
    // 若比赛未进入最后一小时,最新的 ranklist 推到 redis 里
    const str = JSON.stringify(ranklist)
    await redis.set(`oj:ranklist:${contest.cid}`, str) // 更新该比赛的最新排名信息
    res = ranklist
  } else if (!isAdmin(ctx.session.profile) &&
    Date.now() + deadline > contest.end &&
    Date.now() < contest.end) {
    // 比赛最后一小时封榜,普通用户只能看到题目提交的变化
    const mid = await redis.get(`oj:ranklist:${contest.cid}`) // 获取 redis 中该比赛的排名信息
    res = JSON.parse(mid)
    Object.entries(ranklist).map(([uid, problems]) => {
      Object.entries(problems).map(([pid, sub]) => {
        if (sub.wa < 0) {
          res[uid][pid] = {
            wa: sub.wa
          }
        }
      })
    })
    const str = JSON.stringify(res)
    await redis.set(`oj:ranklist:${contest.cid}`, str) // 将更新后的 ranklist 更新到 redis
    // 比赛结束
    res = ranklist
  }
  ctx.body = {
    ranklist: res
  }
}

Проект использует Docker для развертывания на одном клике. Напишите DockerFile, чтобы сделать настраивания зеркалов в Docker-Configure Configure все необходимые зеркала.Процесс развертывания

наконец

Место ограничено и не может показать больше контента.Если вам интересно, вы можете перейти по адресу проекта, чтобы прочитать исходный код.Конечно, если вы считаете проект неплохим 👏, поставьте звезду ⭐️, чтобы поощрить его~