Bläddra i källkod

initial commit

TianChenjiang 5 år sedan
incheckning
dcf77f5c83
96 ändrade filer med 4624 tillägg och 0 borttagningar
  1. 33 0
      .gitignore
  2. 117 0
      .mvn/wrapper/MavenWrapperDownloader.java
  3. BIN
      .mvn/wrapper/maven-wrapper.jar
  4. 2 0
      .mvn/wrapper/maven-wrapper.properties
  5. 4 0
      Dockerfile
  6. 60 0
      README.md
  7. 310 0
      mvnw
  8. 182 0
      mvnw.cmd
  9. 192 0
      pom.xml
  10. 21 0
      settings.xml
  11. 21 0
      src/main/java/com/nju/edu/eval/EvalApplication.java
  12. 80 0
      src/main/java/com/nju/edu/eval/aspect/auth/AuthAspect.java
  13. 15 0
      src/main/java/com/nju/edu/eval/aspect/auth/AuthorizationImpl.java
  14. 18 0
      src/main/java/com/nju/edu/eval/aspect/auth/Authorized.java
  15. 26 0
      src/main/java/com/nju/edu/eval/config/AdditionalWebConfig.java
  16. 44 0
      src/main/java/com/nju/edu/eval/config/JsonConfig.java
  17. 35 0
      src/main/java/com/nju/edu/eval/config/LocalDateTimeConfig.java
  18. 14 0
      src/main/java/com/nju/edu/eval/config/MySQL57InnoDBDialect.java
  19. 40 0
      src/main/java/com/nju/edu/eval/config/OpenApiConfig.java
  20. 24 0
      src/main/java/com/nju/edu/eval/config/RestTemplateConfig.java
  21. 69 0
      src/main/java/com/nju/edu/eval/config/SwaggerConfig.java
  22. 31 0
      src/main/java/com/nju/edu/eval/data/dao/ExamDao.java
  23. 44 0
      src/main/java/com/nju/edu/eval/data/dao/RecordDao.java
  24. 20 0
      src/main/java/com/nju/edu/eval/data/dao/SampleExamPaperDao.java
  25. 22 0
      src/main/java/com/nju/edu/eval/data/dao/ScoreDao.java
  26. 30 0
      src/main/java/com/nju/edu/eval/data/dao/UserDao.java
  27. 110 0
      src/main/java/com/nju/edu/eval/data/entity/Exam.java
  28. 43 0
      src/main/java/com/nju/edu/eval/data/entity/GeneralReport.java
  29. 73 0
      src/main/java/com/nju/edu/eval/data/entity/QuestionRecord.java
  30. 42 0
      src/main/java/com/nju/edu/eval/data/entity/SampleExamPaper.java
  31. 80 0
      src/main/java/com/nju/edu/eval/data/entity/Score.java
  32. 49 0
      src/main/java/com/nju/edu/eval/data/entity/User.java
  33. 21 0
      src/main/java/com/nju/edu/eval/exception/MyServiceException.java
  34. 19 0
      src/main/java/com/nju/edu/eval/exception/MyServiceExceptionHandler.java
  35. 20 0
      src/main/java/com/nju/edu/eval/exception/ThirdPartyServiceException.java
  36. 50 0
      src/main/java/com/nju/edu/eval/manager/JudgeManager.java
  37. 67 0
      src/main/java/com/nju/edu/eval/manager/QuestionManager.java
  38. 38 0
      src/main/java/com/nju/edu/eval/model/vo/exam/CreateExamVO.java
  39. 36 0
      src/main/java/com/nju/edu/eval/model/vo/exam/ExamInfoVO.java
  40. 18 0
      src/main/java/com/nju/edu/eval/model/vo/exam/ExamListAndNumVO.java
  41. 53 0
      src/main/java/com/nju/edu/eval/model/vo/exam/ExamVO.java
  42. 20 0
      src/main/java/com/nju/edu/eval/model/vo/exam/NewExamVO.java
  43. 17 0
      src/main/java/com/nju/edu/eval/model/vo/exam/QuestionScoreMapVO.java
  44. 20 0
      src/main/java/com/nju/edu/eval/model/vo/judge/CodeJudgeVO.java
  45. 23 0
      src/main/java/com/nju/edu/eval/model/vo/judge/ObjectiveJudgeResOfExerciseVO.java
  46. 23 0
      src/main/java/com/nju/edu/eval/model/vo/judge/ObjectiveJudgeResVO.java
  47. 20 0
      src/main/java/com/nju/edu/eval/model/vo/judge/ObjectiveJudgeVO.java
  48. 18 0
      src/main/java/com/nju/edu/eval/model/vo/judge/ObjectiveJudgeVOOfExercise.java
  49. 91 0
      src/main/java/com/nju/edu/eval/model/vo/question/CompleteQuestionVO.java
  50. 19 0
      src/main/java/com/nju/edu/eval/model/vo/question/QuestionAndScoreVO.java
  51. 16 0
      src/main/java/com/nju/edu/eval/model/vo/question/QuestionStemListVO.java
  52. 70 0
      src/main/java/com/nju/edu/eval/model/vo/question/QuestionStemVO.java
  53. 33 0
      src/main/java/com/nju/edu/eval/model/vo/record/QuestionRecordVO.java
  54. 27 0
      src/main/java/com/nju/edu/eval/model/vo/score/PersonalScoreReportVO.java
  55. 19 0
      src/main/java/com/nju/edu/eval/model/vo/user/UpdateUserVO.java
  56. 20 0
      src/main/java/com/nju/edu/eval/model/vo/user/UserVO.java
  57. 58 0
      src/main/java/com/nju/edu/eval/publicdatas/enums/CodeJudgeResultType.java
  58. 17 0
      src/main/java/com/nju/edu/eval/publicdatas/enums/ExamState.java
  59. 14 0
      src/main/java/com/nju/edu/eval/publicdatas/enums/QuestionType.java
  60. 15 0
      src/main/java/com/nju/edu/eval/publicdatas/enums/UserRole.java
  61. 116 0
      src/main/java/com/nju/edu/eval/service/ExamService.java
  62. 57 0
      src/main/java/com/nju/edu/eval/service/JudgeService.java
  63. 46 0
      src/main/java/com/nju/edu/eval/service/QuestionService.java
  64. 30 0
      src/main/java/com/nju/edu/eval/service/RecordService.java
  65. 17 0
      src/main/java/com/nju/edu/eval/service/ScoreService.java
  66. 46 0
      src/main/java/com/nju/edu/eval/service/UserService.java
  67. 37 0
      src/main/java/com/nju/edu/eval/service/convertor/ExamConvertor.java
  68. 18 0
      src/main/java/com/nju/edu/eval/service/convertor/RecordConvertor.java
  69. 17 0
      src/main/java/com/nju/edu/eval/service/convertor/ScoreConvertor.java
  70. 22 0
      src/main/java/com/nju/edu/eval/service/convertor/UserConvertor.java
  71. 228 0
      src/main/java/com/nju/edu/eval/service/serviceImpl/ExamServiceImpl.java
  72. 215 0
      src/main/java/com/nju/edu/eval/service/serviceImpl/JudgeServiceImpl.java
  73. 56 0
      src/main/java/com/nju/edu/eval/service/serviceImpl/QuestionServiceImpl.java
  74. 36 0
      src/main/java/com/nju/edu/eval/service/serviceImpl/RecordServiceImpl.java
  75. 27 0
      src/main/java/com/nju/edu/eval/service/serviceImpl/ScoreServiceImpl.java
  76. 75 0
      src/main/java/com/nju/edu/eval/service/serviceImpl/UserServiceImpl.java
  77. 20 0
      src/main/java/com/nju/edu/eval/thirdparty/CodeJudgeThirdPartyService.java
  78. 20 0
      src/main/java/com/nju/edu/eval/thirdparty/ObjectiveQuestionJudgeThirdPartyService.java
  79. 50 0
      src/main/java/com/nju/edu/eval/thirdparty/QuestionThirdPartyService.java
  80. 39 0
      src/main/java/com/nju/edu/eval/thirdparty/thirdpartyImpl/CodeJudgeThirdPartyServiceImpl.java
  81. 41 0
      src/main/java/com/nju/edu/eval/thirdparty/thirdpartyImpl/ObjectiveQuestionJudgeThirdPartyServiceImpl.java
  82. 65 0
      src/main/java/com/nju/edu/eval/thirdparty/thirdpartyImpl/QuestionThirdPartyServiceImpl.java
  83. 46 0
      src/main/java/com/nju/edu/eval/util/BeanCopyUtil.java
  84. 13 0
      src/main/java/com/nju/edu/eval/util/BeanCopyUtilCallBack.java
  85. 26 0
      src/main/java/com/nju/edu/eval/util/InviteCodeUtil.java
  86. 45 0
      src/main/java/com/nju/edu/eval/util/JwtUtil.java
  87. 37 0
      src/main/java/com/nju/edu/eval/web/Response.java
  88. 140 0
      src/main/java/com/nju/edu/eval/web/controller/ExamController.java
  89. 76 0
      src/main/java/com/nju/edu/eval/web/controller/JudgeController.java
  90. 56 0
      src/main/java/com/nju/edu/eval/web/controller/QuestionController.java
  91. 40 0
      src/main/java/com/nju/edu/eval/web/controller/RecordController.java
  92. 36 0
      src/main/java/com/nju/edu/eval/web/controller/ScoreController.java
  93. 43 0
      src/main/java/com/nju/edu/eval/web/controller/UserController.java
  94. 45 0
      src/main/resources/application.yml
  95. 13 0
      src/test/java/com/nju/edu/eval/EvalApplicationTests.java
  96. 17 0
      src/test/java/com/nju/edu/eval/testUtils.java

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 117 - 0
.mvn/wrapper/MavenWrapperDownloader.java

@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+    private static final String WRAPPER_VERSION = "0.5.6";
+    /**
+     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+     */
+    private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+        + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+    /**
+     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+     * use instead of the default one.
+     */
+    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+            ".mvn/wrapper/maven-wrapper.properties";
+
+    /**
+     * Path where the maven-wrapper.jar will be saved to.
+     */
+    private static final String MAVEN_WRAPPER_JAR_PATH =
+            ".mvn/wrapper/maven-wrapper.jar";
+
+    /**
+     * Name of the property which should be used to override the default download url for the wrapper.
+     */
+    private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+    public static void main(String args[]) {
+        System.out.println("- Downloader started");
+        File baseDirectory = new File(args[0]);
+        System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+        // If the maven-wrapper.properties exists, read it and check if it contains a custom
+        // wrapperUrl parameter.
+        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+        String url = DEFAULT_DOWNLOAD_URL;
+        if(mavenWrapperPropertyFile.exists()) {
+            FileInputStream mavenWrapperPropertyFileInputStream = null;
+            try {
+                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+                Properties mavenWrapperProperties = new Properties();
+                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+            } catch (IOException e) {
+                System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+            } finally {
+                try {
+                    if(mavenWrapperPropertyFileInputStream != null) {
+                        mavenWrapperPropertyFileInputStream.close();
+                    }
+                } catch (IOException e) {
+                    // Ignore ...
+                }
+            }
+        }
+        System.out.println("- Downloading from: " + url);
+
+        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+        if(!outputFile.getParentFile().exists()) {
+            if(!outputFile.getParentFile().mkdirs()) {
+                System.out.println(
+                        "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+            }
+        }
+        System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+        try {
+            downloadFileFromURL(url, outputFile);
+            System.out.println("Done");
+            System.exit(0);
+        } catch (Throwable e) {
+            System.out.println("- Error downloading");
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+        if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+            String username = System.getenv("MVNW_USERNAME");
+            char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+            Authenticator.setDefault(new Authenticator() {
+                @Override
+                protected PasswordAuthentication getPasswordAuthentication() {
+                    return new PasswordAuthentication(username, password);
+                }
+            });
+        }
+        URL website = new URL(urlString);
+        ReadableByteChannel rbc;
+        rbc = Channels.newChannel(website.openStream());
+        FileOutputStream fos = new FileOutputStream(destination);
+        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+        fos.close();
+        rbc.close();
+    }
+
+}

BIN
.mvn/wrapper/maven-wrapper.jar


+ 2 - 0
.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

+ 4 - 0
Dockerfile

@@ -0,0 +1,4 @@
+FROM adoptopenjdk/openjdk11:alpine-jre
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]

+ 60 - 0
README.md

@@ -0,0 +1,60 @@
+# seecoder-eval
+backend repo of seecoder-eval system
+
+[API documentation](http://47.111.95.164:8080/swagger-ui/index.html)
+
+
+
+## 数据层设计
+
+实体包括:
+
+- 用户
+- 考试
+- 个人成绩报告(某次考试)
+- 整体成绩报告
+
+
+
+个人成绩报告是用户和考试的中间表,再添加了一些其他的属性
+
+另外生成的中间表还有单项题目得分表,每个个人成绩报告都有一个单项成绩得分表,其实也就是一个题目id和题目得分的映射。
+
+
+
+注:评测系统只维护某次考试包含的题目ID列表,不持久化具体的题目信息,这部分内容仍然交给题库系统进行管理
+
+
+
+
+
+## TODO
+
+出试卷的方式:
+
+- 固定试卷
+- 半自动组卷(固定试卷 + 增删题目)
+- 自行组卷
+  - 提供给组卷人以下信息
+    - 知识点覆盖情况
+    - 题目类型
+    - 支持搜索题目
+
+extendExam 只能由创建该考试的老师或者HR调用
+
+
+
+是否支持通过邀请码直接加入考试?
+
+返回题目id list?还是直接返回题目内容?
+
+咱不需要区分考试和练习,把练习设置为时间较长的考试即可,过了考试/练习时间,均不允许再次提交  
+
+
+
+
+
+组试卷,试卷的相关分值,以及最终试卷的做题结果都由评测系统完成
+用户的做题记录由判题系统在判题的同时,上传到题库系统
+用户的相关做题记录将来分析系统会进行数据分析
+

+ 310 - 0
mvnw

@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        export JAVA_HOME="`/usr/libexec/java_home`"
+      else
+        export JAVA_HOME="/Library/Java/Home"
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+      PRG="$link"
+    else
+      PRG="`dirname "$PRG"`/$link"
+    fi
+  done
+
+  saveddir=`pwd`
+
+  M2_HOME=`dirname "$PRG"`/..
+
+  # make it fully qualified
+  M2_HOME=`cd "$M2_HOME" && pwd`
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --unix "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="`which javac`"
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=`which readlink`
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+      if $darwin ; then
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+      else
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+      fi
+      javaHome="`dirname \"$javaExecutable\"`"
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="`which java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=`cd "$wdir/.."; pwd`
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' < "$1")"
+  fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found .mvn/wrapper/maven-wrapper.jar"
+    fi
+else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+    fi
+    if [ -n "$MVNW_REPOURL" ]; then
+      jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    fi
+    while IFS="=" read key value; do
+      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+      esac
+    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Downloading from: $jarUrl"
+    fi
+    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+    if $cygwin; then
+      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+    fi
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget "$jarUrl" -O "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl -o "$wrapperJarPath" "$jarUrl" -f
+        else
+            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+        fi
+
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaClass=`cygpath --path --windows "$javaClass"`
+        fi
+        if [ -e "$javaClass" ]; then
+            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Compiling MavenWrapperDownloader.java ..."
+                fi
+                # Compiling the Java class
+                ("$JAVA_HOME/bin/javac" "$javaClass")
+            fi
+            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                # Running the downloader
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Running MavenWrapperDownloader.java ..."
+                fi
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

+ 182 - 0
mvnw.cmd

@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%

+ 192 - 0
pom.xml

@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>2.3.5.RELEASE</version>
+		<relativePath/> <!-- lookup parent from repository -->
+	</parent>
+	<groupId>com.nju.edu</groupId>
+	<artifactId>eval</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<name>eval</name>
+	<description>Eval system for seecoder</description>
+
+	<properties>
+		<java.version>1.8</java.version>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-jpa</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-java</artifactId>
+			<version>8.0.18</version>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-devtools</artifactId>
+			<optional>true</optional>
+		</dependency>
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+			<optional>true</optional>
+			<version>1.18.10</version>
+		</dependency>
+
+		<!--swagger-->
+		<dependency>
+			<groupId>io.springfox</groupId>
+			<artifactId>springfox-boot-starter</artifactId>
+			<version>3.0.0</version>
+		</dependency>
+
+		<!-- aop -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-aop</artifactId>
+			<version>2.3.5.RELEASE</version>
+		</dependency>
+
+		<!-- jwt -->
+		<dependency>
+			<groupId>com.auth0</groupId>
+			<artifactId>java-jwt</artifactId>
+			<version>3.11.0</version>
+		</dependency>
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt</artifactId>
+			<version>0.9.1</version>
+		</dependency>
+
+		<!-- validation -->
+		<dependency>
+			<groupId>javax.validation</groupId>
+			<artifactId>validation-api</artifactId>
+			<version>2.0.1.Final</version>
+		</dependency>
+
+		<!-- 字符串工具类 -->
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-text</artifactId>
+			<version>1.9</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>fastjson</artifactId>
+			<version>1.2.75</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.zalando</groupId>
+			<artifactId>problem-spring-web</artifactId>
+			<version>0.23.0</version>
+		</dependency>
+
+
+		<!--第三方知识点库-->
+<!--		<dependency>-->
+<!--			<groupId>cn.seecoder</groupId>-->
+<!--			<artifactId>seecoder-bok</artifactId>-->
+<!--			<version>0.1.2</version>-->
+<!--		</dependency>-->
+
+		<dependency>
+			<groupId>cn.seecoder</groupId>
+			<artifactId>seecoder-bok-client</artifactId>
+			<version>0.1.2</version>
+		</dependency>
+
+		<dependency>
+			<groupId>cn.seecoder</groupId>
+			<artifactId>seecoder-bok-common</artifactId>
+			<version>0.1.2</version>
+		</dependency>
+
+		<!--客观题评测-->
+		<dependency>
+			<groupId>cn.seecoder</groupId>
+			<artifactId>judgement-common</artifactId>
+			<version>0.2.0</version>
+			<scope>compile</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>cn.seecoder</groupId>
+			<artifactId>judgement-client</artifactId>
+			<version>0.2.0</version>
+		</dependency>
+
+		<!--代码题评测-->
+		<dependency>
+			<groupId>cn.seecoder</groupId>
+			<artifactId>codejudge-client</artifactId>
+			<version>0.1.0</version>
+		</dependency>
+
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+			<exclusions>
+				<exclusion>
+					<groupId>org.junit.vintage</groupId>
+					<artifactId>junit-vintage-engine</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+				<configuration>
+					<fork>true</fork>
+					<addResources>true</addResources>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+	<repositories>
+		<repository>
+			<id>nexus</id>
+			<url>>http://nexus.seecoder.cn/repository/maven-releases/</url>
+			<releases>
+				<enabled>true</enabled>
+				<updatePolicy>always</updatePolicy>
+			</releases>
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+		</repository>
+	</repositories>
+
+	<distributionManagement>
+		<repository>
+			<id>nexus</id>
+			<url>http://nexus.seecoder.cn/repository/maven-releases/</url>
+		</repository>
+	</distributionManagement>
+
+</project>

+ 21 - 0
settings.xml

@@ -0,0 +1,21 @@
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                                        http://maven.apache.org/xsd/settings-1.0.0.xsd">
+    <mirrors>
+        <mirror>
+            <id>seecoder-nexus</id>
+            <mirrorOf>*</mirrorOf>
+            <name>seecoder-maven</name>
+            <url>http://nexus.seecoder.cn/repository/maven-releases/</url>
+        </mirror>
+
+        <mirror>
+            <id>alimaven</id>
+            <name>aliyun maven</name>
+            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
+            <mirrorOf>central</mirrorOf>
+        </mirror>
+    </mirrors>
+
+</settings>

+ 21 - 0
src/main/java/com/nju/edu/eval/EvalApplication.java

@@ -0,0 +1,21 @@
+package com.nju.edu.eval;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+/**
+ * @author mars
+ */
+@SpringBootApplication
+@ComponentScan(basePackages = "com.nju.edu.eval.*")
+@EnableAspectJAutoProxy
+@EnableJpaRepositories
+public class EvalApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(EvalApplication.class, args);
+	}
+}

+ 80 - 0
src/main/java/com/nju/edu/eval/aspect/auth/AuthAspect.java

@@ -0,0 +1,80 @@
+package com.nju.edu.eval.aspect.auth;
+
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.interfaces.Claim;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.exception.MyServiceException;
+import com.nju.edu.eval.service.UserService;
+import com.nju.edu.eval.service.convertor.UserConvertor;
+import com.nju.edu.eval.model.vo.user.UpdateUserVO;
+import com.nju.edu.eval.util.JwtUtil;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author mars
+ */
+@Aspect
+@Configuration
+@Order(1)
+public class AuthAspect {
+    private final UserService userService;
+
+    @Autowired
+    public AuthAspect(UserService userService) {
+        this.userService = userService;
+    }
+
+    private User getUserByToken(String token) throws JWTVerificationException {
+        try {
+            Claim claim = JwtUtil.parseJwt(token);
+            return claim.as(User.class);
+        } catch (MyServiceException e) {
+            throw new  MyServiceException(e.getCode(), e.getMessage());
+        }
+    }
+
+    @Before(value = "execution(public * com.nju.edu.eval.web.controller.*.*(..)) && @annotation(authorized)")
+    public void authCheck(JoinPoint joinPoint, Authorized authorized) {
+        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
+        String token = Optional.ofNullable(httpServletRequest.getHeader("Authorization")).
+                orElseThrow(() -> new MyServiceException("A0223", "用户未获得第三方登录授权"));
+
+        User user = getUserByToken(token);
+        // 创建或者更新用户
+        if (userService.existsById(user.getId())) {
+            userService.updateUser(new UpdateUserVO(user.getId(), user.getName(), user.getEmail()));
+        } else {
+            //TODO 这里没有对HR和TEACHER作区分
+            userService.createUser(UserConvertor.convertToVO(user));
+        }
+
+        // 判断切面方法是否包含当前token对应的角色
+        if (!Arrays.stream(authorized.roles()).collect(Collectors.toList()).contains(user.getRole())) {
+            throw new MyServiceException("A0301", "访问未授权");
+        } else {
+            // 将token的对象赋值给切面方法的user参数
+            Object[] objects = joinPoint.getArgs();
+            for (Object o : objects) {
+                if (o instanceof User) {
+                    System.out.println(user);
+                    BeanUtils.copyProperties(user, o);
+                    break;
+                }
+            }
+        }
+    }
+}

+ 15 - 0
src/main/java/com/nju/edu/eval/aspect/auth/AuthorizationImpl.java

@@ -0,0 +1,15 @@
+package com.nju.edu.eval.aspect.auth;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author mars
+ */
+@Component
+public class AuthorizationImpl{
+
+    public boolean authorize(String token) {
+        // implement jwt or any any token based authorization logic
+        return true;
+    }
+}

+ 18 - 0
src/main/java/com/nju/edu/eval/aspect/auth/Authorized.java

@@ -0,0 +1,18 @@
+package com.nju.edu.eval.aspect.auth;
+
+import com.nju.edu.eval.publicdatas.enums.UserRole;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author mars
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Authorized {
+    UserRole[]  roles() default {UserRole.ADMIN, UserRole.STUDENT, UserRole.HR, UserRole.TEACHER};
+    String message() default "";
+}

+ 26 - 0
src/main/java/com/nju/edu/eval/config/AdditionalWebConfig.java

@@ -0,0 +1,26 @@
+package com.nju.edu.eval.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ *
+ * @author mars
+ */
+@Configuration
+public class AdditionalWebConfig {
+    @Bean
+    public WebMvcConfigurer CORSConfigurer() {
+        return new WebMvcConfigurer() {
+            @Override
+            public void addCorsMappings(CorsRegistry registry) {
+                registry.addMapping("/**")
+                        .allowedHeaders("*")
+                        .allowedMethods("*")
+                        .allowedOrigins("*");
+            }
+        };
+    }
+}

+ 44 - 0
src/main/java/com/nju/edu/eval/config/JsonConfig.java

@@ -0,0 +1,44 @@
+package com.nju.edu.eval.config;
+
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.alibaba.fastjson.support.config.FastJsonConfig;
+import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Configuration
+public class JsonConfig implements WebMvcConfigurer {
+
+//
+//    @Override
+//    public void configureMessageConverters(@NotNull List<HttpMessageConverter<?>> converters) {
+//        WebMvcConfigurer.super.configureMessageConverters(converters);
+//        //1.需要定义一个convert转换消息的对象;
+//        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
+//        //2.添加fastJson的配置信息,比如:是否要格式化返回的json数据;
+//        FastJsonConfig fastJsonConfig = new FastJsonConfig();
+//        //3处理中文乱码问题
+//        List<MediaType> fastMediaTypes = new ArrayList<>();
+//        fastMediaTypes.add(MediaType.APPLICATION_JSON);
+//        fastMediaTypes.add(MediaType.APPLICATION_PROBLEM_JSON);
+//        //4.在convert中添加配置信息.
+//        fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
+//        fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
+//        //5.将convert添加到converters当中.
+//        converters.add(1, fastJsonHttpMessageConverter);
+//    }
+
+}

+ 35 - 0
src/main/java/com/nju/edu/eval/config/LocalDateTimeConfig.java

@@ -0,0 +1,35 @@
+package com.nju.edu.eval.config;
+
+
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author mars
+ */
+@Configuration
+public class LocalDateTimeConfig {
+
+    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
+    private String pattern;
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
+        return builder -> {
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+
+            //返回时间数据序列化
+            builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
+            //接收时间数据反序列化
+            builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
+        };
+    }
+
+}

+ 14 - 0
src/main/java/com/nju/edu/eval/config/MySQL57InnoDBDialect.java

@@ -0,0 +1,14 @@
+package com.nju.edu.eval.config;
+
+import org.hibernate.dialect.MySQL5InnoDBDialect;
+
+/**
+ * @author mars
+ */
+public class MySQL57InnoDBDialect extends MySQL5InnoDBDialect {
+    @Override
+    public String getTableTypeString() {
+        return " ENGINE=InnoDB DEFAULT CHARSET=utf8";
+    }
+}
+

+ 40 - 0
src/main/java/com/nju/edu/eval/config/OpenApiConfig.java

@@ -0,0 +1,40 @@
+package com.nju.edu.eval.config;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Collections;
+
+@Configuration
+public class OpenApiConfig {
+
+    private static final String API_KEY = "apiKey";
+
+    @Bean
+    public OpenAPI customOpenAPI() {
+        return new OpenAPI()
+                .components(new Components()
+                        // define the apiKey SecuritySchema
+                        .addSecuritySchemes(API_KEY,apiKeySecuritySchema()))
+                .info(new Info().title("Title API").description(
+                        "RESTful services documentation with OpenAPI 3."))
+                // then apply it. If you don't apply it will not be added to the header in cURL
+                .security(Collections.singletonList(new SecurityRequirement().addList(API_KEY)));
+    }
+
+    public SecurityScheme apiKeySecuritySchema() {
+        return new SecurityScheme()
+                // authorisation-token
+                .name("Authorization")
+                .description("Description about the TOKEN")
+                .in(SecurityScheme.In.HEADER)
+                .type(SecurityScheme.Type.APIKEY);
+    }
+
+
+}

+ 24 - 0
src/main/java/com/nju/edu/eval/config/RestTemplateConfig.java

@@ -0,0 +1,24 @@
+package com.nju.edu.eval.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestTemplateConfig {
+
+    @Bean
+    public RestTemplate restTemplate(ClientHttpRequestFactory factory){
+        return new RestTemplate(factory);
+    }
+
+    @Bean
+    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setConnectTimeout(3000);
+        factory.setReadTimeout(3000);
+        return factory;
+    }
+}

+ 69 - 0
src/main/java/com/nju/edu/eval/config/SwaggerConfig.java

@@ -0,0 +1,69 @@
+package com.nju.edu.eval.config;
+
+import springfox.documentation.service.*;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.oas.annotations.EnableOpenApi;
+
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * @author mars
+ */
+@Configuration
+@EnableOpenApi
+public class SwaggerConfig {
+    @Bean
+    public Docket api() {
+        return new Docket(DocumentationType.OAS_30)
+                .select()
+                .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build()
+//                .securitySchemes(Collections.singletonList(new ApiKey("Authorization", "Authorization", "header")))
+                .securitySchemes(getSecuritySchemes())
+                .securityContexts(getSecurityContexts())
+                .pathMapping("/")
+                .apiInfo(getApiInfo());
+    }
+
+    private ApiInfo getApiInfo() {
+        return new ApiInfoBuilder()
+                .title("seecoder评测系统接口文档")
+                .description("http://47.111.95.164:8080/swagger-ui/index.html")
+                .version("1.0")
+                .build();
+    }
+
+    private List<SecurityScheme> getSecuritySchemes() {
+        List<SecurityScheme> res = new ArrayList<>();
+        res.add(new ApiKey("Authorization", "Authorization", "header"));
+        return res;
+    }
+
+    private List<SecurityContext> getSecurityContexts() {
+        List<SecurityContext> res = new ArrayList<>();
+        res.add(SecurityContext.builder()
+                .securityReferences(defaultAuth())
+//                .forPaths(PathSelectors.regex("/.*"))
+                .build());
+        return res;
+    }
+
+    private List<SecurityReference> defaultAuth() {
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        // 这个reference对应securitySchemes的name
+        return Arrays.asList(new SecurityReference("Authorization", authorizationScopes)); }
+}

+ 31 - 0
src/main/java/com/nju/edu/eval/data/dao/ExamDao.java

@@ -0,0 +1,31 @@
+package com.nju.edu.eval.data.dao;
+
+import com.nju.edu.eval.data.entity.Exam;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import javax.xml.crypto.Data;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Repository
+public interface ExamDao extends JpaRepository<Exam, Integer> {
+    /**
+     * 根据创建者Id查询所有的考试
+     * @param creatorId
+     * @return 考试列表
+     */
+    List<Exam> findAllByCreator_Id(Integer creatorId);
+
+    /**
+     * 获得开始时间在参数范围内的考试
+     * @param startTime 开始时间范围
+     * @param endTime 结束时间范围
+     * @return 考试列表
+     */
+
+    List<Exam> findAllByStartTimeBetweenOrderByStartTime(Date startTime, Date endTime);
+}

+ 44 - 0
src/main/java/com/nju/edu/eval/data/dao/RecordDao.java

@@ -0,0 +1,44 @@
+package com.nju.edu.eval.data.dao;
+
+import com.nju.edu.eval.data.entity.QuestionRecord;
+import com.nju.edu.eval.data.entity.SampleExamPaper;
+import com.nju.edu.eval.data.entity.Score;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Repository
+public interface RecordDao extends JpaRepository<QuestionRecord, Integer> {
+    /**
+     * 根据用户Id和考试Id和题目Id获得得分最高的record
+     * @param userId 用户id
+     * @param examId 考试id
+     * @param questionId 题目id
+     * @return 个人成绩记录
+     */
+    @Query(value = "select * from record r where r.exam_id = :examId and r.student_id = :userId and  r.question_id = :questionId order by r.actual_score desc limit 1", nativeQuery = true)
+    QuestionRecord findFirstByUserIdAndExamIdAndQuestionIdOrderByActualScore(@Param("userId") Integer userId, @Param("examId") Integer examId, @Param("questionId") String questionId);
+
+    /**
+     * 根据用户Id和考试Id和题目Id获得所有的record
+     * @param userId
+     * @param questionId
+     * @param examId
+     * @return
+     */
+    @Query(value = "select * from record r where r.exam_id = :examId and r.student_id = :userId and  r.question_id = :questionId order by r.submitTime DESC ", nativeQuery = true)
+    List<QuestionRecord>  findAllByUserIdAndQuestionIdAndExamId(Integer userId, String questionId, Integer examId);
+
+    /**
+     * 返回某次考试的所有提交记录
+     * @param examId 考试id
+     * @return
+     */
+    List<QuestionRecord> findAllByExamIdOrderByIdDesc(Integer examId);
+}

+ 20 - 0
src/main/java/com/nju/edu/eval/data/dao/SampleExamPaperDao.java

@@ -0,0 +1,20 @@
+package com.nju.edu.eval.data.dao;
+
+import com.nju.edu.eval.data.entity.SampleExamPaper;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.awt.print.Pageable;
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Repository
+public interface SampleExamPaperDao extends JpaRepository<SampleExamPaper, Integer> {
+    /**
+     * 获得所有样卷
+     * @return
+     */
+    List<SampleExamPaper> findAllByIdNotNull();
+}

+ 22 - 0
src/main/java/com/nju/edu/eval/data/dao/ScoreDao.java

@@ -0,0 +1,22 @@
+package com.nju.edu.eval.data.dao;
+
+import com.nju.edu.eval.data.entity.Score;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author mars
+ */
+@Repository
+public interface ScoreDao extends JpaRepository<Score, Integer> {
+    /**
+     * 根据用户Id和考试Id获得唯一的Score
+     * @param userId 用户id
+     * @param examId 考试id
+     * @return 个人成绩记录
+     */
+    @Query(value = "select * from score s where s.exam_id = :examId and s.student_id = :userId", nativeQuery = true)
+    Score findByUserIdAndExamId(@Param("userId") Integer userId, @Param("examId") Integer examId);
+}

+ 30 - 0
src/main/java/com/nju/edu/eval/data/dao/UserDao.java

@@ -0,0 +1,30 @@
+package com.nju.edu.eval.data.dao;
+
+import com.nju.edu.eval.aspect.auth.Authorized;
+import com.nju.edu.eval.data.entity.Exam;
+import com.nju.edu.eval.data.entity.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+
+/**
+ * @author mars
+ */
+@Repository
+public interface UserDao extends JpaRepository<User, Integer> {
+    /**
+     * 是否存在该用户
+     * @param id 门户系统用户id
+     * @return boolean
+     */
+    boolean existsByIdIs(Integer id);
+
+    /**
+     * 获得参加某个考试的所有用户
+     * @param exam
+     * @return
+     */
+    List<User> findAllByExamsContains(Exam exam);
+}

+ 110 - 0
src/main/java/com/nju/edu/eval/data/entity/Exam.java

@@ -0,0 +1,110 @@
+package com.nju.edu.eval.data.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.nju.edu.eval.publicdatas.enums.ExamState;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author mars
+ */
+@Data
+@Entity
+@EqualsAndHashCode
+@NoArgsConstructor
+@AllArgsConstructor
+@Table(name = "exam")
+public class Exam {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    @Column(name = "exam_name")
+    private String examName;
+
+    private String position;
+
+    @ElementCollection
+    @CollectionTable(name = "skill_of_exam", joinColumns = @JoinColumn(name = "exam_id"))
+    @Column(name = "skill")
+    private  List<String> skills;
+
+    @Column(name = "expected_difficulty")
+    private int difficulty;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date startTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date endTime;
+
+    private String comment;
+
+    private String inviteCode = "";
+
+    @Column(name = "is_invigilate")
+    private Integer antiCheating;
+
+    /**
+     * 是否需要邀请码参加考试
+     */
+    @Column(name = "is_public")
+    private Integer isPublic;
+
+//    @ElementCollection
+//    @CollectionTable(name = "question_id_of_exam",
+//            joinColumns = @JoinColumn(name = "exam_id"))
+//    @Column(name = "question_id")
+//    private  List<String> questionIds;
+
+    /**
+     * 试卷中题目id - 题目分值的map
+     */
+    @ElementCollection
+    @CollectionTable(name = "question_score_of_exam", joinColumns = @JoinColumn(name = "exam_id"))
+    @MapKeyColumn(name="question_id")
+    @Column(name="score")
+    private Map<String, Double> questionAndScore = new HashMap<>();
+
+    /**
+     * 考试创建者
+     */
+    @ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
+    @JoinColumn(name = "creator_id")
+    private User creator;
+
+    @OneToOne(cascade = CascadeType.ALL)
+    @JoinColumn(name = "general_report_id")
+    private GeneralReport generalReport;
+
+//    private List<Question>
+
+    public Exam(String examName, String position, List<String> skills, Date startTime, Date endTime, String comment, User user) {
+        this.examName = examName;
+        this.position = position;
+        this.skills = skills;
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.comment = comment;
+        this.creator = user;
+    }
+
+    public ExamState getState() {
+        Date date = new Date();
+        if (this.startTime.after(date)) {
+            return ExamState.NOT_STARTED;
+        }
+        if (this.endTime.before(date)) {
+            return ExamState.OVER;
+        }
+        return ExamState.IN_PROGRESS;
+    }
+}

+ 43 - 0
src/main/java/com/nju/edu/eval/data/entity/GeneralReport.java

@@ -0,0 +1,43 @@
+package com.nju.edu.eval.data.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Data
+@Entity
+@EqualsAndHashCode
+@NoArgsConstructor
+@AllArgsConstructor
+@Table(name = "general_report")
+public class GeneralReport {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    /**
+     * 总体成绩报告对应的考试
+     */
+    @OneToOne
+    @JoinColumn(name = "exam_id")
+    private Exam exam;
+
+    /**
+     * 报名人数
+     */
+    private int registrationNum;
+
+    /**
+     * 实际参加人数
+     */
+    private int takenNum;
+
+    // 这里不存储排名,当查询时直接用exam_id查score表计算
+}

+ 73 - 0
src/main/java/com/nju/edu/eval/data/entity/QuestionRecord.java

@@ -0,0 +1,73 @@
+package com.nju.edu.eval.data.entity;
+
+import com.nju.edu.eval.publicdatas.enums.CodeJudgeResultType;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.util.Date;
+
+/**
+ * @author mars
+ */
+@Data
+@Entity
+@EqualsAndHashCode
+@NoArgsConstructor
+@AllArgsConstructor
+@Table(name = "record")
+public class QuestionRecord {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    @ManyToOne
+    @JoinColumn(name = "student_id")
+    private User user;
+
+    @ManyToOne
+    @JoinColumn(name = "exam_id")
+    private Exam exam;
+
+    @Column(name = "question_id")
+    private String questionId;
+
+    @ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
+    @JoinColumn(name = "score_id")
+    private Score score;
+
+    /**
+     * 该题得分
+     */
+    @Column(name = "actual_score")
+    private double actualScore;
+
+    /**
+     * 用户提交的答案
+     */
+    @Column(name = "submit_answer",columnDefinition = "TEXT")
+    private String submitAnswer;
+
+    /**
+     * 代码题提交后的信息
+     */
+    @Enumerated(EnumType.STRING)
+    private CodeJudgeResultType message;
+
+    /**
+     * 提交时间
+     */
+    private Date submitTime;
+
+    public QuestionRecord(User user, Exam exam, String questionId, Score score, String submitAnswer, double actualScore, Date submitTime) {
+        this.user = user;
+        this.exam = exam;
+        this.questionId = questionId;
+        this.score = score;
+        this.submitAnswer = submitAnswer;
+        this.actualScore = actualScore;
+        this.submitTime = submitTime;
+    }
+}

+ 42 - 0
src/main/java/com/nju/edu/eval/data/entity/SampleExamPaper.java

@@ -0,0 +1,42 @@
+package com.nju.edu.eval.data.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 固定试卷
+ * @author mars
+ */
+@Data
+@Entity
+@EqualsAndHashCode
+@NoArgsConstructor
+@AllArgsConstructor
+@Table(name = "sample_exam_paper")
+public class SampleExamPaper {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    private String name;
+
+//    @ElementCollection
+//    @CollectionTable(name = "question_id_of_sample_exam_paper",
+//            joinColumns = @JoinColumn(name = "sample_exam_paper_id"))
+//    @Column(name = "question_id")
+//    private List<String> questionIds;
+
+    @ElementCollection
+    @CollectionTable(name = "question_score_of_sample_exam_paper", joinColumns = @JoinColumn(name = "sample_exam_paper_id"))
+    @MapKeyColumn(name="question_id")
+    @Column(name="score")
+    private Map<String, Double> questionAndScore = new HashMap<>();
+
+}

+ 80 - 0
src/main/java/com/nju/edu/eval/data/entity/Score.java

@@ -0,0 +1,80 @@
+package com.nju.edu.eval.data.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.springframework.boot.context.properties.bind.DefaultValue;
+
+import javax.persistence.*;
+import java.util.*;
+
+/**
+ * 某次考试,某位同学的个人成绩报告
+ * @author mars
+ */
+@Data
+@Entity
+@EqualsAndHashCode
+@NoArgsConstructor
+@AllArgsConstructor
+@Table(name = "score")
+public class Score {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    @ManyToOne
+    @JoinColumn(name = "student_id")
+    private User user;
+
+    @ManyToOne
+    @JoinColumn(name = "exam_id")
+    private Exam exam;
+
+    private double score = 0.0;
+
+    private double objectiveScore = 0.0;
+
+    /**
+     * 本次考试排名
+     */
+    private int rank = 0;
+
+    /**
+     * 第一次提交时间
+     */
+    private Date submitTime;
+
+    /**
+     * 存储用户做题记录,包括用户单题得分(判题结果),以及用户所提交的答案
+     */
+    @OneToMany(mappedBy = "score", cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
+    private List<QuestionRecord> records = new ArrayList<>();
+
+    /**
+     * 存储单题得分,暂时想用map存储
+     */
+//    @ElementCollection
+//    @CollectionTable(name = "single_question_score", joinColumns = @JoinColumn(name = "score_id"))
+//    @MapKeyColumn(name="question_id")
+//    @Column(name="score")
+//    private Map<String, Integer> scores = new HashMap<>();
+
+    /**
+     * 问题id - 提交答案 map
+     * 存储本次考试用户提交的答案
+     */
+//    @ElementCollection
+//    @CollectionTable(name = "user_record_of_exam", joinColumns = @JoinColumn(name = "score_id"))
+//    @MapKeyColumn(name="question_id")
+//    @Column(name="record")
+//    private Map<String, String> userRecord = new HashMap<>();
+
+
+    public Score(User user, Exam exam) {
+        this.user = user;
+        this.exam = exam;
+    }
+
+}

+ 49 - 0
src/main/java/com/nju/edu/eval/data/entity/User.java

@@ -0,0 +1,49 @@
+package com.nju.edu.eval.data.entity;
+
+import com.nju.edu.eval.publicdatas.enums.UserRole;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Data
+@Entity
+@EqualsAndHashCode
+@NoArgsConstructor
+@AllArgsConstructor
+@Table(name = "user")
+public class User {
+    @Id
+    private Integer id;
+
+    @Enumerated(EnumType.STRING)
+    private UserRole role;
+
+    private String name;
+
+    private String email;
+
+    private String phone;
+
+    /**
+     * 学生参加的考试 中间表是score
+     */
+    @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
+    @JoinTable(name = "score", joinColumns = @JoinColumn(name = "student_id"),
+                                    inverseJoinColumns = @JoinColumn(name = "exam_id"))
+    private List<Exam> exams = new ArrayList<>();
+
+    /**
+     * 某个学生考试的个人成绩报告集合
+     */
+    @OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
+    private List<Score> scores;
+
+}

+ 21 - 0
src/main/java/com/nju/edu/eval/exception/MyServiceException.java

@@ -0,0 +1,21 @@
+package com.nju.edu.eval.exception;
+
+import lombok.Data;
+import org.hibernate.service.spi.ServiceException;
+
+/**
+ * @author mars
+ */
+@Data
+public class MyServiceException extends ServiceException {
+    private String code;
+
+    public MyServiceException(String message, Throwable root) {
+        super(message, root);
+    }
+
+    public MyServiceException(String code, String message) {
+        super(message);
+        this.code = code;
+    }
+}

+ 19 - 0
src/main/java/com/nju/edu/eval/exception/MyServiceExceptionHandler.java

@@ -0,0 +1,19 @@
+package com.nju.edu.eval.exception;
+
+import com.nju.edu.eval.web.Response;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * @author mars
+ */
+@RestControllerAdvice(basePackages = {"com.nju.edu.eval.web.controller", "com.nju.edu.eval.aspect.auth"})
+public class MyServiceExceptionHandler {
+    @ExceptionHandler(MyServiceException.class)
+    @ResponseBody
+    private Response handleMyServiceException(MyServiceException e) {
+        return new Response(e.getCode(), e.getMessage());
+    }
+
+}

+ 20 - 0
src/main/java/com/nju/edu/eval/exception/ThirdPartyServiceException.java

@@ -0,0 +1,20 @@
+package com.nju.edu.eval.exception;
+
+import lombok.Data;
+import org.hibernate.service.spi.ServiceException;
+
+/**
+ * @author mars
+ */
+@Data
+public class ThirdPartyServiceException extends ServiceException {
+    private String code;
+    public ThirdPartyServiceException(String message, Throwable root) {
+        super(message, root);
+    }
+
+    public ThirdPartyServiceException(String message, String code) {
+        super(message);
+        this.code = code;
+    }
+}

+ 50 - 0
src/main/java/com/nju/edu/eval/manager/JudgeManager.java

@@ -0,0 +1,50 @@
+package com.nju.edu.eval.manager;
+
+import cn.seecoder.codejudge.client.JudgeDTO;
+import cn.seecoder.codejudge.client.JudgeResult;
+import cn.seecoder.judgement.dto.StudentAnswerDTO;
+import cn.seecoder.judgement.vo.JudgedAnswersVO;
+import com.nju.edu.eval.exception.ThirdPartyServiceException;
+import com.nju.edu.eval.model.vo.judge.ObjectiveJudgeVO;
+import com.nju.edu.eval.thirdparty.CodeJudgeThirdPartyService;
+import com.nju.edu.eval.thirdparty.ObjectiveQuestionJudgeThirdPartyService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 封装了对客观题和代码题的判题
+ * @author mars
+ */
+@Service
+public class JudgeManager {
+    private final ObjectiveQuestionJudgeThirdPartyService judgeThirdPartyService;
+    private final CodeJudgeThirdPartyService codeJudgeThirdPartyService;
+    public JudgeManager(ObjectiveQuestionJudgeThirdPartyService judgeThirdPartyService, CodeJudgeThirdPartyService codeJudgeThirdPartyService) {
+        this.judgeThirdPartyService = judgeThirdPartyService;
+        this.codeJudgeThirdPartyService = codeJudgeThirdPartyService;
+    }
+
+    public JudgedAnswersVO judgeObjectiveQuestionAnswers(Integer userId, ObjectiveJudgeVO objectiveJudgeVO) {
+        System.out.println(objectiveJudgeVO.getRecords());
+        StudentAnswerDTO studentAnswerDTO = StudentAnswerDTO.builder().userId(userId.toString()).records(objectiveJudgeVO.getRecords()).build();
+        Optional<JudgedAnswersVO> res = judgeThirdPartyService.judgeObjectiveQuestionAnswers(studentAnswerDTO);
+        if (res.isPresent()) {
+            return res.get();
+        } else {
+            throw new ThirdPartyServiceException("C0001", "客观题判题异常");
+        }
+    }
+
+    public JudgeResult judgeCodeQuestionAnswers(JudgeDTO judgeDTO) {
+        Optional<JudgeResult> res = codeJudgeThirdPartyService.judgeCodeQuestionAnswers(judgeDTO);
+        if (res.isPresent()) {
+            return res.get();
+        } else {
+            throw new ThirdPartyServiceException("C0001", "代码题判题异常");
+        }
+    }
+
+
+}

+ 67 - 0
src/main/java/com/nju/edu/eval/manager/QuestionManager.java

@@ -0,0 +1,67 @@
+package com.nju.edu.eval.manager;
+
+import cn.seecoder.bok.common.vo.KnowledgeNodeVO;
+import com.nju.edu.eval.exception.ThirdPartyServiceException;
+import com.nju.edu.eval.model.vo.question.CompleteQuestionVO;
+import com.nju.edu.eval.model.vo.question.QuestionStemListVO;
+import com.nju.edu.eval.model.vo.question.QuestionStemVO;
+import com.nju.edu.eval.thirdparty.QuestionThirdPartyService;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author mars
+ * 通用业务处理层
+ * 对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口。
+ * 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。
+ * 与 DAO 层交互,对多个 DAO 的组合复用。
+ */
+@Service
+public class QuestionManager {
+    private final QuestionThirdPartyService questionThirdPartyService;
+    public QuestionManager(QuestionThirdPartyService questionThirdPartyService) {
+        this.questionThirdPartyService = questionThirdPartyService;
+    }
+
+    public Optional<CompleteQuestionVO> getQuestionById(String id) {
+        Optional<CompleteQuestionVO> res = questionThirdPartyService.getQuestionById(id);
+        if (!res.isPresent()) {
+            throw new ThirdPartyServiceException("C0001", "没有对应的问题Id");
+        } else {
+            return res;
+        }
+    }
+
+    public Optional<QuestionStemVO> getQuestionStemById(String id) {
+        Optional<CompleteQuestionVO> questionVO = questionThirdPartyService.getQuestionById(id);
+        if (!questionVO.isPresent()) {
+            throw new ThirdPartyServiceException("C0001", "没有对应的问题Id");
+        } else {
+            CompleteQuestionVO completeQuestionVO = questionVO.get();
+            QuestionStemVO res = new QuestionStemVO();
+            BeanUtils.copyProperties(completeQuestionVO, res);
+            return Optional.of(res);
+        }
+    }
+
+    public List<CompleteQuestionVO> getRecommendQuestions(List<String> knowledgeList, int expectedDifficulty) {
+        return questionThirdPartyService.getRecommendQuestions(knowledgeList, expectedDifficulty);
+    }
+
+    public QuestionStemListVO getQuestionList(String stem, List<String> tags, String knowledgeName, int pageSize, int pageNum) {
+        List<String> knowledgeIds = new ArrayList<>();
+        if (knowledgeName != null) {
+            knowledgeIds = questionThirdPartyService.getKnowledgeNode(knowledgeName)
+                    .stream().map(KnowledgeNodeVO::getId).collect(Collectors.toList());
+        }
+        return questionThirdPartyService.getQuestionList(stem, tags, knowledgeIds, PageRequest.of(pageNum, pageSize, Sort.by("id").ascending()));
+    }
+}

+ 38 - 0
src/main/java/com/nju/edu/eval/model/vo/exam/CreateExamVO.java

@@ -0,0 +1,38 @@
+package com.nju.edu.eval.model.vo.exam;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CreateExamVO {
+    private String examName;
+
+    private String position;
+
+    private List<String> skills;
+
+    private Integer difficulty;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date startTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date endTime;
+
+    private String comment;
+
+    private Boolean antiCheating;
+
+    private Boolean isPublic;
+}

+ 36 - 0
src/main/java/com/nju/edu/eval/model/vo/exam/ExamInfoVO.java

@@ -0,0 +1,36 @@
+package com.nju.edu.eval.model.vo.exam;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ExamInfoVO implements Serializable {
+    private Integer examId;
+
+    private String examName;
+
+    private String position;
+
+    private List<String> skills;
+
+    private Integer difficulty;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date startTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date endTime;
+
+    private String comment;
+}

+ 18 - 0
src/main/java/com/nju/edu/eval/model/vo/exam/ExamListAndNumVO.java

@@ -0,0 +1,18 @@
+package com.nju.edu.eval.model.vo.exam;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ExamListAndNumVO {
+    List<ExamVO> examVOList;
+    Integer totalNum;
+}

+ 53 - 0
src/main/java/com/nju/edu/eval/model/vo/exam/ExamVO.java

@@ -0,0 +1,53 @@
+package com.nju.edu.eval.model.vo.exam;
+
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.ToString;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Data
+@ToString
+public class ExamVO {
+    private Integer examId;
+
+    private String examName;
+
+    private String position;
+
+    private List<String> skills;
+
+    private Integer difficulty;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date startTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date endTime;
+
+    private String comment;
+
+    private String inviteCode;
+
+    private Integer creatorId;
+
+    /**
+     * 是否需要开启反作弊系统
+     */
+    private Boolean antiCheating;
+
+    /**
+     * 该用户是否已经参加
+     */
+    private Boolean isJoined;
+
+    /**
+     * 是否需要邀请码加入
+     */
+    private Boolean isPublic;
+}

+ 20 - 0
src/main/java/com/nju/edu/eval/model/vo/exam/NewExamVO.java

@@ -0,0 +1,20 @@
+package com.nju.edu.eval.model.vo.exam;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class NewExamVO {
+    private Integer examId;
+    private String inviteCode;
+
+//    private List<QUestions>
+
+    private Integer currentDifficulty;
+}

+ 17 - 0
src/main/java/com/nju/edu/eval/model/vo/exam/QuestionScoreMapVO.java

@@ -0,0 +1,17 @@
+package com.nju.edu.eval.model.vo.exam;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class QuestionScoreMapVO {
+    Map<String, Double> questionScoreMap;
+}

+ 20 - 0
src/main/java/com/nju/edu/eval/model/vo/judge/CodeJudgeVO.java

@@ -0,0 +1,20 @@
+package com.nju.edu.eval.model.vo.judge;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author mars
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class CodeJudgeVO implements Serializable {
+    private String code;
+    private String language;
+}

+ 23 - 0
src/main/java/com/nju/edu/eval/model/vo/judge/ObjectiveJudgeResOfExerciseVO.java

@@ -0,0 +1,23 @@
+package com.nju.edu.eval.model.vo.judge;
+
+import cn.seecoder.judgement.dto.StudentAnswerDTO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author mars
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class ObjectiveJudgeResOfExerciseVO implements Serializable {
+    @Override
+    public String toString() {
+        return super.toString();
+    }
+    Map<String, Boolean> result;
+}

+ 23 - 0
src/main/java/com/nju/edu/eval/model/vo/judge/ObjectiveJudgeResVO.java

@@ -0,0 +1,23 @@
+package com.nju.edu.eval.model.vo.judge;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+public class ObjectiveJudgeResVO implements Serializable {
+    @Override
+    public String toString() {
+        return super.toString();
+    }
+
+    private LocalDateTime submitAt;
+    private Integer submitNumber;
+}

+ 20 - 0
src/main/java/com/nju/edu/eval/model/vo/judge/ObjectiveJudgeVO.java

@@ -0,0 +1,20 @@
+package com.nju.edu.eval.model.vo.judge;
+
+import cn.seecoder.judgement.dto.StudentAnswerDTO;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Past;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author mars
+ */
+@Data
+public class ObjectiveJudgeVO implements Serializable {
+    Map<String, StudentAnswerDTO.AnswerRecordDTO> records;
+}

+ 18 - 0
src/main/java/com/nju/edu/eval/model/vo/judge/ObjectiveJudgeVOOfExercise.java

@@ -0,0 +1,18 @@
+package com.nju.edu.eval.model.vo.judge;
+
+import cn.seecoder.judgement.dto.StudentAnswerDTO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ObjectiveJudgeVOOfExercise {
+    Map<String, Object> answers;
+}

+ 91 - 0
src/main/java/com/nju/edu/eval/model/vo/question/CompleteQuestionVO.java

@@ -0,0 +1,91 @@
+package com.nju.edu.eval.model.vo.question;
+
+import cn.seecoder.bok.common.QuestionType;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author mars
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class CompleteQuestionVO implements Serializable {
+    private String id;
+
+    /**
+     * 题干
+     */
+    private String stem;
+
+    /**
+     * 题目类型
+     */
+    private QuestionType type;
+
+    /**
+     * 选项
+     */
+    private Map<String, String> options;
+
+    /**
+     * 填空题 __1__ __2__
+     */
+//    private Map<String, String> blanks;
+
+    /**
+     * 答案
+     */
+    private String answer;
+
+    /**
+     * 关键点
+     */
+    private String keyPoints;
+
+    /**
+     * 分析
+     */
+    private String analysis;
+
+    /**
+     * 标签
+     */
+    private List<String> tags;
+
+    /**
+     * 知识点 id
+     */
+    private List<String> knowledgeId;
+
+    /**
+     * 输入输出的测试用例
+     */
+    private Map<String, String> inputOutputMapping;
+
+
+    /**
+     * 代码题目时间限制
+     */
+    private Integer millisecondLimit;
+
+    /**
+     * 代码空间限制
+     */
+    private Integer memoryLimit;
+
+
+    private Instant createdTime;
+
+    private Instant lastModifiedTime;
+
+}

+ 19 - 0
src/main/java/com/nju/edu/eval/model/vo/question/QuestionAndScoreVO.java

@@ -0,0 +1,19 @@
+package com.nju.edu.eval.model.vo.question;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class QuestionAndScoreVO implements Serializable {
+    private Map<String, Double> questionAndScore;
+}

+ 16 - 0
src/main/java/com/nju/edu/eval/model/vo/question/QuestionStemListVO.java

@@ -0,0 +1,16 @@
+package com.nju.edu.eval.model.vo.question;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+public class QuestionStemListVO {
+    List<QuestionStemVO> questionStemList;
+    Integer totalNum;
+}

+ 70 - 0
src/main/java/com/nju/edu/eval/model/vo/question/QuestionStemVO.java

@@ -0,0 +1,70 @@
+package com.nju.edu.eval.model.vo.question;
+
+import cn.seecoder.bok.common.QuestionType;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class QuestionStemVO {
+    private String id;
+
+    /**
+     * 题干
+     */
+    private String stem;
+
+    /**
+     * 题目类型
+     */
+    private QuestionType type;
+
+    /**
+     * 选项
+     */
+    private Map<String, String> options;
+
+    /**
+     * 填空题 __1__ __2__
+     */
+//    private Map<String, String> blanks;
+
+
+    /**
+     * 关键点
+     */
+    private String keyPoints;
+
+
+    /**
+     * 标签
+     */
+    private List<String> tags;
+
+    /**
+     * 知识点 id
+     */
+    private List<String> knowledgeId;
+
+    /**
+     * 代码题目时间限制
+     */
+    private Integer millisecondLimit;
+
+    /**
+     * 代码空间限制
+     */
+    private Integer memoryLimit;
+
+}

+ 33 - 0
src/main/java/com/nju/edu/eval/model/vo/record/QuestionRecordVO.java

@@ -0,0 +1,33 @@
+package com.nju.edu.eval.model.vo.record;
+
+
+import com.nju.edu.eval.publicdatas.enums.CodeJudgeResultType;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author mars
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class QuestionRecordVO implements Serializable {
+    private Integer id;
+
+    private String questionId;
+
+    private Double actualScore;
+
+    private Integer studentId;
+
+    private String submitAnswer;
+
+    private Date submitTime;
+
+    private CodeJudgeResultType message;
+}

+ 27 - 0
src/main/java/com/nju/edu/eval/model/vo/score/PersonalScoreReportVO.java

@@ -0,0 +1,27 @@
+package com.nju.edu.eval.model.vo.score;
+
+import com.nju.edu.eval.data.entity.Exam;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class PersonalScoreReportVO implements Serializable {
+    private Integer id;
+
+    private Double score;
+
+    private Integer rank;
+
+    private Date submitTime;
+}

+ 19 - 0
src/main/java/com/nju/edu/eval/model/vo/user/UpdateUserVO.java

@@ -0,0 +1,19 @@
+package com.nju.edu.eval.model.vo.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@ToString
+public class UpdateUserVO {
+    private Integer id;
+
+    private String name;
+
+    private String email;
+}

+ 20 - 0
src/main/java/com/nju/edu/eval/model/vo/user/UserVO.java

@@ -0,0 +1,20 @@
+package com.nju.edu.eval.model.vo.user;
+
+import com.nju.edu.eval.publicdatas.enums.UserRole;
+import lombok.Data;
+import lombok.ToString;
+
+
+@Data
+@ToString
+public class UserVO {
+    private Integer id;
+
+    private UserRole role;
+
+    private String name;
+
+    private String email;
+
+    private String phone;
+}

+ 58 - 0
src/main/java/com/nju/edu/eval/publicdatas/enums/CodeJudgeResultType.java

@@ -0,0 +1,58 @@
+package com.nju.edu.eval.publicdatas.enums;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author mars
+ */
+
+public enum CodeJudgeResultType {
+    /**
+     * 正确
+     */
+    @JsonProperty("Accepted")
+    ACCEPT,
+    /**
+     * 输出答案格式出错
+     */
+    @JsonProperty("Presentation Error")
+    PRESENTATION_ERROR,
+    /**
+     * 超时
+     */
+    @JsonProperty("Time Limit Exceeded")
+    TIME_LIMIT_EXCEEDED,
+    /**
+     * 超出空间限制
+     */
+    @JsonProperty("Memory Limit Exceeded")
+    MEMORY_LIMIT_EXCEEDED,
+    /**
+     * 错误答案
+     */
+    @JsonProperty("Wrong Answer")
+    WRONG_ANSWER,
+    /**
+     * 运行错误
+     */
+    @JsonProperty("Runtime Error")
+    RUNTIME_ERROR,
+    /**
+     * 输出超过限制
+     */
+    @JsonProperty("Output Limit Exceeded")
+    OUTPUT_LIMIT_EXCEEDED,
+    /**
+     * 编译错误
+     */
+    @JsonProperty("Compile Error")
+    COMPILE_ERROR,
+    /**
+     * 系统错误
+     */
+    @JsonProperty("System Error")
+    SYSTEM_ERROR;
+
+    private CodeJudgeResultType() {
+    }
+}

+ 17 - 0
src/main/java/com/nju/edu/eval/publicdatas/enums/ExamState.java

@@ -0,0 +1,17 @@
+package com.nju.edu.eval.publicdatas.enums;
+
+import lombok.Data;
+
+
+/**
+ * @author mars
+ */
+
+public enum ExamState {
+    /**
+     * 未开始,正在进行,已结束
+     */
+    NOT_STARTED,
+    IN_PROGRESS,
+    OVER
+}

+ 14 - 0
src/main/java/com/nju/edu/eval/publicdatas/enums/QuestionType.java

@@ -0,0 +1,14 @@
+package com.nju.edu.eval.publicdatas.enums;
+
+public enum QuestionType {
+    /** 选择题 */
+    choice,
+    /** 判断题 */
+    true_false,
+    /** 多选题 */
+    multiple_choice,
+    /** 填空题 */
+    blank,
+    /** 简答题 */
+    short_answer
+}

+ 15 - 0
src/main/java/com/nju/edu/eval/publicdatas/enums/UserRole.java

@@ -0,0 +1,15 @@
+package com.nju.edu.eval.publicdatas.enums;
+
+/**
+ * @author mars
+ */
+
+public enum UserRole {
+    /**
+     * 老师,学生,HR,网站管理员
+     */
+    TEACHER,
+    STUDENT,
+    HR,
+    ADMIN
+}

+ 116 - 0
src/main/java/com/nju/edu/eval/service/ExamService.java

@@ -0,0 +1,116 @@
+package com.nju.edu.eval.service;
+
+import com.nju.edu.eval.data.entity.Exam;
+import com.nju.edu.eval.model.vo.exam.*;
+import com.nju.edu.eval.model.vo.question.QuestionAndScoreVO;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author mars
+ */
+public interface ExamService {
+    /**
+     * 创建考试
+     * @param examVO exam的vo
+     * @param creatorId 创建考试者id
+     * @return 邀请码,题目等内容
+     */
+    NewExamVO createExam(CreateExamVO examVO, Integer creatorId);
+
+    /**
+     * 获得所有考试
+     * @param userId
+     * @param pageSize
+     * @param pageNum
+     * @return 考试列表
+     */
+    ExamListAndNumVO getExamList(int userId, int pageSize, int pageNum);
+
+
+    /**
+     * 获得某个时间段内的考试
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @return 考试信息列表
+     * @see ExamInfoVO
+     */
+    List<ExamInfoVO> getExamListOfTimeRange(Date startTime, Date endTime);
+
+    /**
+     * 根据id获得考试
+     * @param  id 考试id
+     * @return 该id对应的考试
+     */
+    ExamVO getExamById(Integer id);
+
+
+    /**
+     * 获得id对应考试的题目和分值
+     * @param id 考试Id
+     * @return 题目id 和 分值映射的list
+     */
+    QuestionAndScoreVO getExamQuestionAndScoreById(Integer id);
+
+    /**
+     * 获得某次考试的代码题列表
+     * @param id 考试id
+     * @return 代码题id列表
+     */
+    List<String> getCodeQuestionIdsByExamId(Integer id);
+
+    /**
+     * 为考试更新题目
+     * @param examId 考试Id
+     * @param questionScoreMapVO 题目和分值的映射
+     */
+    void updateQuestionsOfExam(Integer examId, QuestionScoreMapVO questionScoreMapVO);
+
+    /**
+     * 延长考试时间
+     * @param examId 考试id
+     * @param endTime 延长后的时间
+     * @return exam的optional值
+     */
+    Optional<Exam> extendExamTime(Integer examId, Date endTime);
+
+    /**
+     * 获得某学生参加的考试相关信息
+     * @param id 学生id
+     * @param pageSize
+     * @param pageNum
+     * @param state
+     * @return
+     */
+    ExamListAndNumVO getExamsByCurrentStudent(Integer id, int pageSize, int pageNum, int state);
+
+
+    /**
+     * 获得由老师或者HR创建的考试
+     * @param id 老师/HR id
+     * @param pageSize
+     * @param pageNum
+     * @param state
+     * @return
+     */
+    ExamListAndNumVO getExamsCreatedByCurrentTeacherOrHR(Integer id, int pageSize, int pageNum, int state);
+
+    /**
+     * 学生通过邀请码参加考试
+     * @param userId 学生id
+     * @param examId 考试id
+     * @param inviteCodeInput 输入的邀请码
+     * @return 是否成功加入考试
+     */
+    boolean takeExam(Integer userId, Integer examId, String inviteCodeInput);
+
+    /**
+     * 给某次考试设置题目
+     * @param examId 考试Id
+     * @param sampleExamPaperId 样卷Id
+     */
+    void setExamQuestionsBySampleExamPaper(int examId, int sampleExamPaperId);
+
+}

+ 57 - 0
src/main/java/com/nju/edu/eval/service/JudgeService.java

@@ -0,0 +1,57 @@
+package com.nju.edu.eval.service;
+
+import cn.seecoder.codejudge.client.JudgeDTO;
+import cn.seecoder.codejudge.client.JudgeResult;
+import cn.seecoder.judgement.vo.JudgedAnswersVO;
+import com.nju.edu.eval.model.vo.judge.CodeJudgeVO;
+import com.nju.edu.eval.model.vo.judge.ObjectiveJudgeResOfExerciseVO;
+import com.nju.edu.eval.model.vo.judge.ObjectiveJudgeResVO;
+import com.nju.edu.eval.model.vo.judge.ObjectiveJudgeVO;
+
+import java.util.List;
+
+/**
+ * @author mars
+ */
+public interface JudgeService {
+    /**
+     * 客观题判题服务,调用JudgeManager实现
+     * @param userId 用户Id
+     * @param examId 对应考试Id
+     * @param objectiveJudgeVO 记录
+     * @return JudgedAnswersVO
+     */
+    ObjectiveJudgeResVO judgeObjectiveQuestionAnswers(Integer userId, Integer examId, ObjectiveJudgeVO objectiveJudgeVO);
+
+    /**
+     * 代码题判题服务,调用JudgeManager实现
+     * @param userId 用户Id
+     * @param examId 对应考试Id
+     * @param questionId 题目Id
+     * @param codeJudgeVO 用户的题目解答和语言
+     * @return JudgeResult
+     */
+    JudgeResult judgeCodeQuestionAnswers(Integer userId, Integer examId, String questionId, CodeJudgeVO codeJudgeVO);
+
+
+    /**
+     * 练习题客观题判题服务,调用JudgeManager实现
+     * @param userId 用户Id
+     * @param objectiveJudgeVO 记录
+     * @return JudgedAnswersVO
+     */
+    ObjectiveJudgeResOfExerciseVO judgeObjectiveQuestionAnswersOfExercise(Integer userId, ObjectiveJudgeVO objectiveJudgeVO);
+
+
+    /**
+     * 练习题代码题判题服务,调用JudgeManager实现
+     * @param userId 用户Id
+     * @param questionId 题目Id
+     * @param codeJudgeVO 用户的题目解答和语言
+     * @return JudgeResult
+     */
+    JudgeResult judgeCodeQuestionAnswersOfExercise(Integer userId,  String questionId, CodeJudgeVO codeJudgeVO);
+
+
+
+}

+ 46 - 0
src/main/java/com/nju/edu/eval/service/QuestionService.java

@@ -0,0 +1,46 @@
+package com.nju.edu.eval.service;
+
+import com.nju.edu.eval.data.entity.SampleExamPaper;
+import com.nju.edu.eval.model.vo.question.CompleteQuestionVO;
+import com.nju.edu.eval.model.vo.question.QuestionStemListVO;
+import com.nju.edu.eval.model.vo.question.QuestionStemVO;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author mars
+ */
+public interface QuestionService {
+    /**
+     * 调用第三方服务根据问题Id获得问题相关信息(包括答案)
+     * @param id 问题Id
+     * @return Optional<QuestionVO>
+     */
+    Optional<CompleteQuestionVO> getQuestionById(String id);
+
+
+    /**
+     * 调用第三方服务根据问题Id获得问题的题干和知识点,面向学生视角
+     * @param id
+     * @return
+     */
+    Optional<QuestionStemVO> getQuestionStemById(String id);
+
+    /**
+     * 获得所有的固定样卷列表
+     * @return 样卷列表
+     */
+    List<SampleExamPaper> getSampleExamPaperList();
+
+    /**
+     * 根据题干,标签,知识点获得题目,并分页
+     * @param stem 题干
+     * @param tags 标签
+     * @param knowledgeName 知识点名
+     * @param pageSize 页大小
+     * @param pageNum 页数
+     * @return
+     */
+    QuestionStemListVO getQuestionList(String stem, List<String> tags, String knowledgeName, int pageSize, int pageNum);
+}

+ 30 - 0
src/main/java/com/nju/edu/eval/service/RecordService.java

@@ -0,0 +1,30 @@
+package com.nju.edu.eval.service;
+
+import com.nju.edu.eval.data.entity.QuestionRecord;
+import com.nju.edu.eval.model.vo.record.QuestionRecordVO;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@Service
+public interface RecordService {
+    /**
+     * 获得某次考试某个同学关于某道题的所有提交记录
+     * @param userId 学生id
+     * @param questionId 问题id
+     * @param examId 考试id
+     * @return
+     */
+    List<QuestionRecordVO> getQuestionRecordsOfExam(Integer userId, String questionId, Integer examId);
+
+    /**
+     * 获得某次考试所有做题记录
+     * @param examId 考试id
+     * @return
+     */
+    List<QuestionRecordVO> getAllQuestionRecordsOfExam(Integer examId);
+
+}

+ 17 - 0
src/main/java/com/nju/edu/eval/service/ScoreService.java

@@ -0,0 +1,17 @@
+package com.nju.edu.eval.service;
+
+import com.nju.edu.eval.data.entity.Score;
+import com.nju.edu.eval.model.vo.score.PersonalScoreReportVO;
+
+/**
+ * @author mars
+ */
+public interface ScoreService {
+    /**
+     * 获得学生某次考试自己的成绩报告
+     * @param userId 用户Id
+     * @param examId 考试Id
+     * @return 成绩报告
+     */
+    PersonalScoreReportVO getPersonalScoreReport(Integer userId, Integer examId);
+}

+ 46 - 0
src/main/java/com/nju/edu/eval/service/UserService.java

@@ -0,0 +1,46 @@
+package com.nju.edu.eval.service;
+
+import com.nju.edu.eval.model.vo.user.UpdateUserVO;
+import com.nju.edu.eval.model.vo.user.UserVO;
+
+import java.util.List;
+
+/**
+ * @author mars
+ */
+public interface UserService {
+    /**
+     * 新建用户
+     * @param userVO
+     * @return 从门户系统新建的用户
+     */
+    UserVO createUser(UserVO userVO);
+
+    /**
+     * 通过id获得用户
+     * @param id
+     * @return
+     */
+    UserVO getUserById(Integer id);
+
+    /**
+     * 从token获得的信息更新用户,保持和门户系统的一致。只能更新邮箱和用户名
+     * @param updateUserVO
+     * @return
+     */
+    UserVO updateUser(UpdateUserVO updateUserVO);
+
+    /**
+     * 判断某用户是否存在
+     * @param id
+     * @return
+     */
+    boolean existsById(Integer id);
+
+    /**
+     * 获得参加某次考试的所有用户
+     * @param examId
+     * @return
+     */
+    List<UserVO> getUsersByExamId(Integer examId);
+}

+ 37 - 0
src/main/java/com/nju/edu/eval/service/convertor/ExamConvertor.java

@@ -0,0 +1,37 @@
+package com.nju.edu.eval.service.convertor;
+
+import com.nju.edu.eval.data.entity.Exam;
+import com.nju.edu.eval.model.vo.exam.ExamInfoVO;
+import com.nju.edu.eval.model.vo.exam.ExamVO;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @author mars
+ */
+public class ExamConvertor {
+    public static Exam convertToEntity(ExamVO examVO) {
+        Exam exam = new Exam();
+        BeanUtils.copyProperties(examVO, exam);
+        exam.setId(examVO.getExamId());
+        return exam;
+    }
+
+    public static ExamVO convertToVO(Exam exam) {
+        ExamVO examVO = new ExamVO();
+        BeanUtils.copyProperties(exam, examVO);
+        examVO.setExamId(exam.getId());
+        examVO.setCreatorId(exam.getCreator().getId());
+        examVO.setAntiCheating(exam.getAntiCheating() == 1);
+        examVO.setIsPublic(exam.getIsPublic() == 1);
+        examVO.setIsJoined(false);
+        return examVO;
+    }
+
+    public static ExamInfoVO convertToExamInfoVO(Exam exam) {
+        ExamInfoVO examInfoVO = new ExamInfoVO();
+        BeanUtils.copyProperties(exam, examInfoVO);
+        examInfoVO.setExamId(exam.getId());
+        return examInfoVO;
+    }
+
+}

+ 18 - 0
src/main/java/com/nju/edu/eval/service/convertor/RecordConvertor.java

@@ -0,0 +1,18 @@
+package com.nju.edu.eval.service.convertor;
+
+import cn.seecoder.bok.common.vo.RecordVO;
+import com.nju.edu.eval.data.entity.QuestionRecord;
+import com.nju.edu.eval.model.vo.record.QuestionRecordVO;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @author mars
+ */
+public class RecordConvertor {
+    public static QuestionRecordVO convertToVO(QuestionRecord questionRecord) {
+        QuestionRecordVO recordVO = new QuestionRecordVO();
+        BeanUtils.copyProperties(questionRecord, recordVO);
+        recordVO.setStudentId(questionRecord.getUser().getId());
+        return recordVO;
+    }
+}

+ 17 - 0
src/main/java/com/nju/edu/eval/service/convertor/ScoreConvertor.java

@@ -0,0 +1,17 @@
+package com.nju.edu.eval.service.convertor;
+
+
+import com.nju.edu.eval.data.entity.Score;
+import com.nju.edu.eval.model.vo.score.PersonalScoreReportVO;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @author mars
+ */
+public class ScoreConvertor {
+    public static PersonalScoreReportVO convertToVO(Score score) {
+        PersonalScoreReportVO scoreReportVO = new PersonalScoreReportVO();
+        BeanUtils.copyProperties(score, scoreReportVO);
+        return scoreReportVO;
+    }
+}

+ 22 - 0
src/main/java/com/nju/edu/eval/service/convertor/UserConvertor.java

@@ -0,0 +1,22 @@
+package com.nju.edu.eval.service.convertor;
+
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.model.vo.user.UserVO;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @author mars
+ */
+public class UserConvertor {
+    public static User convertToEntity(UserVO userVO) {
+        User user = new User();
+        BeanUtils.copyProperties(userVO, user);
+        return user;
+    }
+
+    public static UserVO convertToVO(User user) {
+        UserVO userVO = new UserVO();
+        BeanUtils.copyProperties(user, userVO);
+        return userVO;
+    }
+}

+ 228 - 0
src/main/java/com/nju/edu/eval/service/serviceImpl/ExamServiceImpl.java

@@ -0,0 +1,228 @@
+package com.nju.edu.eval.service.serviceImpl;
+
+import cn.seecoder.bok.common.QuestionType;
+import com.nju.edu.eval.data.dao.ExamDao;
+import com.nju.edu.eval.data.dao.SampleExamPaperDao;
+import com.nju.edu.eval.data.dao.ScoreDao;
+import com.nju.edu.eval.data.dao.UserDao;
+import com.nju.edu.eval.data.entity.Exam;
+import com.nju.edu.eval.data.entity.SampleExamPaper;
+import com.nju.edu.eval.data.entity.Score;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.exception.MyServiceException;
+import com.nju.edu.eval.manager.QuestionManager;
+import com.nju.edu.eval.model.vo.exam.*;
+import com.nju.edu.eval.model.vo.question.QuestionAndScoreVO;
+import com.nju.edu.eval.service.ExamService;
+import com.nju.edu.eval.service.convertor.ExamConvertor;
+import com.nju.edu.eval.util.InviteCodeUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author mars
+ */
+@Service
+public class ExamServiceImpl implements ExamService {
+    private final ExamDao examDao;
+    private final UserDao userDao;
+    private final ScoreDao scoreDao;
+    private final SampleExamPaperDao sampleExamPaperDao;
+    private final QuestionManager questionManager;
+
+    @Autowired
+    public ExamServiceImpl(ExamDao examDao, UserDao userDao, ScoreDao scoreDao, QuestionManager questionManager, SampleExamPaperDao sampleExamPaperDao) {
+        this.examDao = examDao;
+        this.userDao = userDao;
+        this.scoreDao = scoreDao;
+        this.questionManager = questionManager;
+        this.sampleExamPaperDao = sampleExamPaperDao;
+    }
+
+    @Override
+    public NewExamVO createExam(CreateExamVO examVO, Integer creatorId) {
+        User user = userDao.findById(creatorId).get();
+        Exam exam = new Exam(examVO.getExamName(), examVO.getPosition(),
+                                                      examVO.getSkills(), examVO.getStartTime(),
+                                                      examVO.getEndTime(), examVO.getComment(), user);
+        // 只有非公开的考试才需要邀请码
+        if (!examVO.getIsPublic()) {
+            exam.setIsPublic(0);
+            exam.setInviteCode(InviteCodeUtil.produceInviteCode());
+        } else {
+            exam.setIsPublic(1);
+        }
+
+        if (examVO.getAntiCheating()) {
+            exam.setAntiCheating(1);
+        } else {
+            exam.setAntiCheating(0);
+        }
+        examDao.save(exam);
+//        List<CompleteQuestionVO> questionVOs = questionManager.getRecommendQuestions(examVO.getSkills(), examVO.getDifficulty());
+        return new NewExamVO(exam.getId(), exam.getInviteCode(), exam.getDifficulty());
+    }
+
+    @Override
+    public ExamListAndNumVO getExamList(int userId, int pageSize, int pageNum) {
+        Page<Exam> examEntities = examDao.findAll(PageRequest.of(pageNum, pageSize, Sort.by(Sort.Direction.DESC, "startTime")));
+        List<ExamVO> examVOList = examEntities.get()
+                .map(ExamConvertor::convertToVO).collect(Collectors.toList());
+        User user = userDao.findById(userId).orElse(null);
+        List<Exam> currentUserExams = user.getExams();
+        for (ExamVO examVO : examVOList) {
+            Exam exam = examDao.findById(examVO.getExamId()).orElse(null);
+            if (currentUserExams.contains(exam)) {
+                examVO.setIsJoined(true);
+            }
+        }
+        return new ExamListAndNumVO(examVOList, (int) examEntities.getTotalElements());
+    }
+
+    @Override
+    public List<ExamInfoVO> getExamListOfTimeRange(Date startTime, Date endTime) {
+        List<Exam> examList = examDao.findAllByStartTimeBetweenOrderByStartTime(startTime, endTime);
+        return examList.stream().map(ExamConvertor::convertToExamInfoVO).collect(Collectors.toList());
+    }
+
+    @Override
+    public ExamVO getExamById(Integer id) {
+        Exam exam = examDao.findById(id).orElse(null);
+        return ExamConvertor.convertToVO(exam);
+    }
+
+    @Override
+    public QuestionAndScoreVO getExamQuestionAndScoreById(Integer id) {
+        Exam exam = examDao.findById(id).orElse(null);
+        if (exam == null) {
+            throw new MyServiceException("A0411", "没有对应的考试");
+        }
+        if (exam.getQuestionAndScore().size() == 0) {
+            throw new MyServiceException("A0411", "该考试还未设置题目");
+        }
+        return new QuestionAndScoreVO(exam.getQuestionAndScore());
+    }
+
+    @Override
+    public List<String> getCodeQuestionIdsByExamId(Integer id) {
+        Exam exam = examDao.findById(id).orElse(null);
+        if (exam == null) {
+            throw new MyServiceException("A0411", "没有对应的考试");
+        }
+        Set<String> questionIds =  exam.getQuestionAndScore().keySet();
+        return questionIds.stream()
+                .filter(questionId -> questionManager.getQuestionById(questionId).get().getType() == QuestionType.CODE)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void updateQuestionsOfExam(Integer examId, QuestionScoreMapVO questionScoreMapVO) {
+        Exam exam = examDao.findById(examId).orElse(null);
+        if (exam == null) {
+            throw new MyServiceException("A0411", "没有对应的考试");
+        }
+        Map<String, Double> questionScoreMap = questionScoreMapVO.getQuestionScoreMap();
+//        for (Map.Entry<String, Double> entry : questionScoreMap.entrySet()) {
+//            exam.getQuestionAndScore().put(entry.getKey(), entry.getValue());
+//        }
+        exam.setQuestionAndScore(questionScoreMap);
+        examDao.save(exam);
+    }
+
+    @Override
+    public Optional<Exam> extendExamTime(Integer examId, Date endTime) throws MyServiceException {
+        Exam exam = examDao.findById(examId).orElse(null);
+        if (exam != null) {
+            if (endTime.before(exam.getStartTime()) || endTime.before(exam.getEndTime())) {
+                throw new MyServiceException("A0420", "结束时间不能早于原先结束时间和开始时间");
+            }
+            exam.setEndTime(endTime);
+            examDao.save(exam);
+        } else {
+            throw new MyServiceException("A0411", "没有对应的考试");
+        }
+        return Optional.of(exam);
+    }
+
+    @Override
+    public ExamListAndNumVO getExamsByCurrentStudent(Integer id, int pageSize, int pageNum, int state) {
+        User student = userDao.findById(id).orElse(null);
+        if (student != null) {
+            List<Exam> exams = student.getExams();
+            return new ExamListAndNumVO(
+                    exams.stream().sorted(Comparator.comparing(Exam::getStartTime).reversed())
+                            .filter(exam -> state == 0 || exam.getState().ordinal() + 1 == state)
+                            .skip(pageSize * (pageNum - 1)).limit(pageSize)
+                            .map(ExamConvertor::convertToVO).collect(Collectors.toList()),
+                    //输出总个数,用于前端分页
+                    (int) exams.stream().filter(exam -> state == 0 || exam.getState().ordinal() + 1 == state).count());
+        } else {
+            throw new MyServiceException("A0201", "用户账号不存在");
+        }
+    }
+
+    @Override
+    public ExamListAndNumVO getExamsCreatedByCurrentTeacherOrHR(Integer id, int pageSize, int pageNum, int state) {
+        User user = userDao.findById(id).orElse(null);
+        if (user != null) {
+            return new ExamListAndNumVO(examDao.findAllByCreator_Id(user.getId()).stream()
+                    .filter(exam -> state == 0 || exam.getState().ordinal() + 1 == state)
+                    .sorted(Comparator.comparing(Exam::getStartTime).reversed())
+                    .skip(pageSize * (pageNum - 1)).limit(pageSize)
+                    .map(ExamConvertor::convertToVO).collect(Collectors.toList()),
+                    //输出总个数,用于前端分页
+                    (int) examDao.findAllByCreator_Id(user.getId()).stream()
+                            .filter(exam -> state == 0 || exam.getState().ordinal() + 1 == state).count());
+        } else {
+            throw new MyServiceException("A0201", "用户账号不存在");
+        }
+    }
+
+    @Override
+    public boolean takeExam(Integer userId, Integer examId, String inviteCodeInput) {
+        User user = userDao.findById(userId).orElse(null);
+        System.out.println(user.getExams());
+        Exam exam = examDao.findById(examId).orElse(null);
+        if (user.getExams().contains(exam)) {
+            throw new MyServiceException("A0506", "已加入考试!");
+        }
+        if (user != null) {
+            String correctInviteCode = exam.getInviteCode();
+            if (exam.getIsPublic() == 1 || correctInviteCode.equals(inviteCodeInput)) {
+//                List<Exam> examsOfUser = user.getExams();
+//                // 更新用户参加的考试
+//                examsOfUser.add(exam);
+//                userDao.save(user);
+                Score score = new Score(user, exam);
+                scoreDao.save(score);
+                return true;
+            } else {
+                throw new MyServiceException("A0210", "邀请码输入错误");
+            }
+        } else {
+            throw new MyServiceException("A0201", "用户账号不存在");
+        }
+    }
+
+    @Override
+    public void setExamQuestionsBySampleExamPaper(int examId, int sampleExamPaperId) {
+        Exam exam = examDao.findById(examId).orElse(null);
+        SampleExamPaper sampleExamPaper = sampleExamPaperDao.findById(sampleExamPaperId).orElse(null);
+        if (exam == null) {
+            throw new MyServiceException("A0201", "无对应的考试");
+        }
+        if (sampleExamPaper == null) {
+            throw new MyServiceException("A0201", "无对应样卷");
+        }
+
+        Map<String, Double> questionAndScore = new HashMap<>(sampleExamPaper.getQuestionAndScore());
+        exam.setQuestionAndScore(questionAndScore);
+        examDao.save(exam);
+    }
+}

+ 215 - 0
src/main/java/com/nju/edu/eval/service/serviceImpl/JudgeServiceImpl.java

@@ -0,0 +1,215 @@
+package com.nju.edu.eval.service.serviceImpl;
+
+import cn.seecoder.codejudge.client.JudgeDTO;
+import cn.seecoder.codejudge.client.JudgeResult;
+import cn.seecoder.codejudge.client.JudgeResultType;
+import cn.seecoder.judgement.dto.StudentAnswerDTO;
+import cn.seecoder.judgement.vo.JudgedAnswersVO;
+import com.nju.edu.eval.data.dao.ExamDao;
+import com.nju.edu.eval.data.dao.RecordDao;
+import com.nju.edu.eval.data.dao.ScoreDao;
+import com.nju.edu.eval.data.dao.UserDao;
+import com.nju.edu.eval.data.entity.Exam;
+import com.nju.edu.eval.data.entity.QuestionRecord;
+import com.nju.edu.eval.data.entity.Score;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.exception.MyServiceException;
+import com.nju.edu.eval.exception.ThirdPartyServiceException;
+import com.nju.edu.eval.manager.JudgeManager;
+import com.nju.edu.eval.manager.QuestionManager;
+import com.nju.edu.eval.model.vo.judge.CodeJudgeVO;
+import com.nju.edu.eval.model.vo.judge.ObjectiveJudgeResOfExerciseVO;
+import com.nju.edu.eval.model.vo.judge.ObjectiveJudgeResVO;
+import com.nju.edu.eval.model.vo.judge.ObjectiveJudgeVO;
+import com.nju.edu.eval.model.vo.question.CompleteQuestionVO;
+import com.nju.edu.eval.publicdatas.enums.CodeJudgeResultType;
+import com.nju.edu.eval.service.JudgeService;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author mars
+ */
+@Service
+public class JudgeServiceImpl implements JudgeService {
+    private final JudgeManager judgeManager;
+    private final QuestionManager questionManager;
+    private final ScoreDao scoreDao;
+    private final RecordDao recordDao;
+    private final ExamDao examDao;
+    private final UserDao userDao;
+    public JudgeServiceImpl(JudgeManager judgeManager, QuestionManager questionManager, ScoreDao scoreDao, RecordDao recordDao, ExamDao examDao, UserDao userDao) {
+        this.judgeManager = judgeManager;
+        this.questionManager = questionManager;
+        this.scoreDao = scoreDao;
+        this.recordDao = recordDao;
+        this.examDao = examDao;
+        this.userDao = userDao;
+    }
+
+
+    @Override
+    public ObjectiveJudgeResVO judgeObjectiveQuestionAnswers(Integer userId, Integer examId, ObjectiveJudgeVO objectiveJudgeVO) {
+        try {
+            // 提交给判题系统
+            JudgedAnswersVO judgedAnswersVO = judgeManager.judgeObjectiveQuestionAnswers(userId, objectiveJudgeVO);
+
+            User user = userDao.findById(userId).orElse(null);
+            Exam exam = examDao.findById(examId).orElse(null);
+            Score score = scoreDao.findByUserIdAndExamId(userId, examId);
+            Date currentTime = new Date();
+            if (score == null) {
+                score = new Score(user, exam);
+            }
+            score.setSubmitTime(currentTime);
+            scoreDao.save(score);
+            List<QuestionRecord> records = score.getRecords();
+
+            double curScore = 0.0;
+            // 存储用户提交的记录
+            for (Map.Entry<String, StudentAnswerDTO.AnswerRecordDTO> entry : objectiveJudgeVO.getRecords().entrySet()) {
+                // 获得该题满分
+                assert exam != null;
+                String questionId = entry.getKey();
+                double scoreOfQuestion = exam.getQuestionAndScore().get(questionId);
+                // 看该题是否正确
+                boolean isTrue = judgedAnswersVO.getAnswers().get(questionId).getCorrect();
+                double actualScore = isTrue ? scoreOfQuestion : 0;
+                curScore += actualScore;
+                QuestionRecord questionRecord = new QuestionRecord(user, exam, entry.getKey(), score, (String) entry.getValue().getAnswer(), actualScore, currentTime);
+                records.add(questionRecord);
+            }
+            double maxScore = Math.max(score.getScore() - score.getObjectiveScore() + curScore, score.getScore());
+            score.setObjectiveScore(Math.max(curScore, score.getObjectiveScore()));
+            score.setScore(maxScore);
+            scoreDao.save(score);
+            return new ObjectiveJudgeResVO(judgedAnswersVO.getSubmitAt(), judgedAnswersVO.getSubmitNumber());
+
+        } catch (ThirdPartyServiceException exception) {
+            throw new MyServiceException(exception.getCode(), exception.getMessage());
+        }
+    }
+
+    @Override
+    public JudgeResult judgeCodeQuestionAnswers(Integer userId, Integer examId, String questionId, CodeJudgeVO codeJudgeVO) {
+        try {
+            JudgeDTO judgeDTO = new JudgeDTO();
+            judgeDTO.setCode(codeJudgeVO.getCode());
+            judgeDTO.setLanguage(codeJudgeVO.getLanguage());
+
+            CompleteQuestionVO question = questionManager.getQuestionById(questionId).get();
+            judgeDTO.setMemoryLimit(question.getMemoryLimit());
+            judgeDTO.setInputList(question.getInputOutputMapping().entrySet().stream().map(Map.Entry<String,String>::getKey).collect(Collectors.toList()));
+            judgeDTO.setOutputList(question.getInputOutputMapping().entrySet().stream().map(Map.Entry<String,String>::getValue).collect(Collectors.toList()));
+
+            JudgeResult judgeResult = judgeManager.judgeCodeQuestionAnswers(judgeDTO);
+            CodeJudgeResultType message = CodeJudgeResultType.values()[JudgeResultType.ACCEPT.ordinal()];
+            for (JudgeResult.Item item : judgeResult.getResultList()) {
+                if (item.getResult() != JudgeResultType.ACCEPT) {
+                    message = CodeJudgeResultType.values()[item.getResult().ordinal()];
+                    break;
+                }
+            }
+
+            User user = userDao.findById(userId).orElse(null);
+            Exam exam = examDao.findById(examId).orElse(null);
+            Score score = scoreDao.findByUserIdAndExamId(userId, examId);
+            Date currentTime = new Date();
+            if (score == null) {
+                score = new Score(user, exam);
+            }
+            score.setSubmitTime(currentTime);
+            scoreDao.save(score);
+            List<QuestionRecord> records = score.getRecords();
+
+            // 获得上一次用户提交的代码分数,如果没有,那就是0
+            double preScore = 0.0;
+            QuestionRecord previousRecord = recordDao.findFirstByUserIdAndExamIdAndQuestionIdOrderByActualScore(userId, examId, questionId);
+            if (previousRecord != null) {
+                preScore = previousRecord.getActualScore();
+            }
+            double curScore = 0.0;
+
+            // 存储用户提交的记录
+            // 获得该题满分
+            double scoreOfQuestion = exam.getQuestionAndScore().get(questionId);
+
+
+            // 遍历OJ结果,如果该测试用例AC则得分
+            int accecptedNum = 0;
+            for (JudgeResult.Item item: judgeResult.getResultList()) {
+                if (item.getResult() == JudgeResultType.ACCEPT) {
+                    accecptedNum++;
+                }
+            }
+
+            // 获得该题满分
+            curScore = ((double) accecptedNum / judgeResult.getResultList().size()) * scoreOfQuestion;
+            QuestionRecord questionRecord = new QuestionRecord(user, exam, questionId, score, judgeDTO.getCode(), curScore, currentTime);
+            questionRecord.setMessage(message);
+            records.add(questionRecord);
+
+            // 更新成绩报告中的分数,通过减去该题最高得分来获得
+            double maxScore = Math.max(score.getScore() - preScore + curScore, score.getScore());
+            score.setScore(maxScore);
+            scoreDao.save(score);
+            return judgeResult;
+        } catch (ThirdPartyServiceException exception) {
+            throw new MyServiceException(exception.getCode(), exception.getMessage());
+        }
+    }
+
+    @Override
+    public ObjectiveJudgeResOfExerciseVO judgeObjectiveQuestionAnswersOfExercise(Integer userId, ObjectiveJudgeVO objectiveJudgeVO) {
+        try {
+            // 提交给判题系统
+            JudgedAnswersVO judgedAnswersVO = judgeManager.judgeObjectiveQuestionAnswers(userId, objectiveJudgeVO);
+            Map<String, Boolean> result = new HashMap<>();
+
+            User user = userDao.findById(userId).orElse(null);
+            Date currentTime = new Date();
+
+            double curScore = 0.0;
+            // 存储用户提交的记录
+            for (Map.Entry<String, StudentAnswerDTO.AnswerRecordDTO> entry : objectiveJudgeVO.getRecords().entrySet()) {
+                // 获得该题满分
+                String questionId = entry.getKey();
+                // 看该题是否正确
+                boolean isTrue = judgedAnswersVO.getAnswers().get(questionId).getCorrect();
+                result.put(questionId, isTrue);
+            }
+
+            return new ObjectiveJudgeResOfExerciseVO(result);
+
+        } catch (ThirdPartyServiceException exception) {
+            throw new MyServiceException(exception.getCode(), exception.getMessage());
+        }
+    }
+
+    @Override
+    public JudgeResult judgeCodeQuestionAnswersOfExercise(Integer userId, String questionId, CodeJudgeVO codeJudgeVO) {
+        try {
+            JudgeDTO judgeDTO = new JudgeDTO();
+            judgeDTO.setCode(codeJudgeVO.getCode());
+            judgeDTO.setLanguage(codeJudgeVO.getLanguage());
+
+            CompleteQuestionVO question = questionManager.getQuestionById(questionId).get();
+            judgeDTO.setMemoryLimit(question.getMemoryLimit());
+            judgeDTO.setInputList(question.getInputOutputMapping().entrySet().stream().map(Map.Entry<String,String>::getKey).collect(Collectors.toList()));
+            judgeDTO.setOutputList(question.getInputOutputMapping().entrySet().stream().map(Map.Entry<String,String>::getValue).collect(Collectors.toList()));
+
+            JudgeResult judgeResult = judgeManager.judgeCodeQuestionAnswers(judgeDTO);
+            return judgeResult;
+
+        } catch (ThirdPartyServiceException exception) {
+            throw new MyServiceException(exception.getCode(), exception.getMessage());
+        }
+    }
+
+
+}

+ 56 - 0
src/main/java/com/nju/edu/eval/service/serviceImpl/QuestionServiceImpl.java

@@ -0,0 +1,56 @@
+package com.nju.edu.eval.service.serviceImpl;
+
+import com.nju.edu.eval.data.dao.SampleExamPaperDao;
+import com.nju.edu.eval.data.entity.SampleExamPaper;
+import com.nju.edu.eval.exception.MyServiceException;
+import com.nju.edu.eval.exception.ThirdPartyServiceException;
+import com.nju.edu.eval.manager.QuestionManager;
+import com.nju.edu.eval.model.vo.question.QuestionStemListVO;
+import com.nju.edu.eval.model.vo.question.QuestionStemVO;
+import com.nju.edu.eval.service.QuestionService;
+import com.nju.edu.eval.model.vo.question.CompleteQuestionVO;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author mars
+ */
+@Service
+public class QuestionServiceImpl implements QuestionService {
+    private final QuestionManager questionManager;
+    private final SampleExamPaperDao sampleExamPaperDao;
+    public QuestionServiceImpl(QuestionManager questionManager, SampleExamPaperDao sampleExamPaperDao) {
+        this.questionManager = questionManager;
+        this.sampleExamPaperDao = sampleExamPaperDao;
+    }
+
+    @Override
+    public Optional<CompleteQuestionVO> getQuestionById(String id) {
+        try {
+             return questionManager.getQuestionById(id);
+        } catch (ThirdPartyServiceException e) {
+            throw new MyServiceException(e.getCode(), e.getMessage());
+        }
+    }
+
+    @Override
+    public Optional<QuestionStemVO> getQuestionStemById(String id) {
+        try {
+            return questionManager.getQuestionStemById(id);
+        } catch (ThirdPartyServiceException e) {
+            throw new MyServiceException(e.getCode(), e.getMessage());
+        }
+    }
+
+    @Override
+    public List<SampleExamPaper> getSampleExamPaperList() {
+        return sampleExamPaperDao.findAllByIdNotNull();
+    }
+
+    @Override
+    public QuestionStemListVO getQuestionList(String stem, List<String> tags, String knowledgeName, int pageSize, int pageNum) {
+        return questionManager.getQuestionList(stem, tags, knowledgeName, pageSize, pageNum);
+    }
+}

+ 36 - 0
src/main/java/com/nju/edu/eval/service/serviceImpl/RecordServiceImpl.java

@@ -0,0 +1,36 @@
+package com.nju.edu.eval.service.serviceImpl;
+
+import com.nju.edu.eval.data.dao.RecordDao;
+import com.nju.edu.eval.data.entity.QuestionRecord;
+import com.nju.edu.eval.model.vo.record.QuestionRecordVO;
+import com.nju.edu.eval.service.RecordService;
+import com.nju.edu.eval.service.convertor.RecordConvertor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+/**
+ * @author mars
+ */
+@Service
+public class RecordServiceImpl implements RecordService {
+    private final RecordDao recordDao;
+    @Autowired
+    public RecordServiceImpl(RecordDao recordDao) {
+        this.recordDao = recordDao;
+    }
+
+    @Override
+    public List<QuestionRecordVO> getQuestionRecordsOfExam(Integer userId, String questionId, Integer examId) {
+        return recordDao.findAllByUserIdAndQuestionIdAndExamId(userId, questionId, examId)
+                .stream().map(RecordConvertor::convertToVO).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<QuestionRecordVO> getAllQuestionRecordsOfExam(Integer examId) {
+        return recordDao.findAllByExamIdOrderByIdDesc(examId).stream().map(RecordConvertor::convertToVO).collect(Collectors.toList());
+    }
+}

+ 27 - 0
src/main/java/com/nju/edu/eval/service/serviceImpl/ScoreServiceImpl.java

@@ -0,0 +1,27 @@
+package com.nju.edu.eval.service.serviceImpl;
+
+import com.nju.edu.eval.data.dao.ScoreDao;
+import com.nju.edu.eval.data.entity.Score;
+import com.nju.edu.eval.model.vo.score.PersonalScoreReportVO;
+import com.nju.edu.eval.service.ScoreService;
+import com.nju.edu.eval.service.convertor.ScoreConvertor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author mars
+ */
+@Service
+public class ScoreServiceImpl implements ScoreService {
+    private final ScoreDao scoreDao;
+
+    @Autowired
+    public ScoreServiceImpl(ScoreDao scoreDao) {
+        this.scoreDao = scoreDao;
+    }
+
+    @Override
+    public PersonalScoreReportVO getPersonalScoreReport(Integer userId, Integer examId) {
+        return ScoreConvertor.convertToVO(scoreDao.findByUserIdAndExamId(userId, examId));
+    }
+}

+ 75 - 0
src/main/java/com/nju/edu/eval/service/serviceImpl/UserServiceImpl.java

@@ -0,0 +1,75 @@
+package com.nju.edu.eval.service.serviceImpl;
+
+import com.nju.edu.eval.data.dao.ExamDao;
+import com.nju.edu.eval.data.dao.UserDao;
+import com.nju.edu.eval.data.entity.Exam;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.exception.MyServiceException;
+import com.nju.edu.eval.service.UserService;
+import com.nju.edu.eval.service.convertor.UserConvertor;
+import com.nju.edu.eval.model.vo.user.UpdateUserVO;
+import com.nju.edu.eval.model.vo.user.UserVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+/**
+ * @author mars
+ */
+@Service
+public class UserServiceImpl implements UserService {
+    private final UserDao userDao;
+    private final ExamDao examDao;
+
+    @Autowired
+    public UserServiceImpl(UserDao userDao, ExamDao examDao) {
+        this.userDao = userDao;
+        this.examDao = examDao;
+    }
+
+    @Override
+    public UserVO createUser(UserVO userVO) {
+        User user = userDao.save(UserConvertor.convertToEntity(userVO));
+        return UserConvertor.convertToVO(user);
+    }
+
+    @Override
+    public UserVO getUserById(Integer id) {
+        User user = userDao.findById(id).orElse(null);
+        if (user != null) {
+            return UserConvertor.convertToVO(user);
+        } else {
+            throw new MyServiceException("A0201", "用户账号不存在");
+        }
+    }
+
+    @Override
+    public UserVO updateUser(UpdateUserVO updateUserVO) {
+        User user = userDao.findById(updateUserVO.getId()).orElse(null);
+        if (user != null) {
+            user.setEmail(updateUserVO.getEmail());
+            user.setName(updateUserVO.getName());
+            return UserConvertor.convertToVO(user);
+        } else {
+            throw new MyServiceException("A0201", "用户账号不存在");
+        }
+    }
+
+    @Override
+    public boolean existsById(Integer id) {
+        return userDao.existsById(id);
+    }
+
+    @Override
+    public List<UserVO> getUsersByExamId(Integer examId) {
+        Exam exam = examDao.findById(examId).orElse(null);
+        if (exam != null) {
+            return userDao.findAllByExamsContains(exam).stream().map(UserConvertor::convertToVO).collect(Collectors.toList());
+        }
+        return Collections.emptyList();
+    }
+}

+ 20 - 0
src/main/java/com/nju/edu/eval/thirdparty/CodeJudgeThirdPartyService.java

@@ -0,0 +1,20 @@
+package com.nju.edu.eval.thirdparty;
+
+import cn.seecoder.codejudge.client.JudgeDTO;
+import cn.seecoder.codejudge.client.JudgeResult;
+
+import java.util.Optional;
+
+/**
+ * @author mars
+ * 封装调用的代码题评测的第三方服务
+ */
+public interface CodeJudgeThirdPartyService {
+    /**
+     * 代码题判题
+     * @param judgeDTO 代码题相关信息
+     * @return 判题结果
+     * @see JudgeResult
+     */
+    Optional<JudgeResult> judgeCodeQuestionAnswers(JudgeDTO judgeDTO);
+}

+ 20 - 0
src/main/java/com/nju/edu/eval/thirdparty/ObjectiveQuestionJudgeThirdPartyService.java

@@ -0,0 +1,20 @@
+package com.nju.edu.eval.thirdparty;
+
+import cn.seecoder.judgement.dto.StudentAnswerDTO;
+import cn.seecoder.judgement.vo.JudgedAnswersVO;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 封装客观题评测的第三方服务
+ * @author mars
+ */
+public interface ObjectiveQuestionJudgeThirdPartyService {
+    /**
+     * 客观题评测
+     * @param studentAnswerDTO 判题服务中的DTO
+     * @return JudgedAnswersVO 判题结果
+     */
+    public Optional<JudgedAnswersVO> judgeObjectiveQuestionAnswers(StudentAnswerDTO studentAnswerDTO);
+}

+ 50 - 0
src/main/java/com/nju/edu/eval/thirdparty/QuestionThirdPartyService.java

@@ -0,0 +1,50 @@
+package com.nju.edu.eval.thirdparty;
+
+import cn.seecoder.bok.common.vo.KnowledgeNodeVO;
+import com.nju.edu.eval.model.vo.question.CompleteQuestionVO;
+import com.nju.edu.eval.model.vo.question.QuestionStemListVO;
+import com.nju.edu.eval.model.vo.question.QuestionStemVO;
+import org.springframework.data.domain.Pageable;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 封装调用的从题库拿题的第三方服务
+ * 参考 http://gogs.seec.seecoder.cn/SEEC-BOK/SEEC-BOK-CLIENT/src/master/seecoder-bok-client/src/main/java/cn/seecoder/bok/client 编写
+ * @author mars
+ */
+public interface QuestionThirdPartyService {
+    /**
+     * 根据id从题库系统获得题目
+     * @param id 题目id
+     * @return QuestionVO
+     * @see CompleteQuestionVO
+     */
+    Optional<CompleteQuestionVO> getQuestionById(String id);
+
+    /**
+     * 获得推荐样卷题目
+     * @param knowledgeList 知识点列表
+     * @param expectedDifficulty 预期样卷难度
+     * @return 推荐的题目列表
+     */
+    List<CompleteQuestionVO> getRecommendQuestions(List<String> knowledgeList, int expectedDifficulty);
+
+    /**
+     * 根据题干,标签,知识点id来进行题目的搜索
+     * @param stem 题干
+     * @param tags 标签
+     * @param knowledgeIds 知识点id
+     * @param pageable 分页参数
+     * @return 题目vo的list
+     */
+    QuestionStemListVO getQuestionList(String stem, List<String> tags, List<String> knowledgeIds, Pageable pageable);
+
+    /**
+     * 获得知识点相关信息
+     * @param name 知识点名字
+     * @return 知识点信息
+     */
+    List<KnowledgeNodeVO> getKnowledgeNode(String name);
+}

+ 39 - 0
src/main/java/com/nju/edu/eval/thirdparty/thirdpartyImpl/CodeJudgeThirdPartyServiceImpl.java

@@ -0,0 +1,39 @@
+package com.nju.edu.eval.thirdparty.thirdpartyImpl;
+
+
+import cn.seecoder.codejudge.client.CodeJudgeClient;
+import cn.seecoder.codejudge.client.CodeJudgeSystemException;
+import cn.seecoder.codejudge.client.JudgeDTO;
+import cn.seecoder.codejudge.client.JudgeResult;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.Optional;
+
+/**
+ * @author mars
+ */
+@Component
+public class CodeJudgeThirdPartyServiceImpl implements com.nju.edu.eval.thirdparty.CodeJudgeThirdPartyService {
+    @Value("${code-judgement-client.host}")
+    private String host;
+
+    private CodeJudgeClient client;
+
+    @PostConstruct
+    public void init() {
+        client = new CodeJudgeClient(host);
+    }
+
+    @Override
+    public Optional<JudgeResult> judgeCodeQuestionAnswers(JudgeDTO judgeDTO) {
+        try {
+            judgeDTO.setTimeLimit(1000);
+            System.out.println(judgeDTO);
+            return Optional.of(client.judge(judgeDTO));
+        } catch (CodeJudgeSystemException judgementException) {
+            return Optional.empty();
+        }
+    }
+}

+ 41 - 0
src/main/java/com/nju/edu/eval/thirdparty/thirdpartyImpl/ObjectiveQuestionJudgeThirdPartyServiceImpl.java

@@ -0,0 +1,41 @@
+package com.nju.edu.eval.thirdparty.thirdpartyImpl;
+
+import cn.seecoder.judgement.JudgementClient;
+import cn.seecoder.judgement.JudgementException;
+import cn.seecoder.judgement.dto.StudentAnswerDTO;
+import cn.seecoder.judgement.vo.JudgedAnswersVO;
+import com.nju.edu.eval.exception.MyServiceException;
+import com.nju.edu.eval.exception.ThirdPartyServiceException;
+import com.nju.edu.eval.thirdparty.ObjectiveQuestionJudgeThirdPartyService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author mars
+ */
+@Component
+public class ObjectiveQuestionJudgeThirdPartyServiceImpl implements ObjectiveQuestionJudgeThirdPartyService {
+    @Value("${objective-judgement-client.host}")
+    private String host;
+
+    private JudgementClient client;
+
+    @PostConstruct
+    public void init() {
+        client = new JudgementClient(host, "");
+    }
+
+    @Override
+    public Optional<JudgedAnswersVO> judgeObjectiveQuestionAnswers(StudentAnswerDTO studentAnswerDTO) {
+        try {
+            return Optional.of(client.submitQuestionStudentAnswer(studentAnswerDTO));
+        } catch (JudgementException judgementException) {
+            System.out.println(judgementException.getMessage());
+            return Optional.empty();
+        }
+    }
+}

+ 65 - 0
src/main/java/com/nju/edu/eval/thirdparty/thirdpartyImpl/QuestionThirdPartyServiceImpl.java

@@ -0,0 +1,65 @@
+package com.nju.edu.eval.thirdparty.thirdpartyImpl;
+
+import cn.seecoder.bok.client.SeecoderBokClient;
+import cn.seecoder.bok.common.vo.KnowledgeNodeVO;
+import cn.seecoder.bok.common.vo.QuestionVO;
+import com.nju.edu.eval.model.vo.question.CompleteQuestionVO;
+import com.nju.edu.eval.model.vo.question.QuestionStemListVO;
+import com.nju.edu.eval.model.vo.question.QuestionStemVO;
+import com.nju.edu.eval.thirdparty.QuestionThirdPartyService;
+import com.nju.edu.eval.util.BeanCopyUtil;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author mars
+ * 封装调用的题库的第三方服务
+ */
+@Component
+public class QuestionThirdPartyServiceImpl implements QuestionThirdPartyService {
+    @Value("${seec-bok-client.host}")
+    private String host;
+
+    private SeecoderBokClient client;
+
+
+    @PostConstruct
+    public void init() {
+        //TODO
+        client = new SeecoderBokClient(host);
+    }
+
+
+    @Override
+    public Optional<CompleteQuestionVO> getQuestionById(String id) {
+        Optional<QuestionVO> response = client.getQuestionById(id);
+        CompleteQuestionVO res = new CompleteQuestionVO();
+        response.ifPresent(questionVO -> BeanUtils.copyProperties(questionVO, res));
+        return Optional.of(res);
+    }
+
+    @Override
+    public List<CompleteQuestionVO> getRecommendQuestions(List<String> knowledgeList, int expectedDifficulty) {
+        List<CompleteQuestionVO> questionVO = new ArrayList<>();
+        return questionVO;
+    }
+
+    @Override
+    public QuestionStemListVO getQuestionList(String stem, List<String> tags, List<String> knowledgeIds, Pageable pageable) {
+        Page<QuestionVO> questionVOPageList = client.getQuestionList(stem, tags, knowledgeIds, pageable);
+        return new QuestionStemListVO(BeanCopyUtil.copyListProperties(questionVOPageList.toList(), QuestionStemVO::new), (int) questionVOPageList.getTotalElements());
+    }
+
+    @Override
+    public List<KnowledgeNodeVO> getKnowledgeNode(String name) {
+        return client.getKnowledgeNode(name);
+    }
+}

+ 46 - 0
src/main/java/com/nju/edu/eval/util/BeanCopyUtil.java

@@ -0,0 +1,46 @@
+package com.nju.edu.eval.util;
+
+import org.springframework.beans.BeanUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * @author mars
+ */
+public class BeanCopyUtil extends BeanUtils {
+
+    /**
+     * 集合数据的拷贝
+     * @param sources: 数据源类
+     * @param target: 目标类::new(eg: UserVO::new)
+     * @return
+     */
+    public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> target) {
+        return copyListProperties(sources, target, null);
+    }
+
+
+    /**
+     * 带回调函数的集合数据的拷贝(可自定义字段拷贝规则)
+     * @param sources: 数据源类
+     * @param target: 目标类::new(eg: UserVO::new)
+     * @param callBack: 回调函数
+     * @return
+     */
+    public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> target, BeanCopyUtilCallBack<S, T> callBack) {
+        List<T> list = new ArrayList<>(sources.size());
+        for (S source : sources) {
+            T t = target.get();
+            copyProperties(source, t);
+            list.add(t);
+            if (callBack != null) {
+                // 回调
+                callBack.callBack(source, t);
+            }
+        }
+        return list;
+    }
+}
+

+ 13 - 0
src/main/java/com/nju/edu/eval/util/BeanCopyUtilCallBack.java

@@ -0,0 +1,13 @@
+package com.nju.edu.eval.util;
+
+@FunctionalInterface
+public interface BeanCopyUtilCallBack <S, T> {
+
+    /**
+     * 定义默认回调方法
+     * @param t
+     * @param s
+     */
+    void callBack(S t, T s);
+}
+

+ 26 - 0
src/main/java/com/nju/edu/eval/util/InviteCodeUtil.java

@@ -0,0 +1,26 @@
+package com.nju.edu.eval.util;
+
+import java.util.Random;
+
+/**
+ * @author mars
+ */
+public class InviteCodeUtil {
+    public static String produceInviteCode() {
+        char[] c = "abcdefghijklmnopqrstuvwxyz".toCharArray();
+
+        Random rd = new Random();
+        String inviteCode = "";
+
+        int codeSize = 6;
+        for (int k = 0; k <= codeSize; k++) {
+            int index = rd.nextInt(c.length);
+            inviteCode += c[index];
+        }
+        return inviteCode;
+    }
+
+    public static void main(String[] args) {
+        System.out.println(produceInviteCode());
+    }
+}

+ 45 - 0
src/main/java/com/nju/edu/eval/util/JwtUtil.java

@@ -0,0 +1,45 @@
+package com.nju.edu.eval.util;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.exceptions.TokenExpiredException;
+import com.auth0.jwt.interfaces.Claim;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.nju.edu.eval.exception.MyServiceException;
+import org.springframework.stereotype.Component;
+import org.springframework.beans.factory.annotation.Value;
+
+import java.util.Date;
+
+
+/**
+ * @author mars
+ */
+@Component
+public class JwtUtil {
+    private static String jwtSecret;
+
+    @Value("${jwt.secret}")
+    public void setJwtSecret(String jwtSecret) { //通过set让static方法读取配置文件中的值
+        JwtUtil.jwtSecret = jwtSecret;
+    }
+
+    public static Claim parseJwt(String token) {
+        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecret)).build();
+        DecodedJWT decodedjwt;
+        try {
+            decodedjwt = jwtVerifier.verify(token);
+        } catch (TokenExpiredException e) {
+            throw new  MyServiceException("A0230", "用户登陆已过期");
+        } catch (JWTVerificationException | IllegalArgumentException e) { //解析错误 或者 token写错
+            throw new MyServiceException("A0200", "登陆失败");
+        }
+//        if (decodedjwt.getClaim("exp").asDate().before(new Date())) {
+//            throw new  MyServiceException("A0230", "用户登陆已过期");
+//        }
+        return decodedjwt.getClaim("user_info");
+    }
+
+}

+ 37 - 0
src/main/java/com/nju/edu/eval/web/Response.java

@@ -0,0 +1,37 @@
+package com.nju.edu.eval.web;
+
+import com.nju.edu.eval.exception.MyServiceException;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.dom4j.util.AttributeHelper;
+import org.hibernate.service.spi.ServiceException;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Response {
+    private String code;
+    private String msg;
+    private Object result;
+
+    public Response(String code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public static Response buildSuccess(Object result) {
+        return new Response("00000", "Success", result);
+    }
+
+    public static Response buildFailed(String code, ServiceException e) {
+        return new Response(code, e.getLocalizedMessage(), null);
+    }
+
+    public static Response buildFailed(String code, String msg) {
+        return new Response(code, msg, null);
+    }
+}

+ 140 - 0
src/main/java/com/nju/edu/eval/web/controller/ExamController.java

@@ -0,0 +1,140 @@
+package com.nju.edu.eval.web.controller;
+
+import com.nju.edu.eval.aspect.auth.Authorized;
+import com.nju.edu.eval.data.entity.Exam;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.model.vo.exam.CreateExamVO;
+import com.nju.edu.eval.model.vo.exam.QuestionScoreMapVO;
+import com.nju.edu.eval.publicdatas.enums.UserRole;
+import com.nju.edu.eval.service.ExamService;
+import com.nju.edu.eval.model.vo.exam.ExamVO;
+import com.nju.edu.eval.service.QuestionService;
+import com.nju.edu.eval.web.Response;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.Date;
+import java.util.Optional;
+
+/**
+ * @author mars
+ */
+@RestController
+@RequestMapping(path = "/api/exam")
+@Api(tags = {"考试"})
+public class ExamController {
+    private final ExamService examService;
+
+    @Autowired
+    public ExamController(ExamService examService) {
+        this.examService = examService;
+    }
+
+    @ApiOperation("创建考试")
+    @PostMapping("")
+    @Authorized(roles = {UserRole.TEACHER, UserRole.HR, UserRole.STUDENT})
+    public Response createExam(@ApiIgnore User user, @RequestBody CreateExamVO examVO) {
+        return Response.buildSuccess(examService.createExam(examVO, user.getId()));
+    }
+
+    @ApiOperation("为创建的考试通过样卷来设置题目")
+    @PostMapping("/question/sample/{examId}/")
+    @Authorized(roles = {UserRole.TEACHER, UserRole.HR})
+    public Response setExamQuestionsBySampleExamPaper(@PathVariable int examId,
+                                                      @RequestParam(value = "sampleExamPaperId") int sampleExamPaperId) {
+        examService.setExamQuestionsBySampleExamPaper(examId, sampleExamPaperId);
+        return Response.buildSuccess(null);
+    }
+
+
+    @ApiOperation("获得所有考试(默认按时间降序)")
+    @GetMapping("/list")
+    @Authorized
+    public Response getExamList(@ApiIgnore User user,
+                                @RequestParam(value = "pageSize") int pageSize,
+                                @RequestParam(value = "pageNum") int pageNum) {
+        return Response.buildSuccess(examService.getExamList(user.getId(), pageSize, pageNum-1));
+    }
+
+    @ApiOperation("获得开始时间在某个时间段内的考试")
+    @GetMapping("/list/range")
+    public Response getExamListOfTimeRange(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam Date startTime,
+                                           @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam Date endTime) {
+        return Response.buildSuccess(examService.getExamListOfTimeRange(startTime, endTime));
+    }
+
+    @ApiOperation("根据考试id获得对应考试相关信息")
+    @GetMapping("/{examId}")
+    public Response getExamById(@PathVariable Integer examId) {
+        return Response.buildSuccess(examService.getExamById(examId));
+    }
+
+    @ApiOperation("根据考试id获得对应考试的题目")
+    @GetMapping("/{examId}/questions")
+    public Response getExamQuestionAndScoreById(@PathVariable Integer examId) {
+        return Response.buildSuccess(examService.getExamQuestionAndScoreById(examId));
+    }
+
+    @ApiOperation("根据考试id获得对应考试的代码题目Id")
+    @GetMapping("/{examId}/questions/code")
+    public Response getCodeQuestionIdsByExamId(@PathVariable Integer examId) {
+        return Response.buildSuccess(examService.getCodeQuestionIdsByExamId(examId));
+    }
+
+    @ApiOperation("为考试更新题目及对应分值")
+    @PutMapping("/{examId}/questions/")
+//    @Authorized(roles = {UserRole.ADMIN, UserRole.HR, UserRole.TEACHER})
+    public Response updateQuestionsOfExam(@PathVariable Integer examId,
+                                      @RequestBody QuestionScoreMapVO questionScoreMapVO) {
+        examService.updateQuestionsOfExam(examId, questionScoreMapVO);
+        return Response.buildSuccess(null);
+    }
+
+    @ApiOperation("延长考试时间")
+    @PutMapping("/{examId}/time")
+    @Authorized(roles = {UserRole.ADMIN, UserRole.HR, UserRole.TEACHER})
+    public Response extendExamTime(@PathVariable Integer examId,
+                                   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam Date endTime)  {
+        Optional<Exam> examEntityOptional = examService.extendExamTime(examId, endTime);
+        return Response.buildSuccess(null);
+        // TODO 提醒参加该考试的同学 email?
+    }
+
+    @ApiOperation("获得学生参加的考试")
+    @ApiParam()
+    @GetMapping("/student/exams")
+    @Authorized(roles = {UserRole.STUDENT})
+    public Response getExamsByCurrentStudent(@ApiIgnore User user,
+                                             @RequestParam(value = "pageSize", required = false) int pageSize,
+                                             @RequestParam(value = "pageNum", required = false) int pageNum,
+                                             @ApiParam(name = "state", value = "0 所有, 1 未开始,2 进行中,3 已结束")
+                                                 @RequestParam(value = "state") int state) {
+        return Response.buildSuccess(examService.getExamsByCurrentStudent(user.getId(), pageSize, pageNum, state));
+    }
+
+    @ApiOperation("获得老师/HR创建的考试")
+    @GetMapping("/teacher/exams")
+    @Authorized(roles = {UserRole.TEACHER, UserRole.HR})
+    public Response getExamsByCurrentTeacherOrHR(@ApiIgnore User user,
+                                                 @RequestParam(value = "pageSize", required = false) int pageSize,
+                                                 @RequestParam(value = "pageNum", required = false) int pageNum,
+                                                 @ApiParam(name = "state", value = "0 所有, 1 未开始,2 进行中,3 已结束")
+                                                     @RequestParam(value = "state") int state) {
+        return Response.buildSuccess(examService.getExamsCreatedByCurrentTeacherOrHR(user.getId(), pageSize, pageNum, state));
+    }
+
+    @ApiOperation("通过邀请码加入考试")
+    @PutMapping("/invite/code")
+    @Authorized(roles = {UserRole.STUDENT})
+    public Response takeExam(@ApiIgnore User user,
+                             @RequestParam(value = "inviteCode", required = false) String inviteCode,
+                             @RequestParam(value = "examId", required = true) Integer examId
+                             ) {
+        return Response.buildSuccess(examService.takeExam(user.getId(), examId, inviteCode));
+    }
+}

+ 76 - 0
src/main/java/com/nju/edu/eval/web/controller/JudgeController.java

@@ -0,0 +1,76 @@
+package com.nju.edu.eval.web.controller;
+
+import cn.seecoder.codejudge.client.JudgeDTO;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.nju.edu.eval.aspect.auth.Authorized;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.model.vo.judge.CodeJudgeVO;
+import com.nju.edu.eval.model.vo.judge.ObjectiveJudgeVO;
+import com.nju.edu.eval.publicdatas.enums.UserRole;
+import com.nju.edu.eval.service.JudgeService;
+import com.nju.edu.eval.web.Response;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.springframework.context.annotation.Role;
+import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.List;
+
+import static org.apache.commons.text.StringEscapeUtils.unescapeHtml4;
+
+/**
+ * @author mars
+ */
+@RestController
+@RequestMapping(path = "/api/judge")
+@Api(tags = {"判题"})
+public class JudgeController {
+    private JudgeService judgeService;
+    public JudgeController(JudgeService judgeService) {
+        this.judgeService = judgeService;
+    }
+
+    @ApiOperation("考试中的客观题判题")
+    @PostMapping(value = "/objective")
+    @Authorized(roles = {UserRole.STUDENT})
+    public Response judgeObjectiveQuestionAnswers(@ApiIgnore User user, @RequestParam(value = "examId") Integer examId,
+                                                  @RequestBody ObjectiveJudgeVO objectiveJudgeVO) {
+        return Response.buildSuccess(judgeService.judgeObjectiveQuestionAnswers(user.getId(), examId, objectiveJudgeVO));
+    }
+
+    @ApiOperation("考试中的代码题判题")
+    @PostMapping("/code")
+    @Authorized(roles = {UserRole.STUDENT})
+    public Response judgeCodeQuestionAnswers(@ApiIgnore User user, @RequestParam(value = "examId") Integer examId,
+                                             @RequestParam(value = "questionId") String questionId,
+                                                  @RequestBody CodeJudgeVO codeJudgeVO) {
+        return Response.buildSuccess(judgeService.judgeCodeQuestionAnswers(user.getId(), examId, questionId, codeJudgeVO));
+    }
+
+    @ApiOperation("练习中的客观题判题")
+    @PostMapping(value = "/objective/test")
+    @Authorized(roles = {UserRole.STUDENT})
+    public Response judgeObjectiveQuestionAnswersOfExercise(@ApiIgnore User user,
+                                                  @RequestBody ObjectiveJudgeVO objectiveJudgeVO) {
+        return Response.buildSuccess(judgeService.judgeObjectiveQuestionAnswersOfExercise(user.getId(), objectiveJudgeVO));
+    }
+
+    @ApiOperation("练习中的代码题判题")
+    @PostMapping("/code/test")
+    @Authorized(roles = {UserRole.STUDENT})
+    public Response judgeCodeQuestionAnswersOfExercise(@ApiIgnore User user,
+                                             @RequestParam(value = "questionId") String questionId,
+                                             @RequestBody CodeJudgeVO codeJudgeVO) {
+        return Response.buildSuccess(judgeService.judgeCodeQuestionAnswersOfExercise(user.getId(), questionId, codeJudgeVO));
+    }
+
+
+
+//    @ApiOperation("针对某次考试的判题")
+//    @PostMapping("/exam/{examId}")
+
+
+}

+ 56 - 0
src/main/java/com/nju/edu/eval/web/controller/QuestionController.java

@@ -0,0 +1,56 @@
+package com.nju.edu.eval.web.controller;
+
+import com.nju.edu.eval.aspect.auth.Authorized;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.publicdatas.enums.UserRole;
+import com.nju.edu.eval.service.QuestionService;
+import com.nju.edu.eval.web.Response;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author mars
+ */
+@RestController
+@RequestMapping(path = "/api/question")
+@Api(tags = {"题目"})
+public class QuestionController {
+    private final QuestionService questionService;
+    public QuestionController(QuestionService questionService) {
+        this.questionService = questionService;
+    }
+
+    @ApiOperation("根据Id获得题目的全部信息,包括解答")
+    @GetMapping("{id}")
+//    @Authorized(roles = {UserRole.HR, UserRole.TEACHER})
+    public Response getQuestionById(@PathVariable String id) {
+        return Response.buildSuccess(questionService.getQuestionById(id));
+    }
+
+    @ApiOperation("根据Id获得题目的题干(学生视角)")
+    @GetMapping("{id}/stem")
+    public Response getQuestionStemById(@PathVariable String id) {
+        return Response.buildSuccess(questionService.getQuestionStemById(id));
+    }
+
+    @ApiOperation("获得所有的固定试卷")
+    @GetMapping("/sample")
+    public Response getSampleExamPaperList() {
+        return Response.buildSuccess(questionService.getSampleExamPaperList());
+    }
+
+    @ApiOperation("根据题干,知识点,获得题目")
+    @GetMapping("/question")
+    public Response getQuestionList(@RequestParam(value = "stem", required = false) String stem,
+                                    @RequestParam(value = "tags", required = false) List<String> tags,
+                                    @RequestParam(value = "knowledgeName", required = false) String knowledgeName,
+                                    @RequestParam(value = "pageSize") int pageSize,
+                                    @RequestParam(value = "pageNum") int pageNum
+                                    ) {
+        return Response.buildSuccess(questionService.getQuestionList(stem, tags, knowledgeName, pageSize, pageNum-1));
+    }
+
+}

+ 40 - 0
src/main/java/com/nju/edu/eval/web/controller/RecordController.java

@@ -0,0 +1,40 @@
+package com.nju.edu.eval.web.controller;
+
+import com.nju.edu.eval.aspect.auth.Authorized;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.service.RecordService;
+import com.nju.edu.eval.service.UserService;
+import com.nju.edu.eval.web.Response;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * @author mars
+ */
+@RestController
+@RequestMapping(path = "/api/record")
+@Api(tags = {"记录"})
+public class RecordController {
+    private final RecordService recordService;
+    @Autowired
+    public RecordController(RecordService recordService) {
+        this.recordService = recordService;
+    }
+
+    @ApiOperation("获得某次考试某个同学关于某道题的所有提交记录")
+    @GetMapping("")
+    @Authorized()
+    public Response getQuestionRecordsOfExam(@ApiIgnore User user, @RequestParam(value = "examId") Integer examId,
+                                   @RequestParam(value = "questionId") String questionId) {
+        return Response.buildSuccess(recordService.getQuestionRecordsOfExam(user.getId(), questionId, examId));
+    }
+
+    @ApiOperation("获得某次考试所有做题记录")
+    @GetMapping("/{examId}")
+    public Response getAllQuestionRecordsOfExam(@PathVariable Integer examId) {
+        return Response.buildSuccess(recordService.getAllQuestionRecordsOfExam(examId));
+    }
+}

+ 36 - 0
src/main/java/com/nju/edu/eval/web/controller/ScoreController.java

@@ -0,0 +1,36 @@
+package com.nju.edu.eval.web.controller;
+
+import com.nju.edu.eval.aspect.auth.Authorized;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.service.ScoreService;
+import com.nju.edu.eval.web.Response;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * @author mars
+ */
+@RestController
+@RequestMapping(path = "/api/report")
+@Api(tags = {"成绩报告"})
+public class ScoreController {
+    private final ScoreService scoreService;
+
+    @Autowired
+    public ScoreController(ScoreService scoreService) {
+        this.scoreService = scoreService;
+    }
+
+    @ApiOperation("获得某次考试的成绩报告(学生视角)")
+    @GetMapping("")
+    @Authorized()
+    public Response getPersonalScoreReport(@ApiIgnore User user, @RequestParam(value = "examId") Integer examId) {
+        return Response.buildSuccess(scoreService.getPersonalScoreReport(user.getId(), examId));
+    }
+}

+ 43 - 0
src/main/java/com/nju/edu/eval/web/controller/UserController.java

@@ -0,0 +1,43 @@
+package com.nju.edu.eval.web.controller;
+
+import com.nju.edu.eval.aspect.auth.Authorized;
+import com.nju.edu.eval.data.entity.User;
+import com.nju.edu.eval.publicdatas.enums.UserRole;
+import com.nju.edu.eval.service.UserService;
+import com.nju.edu.eval.web.Response;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * @author mars
+ */
+@RestController
+@RequestMapping(path = "/api/user")
+@Api(tags = {"用户信息"})
+public class UserController {
+    private final UserService userService;
+    @Autowired
+    public UserController(UserService userService) {
+        this.userService = userService;
+    }
+
+    @ApiOperation("获得当前用户信息")
+    @GetMapping("")
+    @Authorized()
+    public Response getCurrentUser(@ApiIgnore User user) {
+        return Response.buildSuccess(userService.getUserById(user.getId()));
+    }
+
+    @ApiOperation("获得报名某次考试的所有学生")
+    @GetMapping("/{examId}")
+    @Authorized(roles = {UserRole.ADMIN, UserRole.HR, UserRole.TEACHER})
+    public Response getUsersByExamId(@PathVariable Integer examId) {
+        return Response.buildSuccess(userService.getUsersByExamId(examId));
+    }
+}

+ 45 - 0
src/main/resources/application.yml

@@ -0,0 +1,45 @@
+server:
+  port: 8080
+spring:
+  datasource:
+    url: jdbc:mysql://47.111.95.164:3306/eval?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
+    username: root
+    password: mysqltcj
+    driverClassName: com.mysql.cj.jdbc.Driver
+    hikari:
+      max-lifetime: 600000
+      maximum-pool-size: 100
+  jpa:
+#    database-platform: org.hibernate.dialect.MySQL57InnoDBDialect
+    hibernate:
+      ddl-auto: update
+      naming:
+        implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
+        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+    properties:
+      hibernate.enable_lazy_load_no_trans: true
+      hibernate.dialect: com.nju.edu.eval.config.MySQL57InnoDBDialect  # 人为调整数据库编码
+      hibernate.format_sql: true
+    show-sql: true
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
+    parser:
+      #允许出现特殊字符和转义符
+      allow_unquoted_control_chars: true
+      #允许出现单引号
+      allow_single_quotes: true
+
+jwt:
+  header: Authorization
+  secret: SEEC-1919810-OIDC-114514
+
+seec-bok-client:
+  host: http://bok.seec.seecoder.cn
+
+objective-judgement-client:
+  host: http://seecoder-judgement.seec.seecoder.cn
+
+code-judgement-client:
+  host: http://oj.seec.seecoder.cn
+

+ 13 - 0
src/test/java/com/nju/edu/eval/EvalApplicationTests.java

@@ -0,0 +1,13 @@
+package com.nju.edu.eval;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class EvalApplicationTests {
+
+	@Test
+	void contextLoads() {
+	}
+
+}

+ 17 - 0
src/test/java/com/nju/edu/eval/testUtils.java

@@ -0,0 +1,17 @@
+package com.nju.edu.eval;
+
+import com.nju.edu.eval.util.JwtUtil;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+public class testUtils {
+    @Test
+    public void testJwtUtil(){
+        System.out.println(JwtUtil.parseJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwLnNlZWNvZGV" +
+                "yLmNuIiwic3ViIjoiMTg4NTIwODM1NzciLCJhdWQiOiJzZWVjLWhlbHBlciIsImF1dGhfdGltZSI6MTYwNjIwMzQ3MCwiaWF" +
+                "0IjoxNjA2MjAzNDcwLCJleHAiOjE2MDYyODk4NzAsInVzZXJfaW5mbyI6eyJwaG9uZSI6IjE4ODUyMDgzNTc3IiwiZW1haWwi" +
+                "OiIxNzEyNTA2MzVAc21haWwubmp1LmVkdS5jbiIsImlkIjo4MzksIm5hbWUiOiJNYXJzIiwicm9sZSI6IlNUVURFTlQifX0.Iu" +
+                "ZReNYBkOmFvCEm29Wj-9ML_e4EaIxxauGtdWEj6So"));
+    }
+}