Vue Koa Builds ACM OJ

Keywords: Vue Redis Database JSON

It took me more than two months. lazzzis Completed the second version of Putong OJ, because in the middle of the busy spring recruitment and graduation design, the project was officially launched recently.

Project Online Address: http://acm.cjlu.edu.cn/

Project Front End Address: https://github.com/acm309/PutongOJ-FE

Project Backend Address: https://github.com/acm309/PutongOJ

Let's look for star ah (^ o ^)/~

This OJ front-end architecture is Vue2.5 vue-router vuex Axios iView stylus webpack 3.6 The back-end architecture is Koa2 MongoDB redis

Development background

Our acm started late. The earliest OJ was changed from Hust OJ. The interface was rough. Two years ago, the captain of the acm decided to rewrite OJ with Vue Go, but for some reason, he ran away and finally only fork an open source OJ. A year ago, lazzzis The first version of Putong OJ was developed by using Vue node to reconstruct OJ. This year, because the teacher increased some functional requirements, coupled with some changes in the back-end data structure, and was not satisfied with the first version, I reconstructed with lazzzis and developed the Putong OJ V2 version.

Technology selection

Consider using React to develop (well, I won't do React when I write OJ to be honest), but Vue is easy to use and rich in Chinese resources, so I decided to use the Vue family bucket. In the beginning, vue-resource was recommended officially at Vue 1.0, and then at 2.0, Vue officially no longer recommended vue-resource, but recommended using axios for front-end and back-end communication. In the early stage of development, elementation was used as the UI Library of vue, and then iView was used instead. In fact, these two UI libraries are quite similar, both of them are ant-design style, and the api is quite the same. In my eyes, the big difference is that the components of element are bigger and the iView is smaller (the size of element small is almost the same as the default of iview).

In fact, we don't like Java very much, and we end up with a lightweight and convenient node. The database uses MongoDB, which is mainly convenient for js operation. At the same time, it uses redis as data cache and makes a simple message queue.

preview

The theme color uses Sauvignon, a favorite of lazzzis.

Realization function

OJ is divided into web end and judgment end. This side mainly analyses the web end. The judgment end is from the web end. Acdream's Judgment End The devil changed. There are seven modules in the web end: message module, topic module, discussion module, status module, ranking module, competition module and administrator module. This OJ provides two kinds of users, ordinary users and administrator users. As the name implies, ordinary users can only answer questions, participate in competitions, post, view information, etc. Administrator users have the right to add, delete and check information, topics, competitions, etc.

  • Message module It is the home page of OJ, which contains list page and message details page. It is mainly the message issued by administrator.
  • Topic module One of the core modules of OJ is the title list page and the title details page. There are six tab pages in the title details page, including title description, submission, my submission, statistics, editing and testing data. The two tab pages of editing and testing data are only visible to administrators.
  • Discussion module In fact, it's a forum where users can post comments.
  • State module The user submits the result of the question.
  • Ranking module User ranking, grouping function, easy for teachers to count results
  • Game module One of the core modules, including the competition list and details page, the competition details page has six tab pages, overview, title, submission, status, ranking, editing. The edit page is only visible to administrators.
  • Administrator module One of the core modules contains four function pages: creating messages, creating topics, creating competitions, and managing users.

We have registered an ordinary user account in advance, account 123456, password 123456. Welcome to try it out.

Front end

Let's look at the front-end project structure and build it with scaffolding vue-cli

├── dist // Generate packaged files
│   ├── static
│   │   ├── css
│   │   ├── fonts
│   │   ├── img
│   │   └── js  
│   └── index.html
└── src
    ├── main.js // Project entry
    ├── router // A routing file that describes the components that each routing will use
    │   ├── index.js // router configuration and reference components
    │   └── routes.js // Define individual routes
    ├── assets // Website logo Map Resources
    ├── components // Some small components
    ├── store // vuex file
    │   └── modules // Sub module
    ├── utils // js tool method
    └── views // Routing the corresponding components (these components are introduced in router.js)
        ├── Admin
        ├── Contest
        ├── News
        └── Problem

There are more than 30 pages in the front end, but most of them are only charts, and the logic of the pages is not complicated. iview loads on demand to reduce the size of front-end packaging. In order to ensure the loading speed of the first screen, some routes are lazily loaded.

// Routing Lazy Loading
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')

At the same time, the front end uses many third-party components to achieve small requirements.

  • vue-echarts: A Vue-based Echarts component used in projects to show statistical analysis of submitted results.
  • vue2-editor: Vue-based rich text editor for editing topic content, supporting basic functions such as image uploading.
  • Vue.Draggable: Draggable component based on Vue to facilitate administrators to change the order of contest topics.
  • Vue-clipboard 2: Clipboard based on Vue, which makes it easy for users to copy code.
  • vuex-router-sync: enables vue-router's $route to be accessed by state in vuex.
  • highlight.js: The code on the page is highlighted.

back-end

├── config // Project configuration (database, etc.)
├── model // Database model
├── routes // Back-end routing
├── controllers // Main Function Realization
├── services // Main Services (Judgment, Email Reminder, Update)
├── utils // js tool function
├── test // test
├── app.js
└── manage.js

The back end is developed using koa2, using async/await instead of callback, avoiding callback hell. The main data is saved in mongoose package of the node used in MonoDB. In order to avoid the problem of high concurrency caused by multiple people submitting topics at the same time, the interface follows RESTful design and uses redis to make queue caching for the judgement. User submissions will enter redis and pop-up queues will be handled at the decision end. In the last hour of normal ACM competition, the league tables will be sealed (no update of rankings and ac topics, but the number of submissions of users will be updated). Here, the league tables will be updated with redis. During the competition, only the data will be saved in redis, and the league tables will be sealed. After the game, all the information of the competition will be saved to M. Ongo.

// Return to the league table during the game
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) {
    // If the race does not enter the last hour, the latest ranklist is pushed to redis.
    const str = JSON.stringify(ranklist)
    await redis.set(`oj:ranklist:${contest.cid}`, str) // Update the latest ranking information of the competition
    res = ranklist
  } else if (!isAdmin(ctx.session.profile) &&
    Date.now()   deadline > contest.end &&
    Date.now() < contest.end) {
    // When the final hour of the competition is closed, the average user can only see the changes in the submission of the title.
    const mid = await redis.get(`oj:ranklist:${contest.cid}`) // Get the ranking information of the game in 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) // Update the updated ranklist to redis
    // End of game
    res = ranklist
  }
  ctx.body = {
    ranklist: res
  }
}

The project uses docker for one-click deployment. Dockerfile is written to customize the image on the web side and configure all the images needed for the project in docker-compose. Deployment process

Last

If you are interested, you can enter the project address to read the source code. Of course, if you think the project is good, give a star to encourage you.~

Posted by mazsolt on Tue, 24 Sep 2019 20:29:17 -0700