Quellcode durchsuchen

代码作业部分,包含了用户、课程、小组等基础部分的搭建。。。

GuardOfDawn vor 7 Jahren
Ursprung
Commit
cfbbe6342e
50 geänderte Dateien mit 1191 neuen und 37 gelöschten Zeilen
  1. 2 2
      config.yml
  2. BIN
      problem/64-three-sum.zip
  3. BIN
      problem/66-two-sum.zip
  4. 57 0
      src/main/java/nju/seec/SEECdemo/data/convertor/ScoreDetailSetConverter.java
  5. 8 0
      src/main/java/nju/seec/SEECdemo/data/dao/CourseDAO.java
  6. 8 0
      src/main/java/nju/seec/SEECdemo/data/dao/GroupDAO.java
  7. 19 0
      src/main/java/nju/seec/SEECdemo/data/dao/UserDAO.java
  8. 8 0
      src/main/java/nju/seec/SEECdemo/data/dao/assignment/CodeDAO.java
  9. 12 0
      src/main/java/nju/seec/SEECdemo/data/dao/assignment/CodeResultDAO.java
  10. 51 0
      src/main/java/nju/seec/SEECdemo/data/entity/Course.java
  11. 54 0
      src/main/java/nju/seec/SEECdemo/data/entity/Group.java
  12. 4 0
      src/main/java/nju/seec/SEECdemo/data/entity/Problem.java
  13. 63 0
      src/main/java/nju/seec/SEECdemo/data/entity/assignment/Code.java
  14. 61 0
      src/main/java/nju/seec/SEECdemo/data/entity/assignment/CodeResult.java
  15. 79 0
      src/main/java/nju/seec/SEECdemo/data/user/User.java
  16. 5 0
      src/main/java/nju/seec/SEECdemo/logic/api/GitlabApi.java
  17. 4 0
      src/main/java/nju/seec/SEECdemo/logic/api/JenkinsApi.java
  18. 24 0
      src/main/java/nju/seec/SEECdemo/logic/api/impl/GitlabApiImpl.java
  19. 11 0
      src/main/java/nju/seec/SEECdemo/logic/api/impl/JenkinsApiImpl.java
  20. 10 0
      src/main/java/nju/seec/SEECdemo/logic/api/vo/GitlabGroup.java
  21. 0 7
      src/main/java/nju/seec/SEECdemo/logic/service/AssignmentService.java
  22. 10 0
      src/main/java/nju/seec/SEECdemo/logic/service/CodeService.java
  23. 9 0
      src/main/java/nju/seec/SEECdemo/logic/service/UserService.java
  24. 0 4
      src/main/java/nju/seec/SEECdemo/logic/service/impl/AssignmentServiceImpl.java
  25. 66 0
      src/main/java/nju/seec/SEECdemo/logic/service/impl/UserServiceImpl.java
  26. 100 0
      src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/AssignmentPaperBuilder.java
  27. 53 0
      src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/CodeServiceImpl.java
  28. 22 0
      src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/CourseServiceStub.java
  29. 50 0
      src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/GitHelper.java
  30. 12 0
      src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/QuestionServiceStub.java
  31. 12 0
      src/main/java/nju/seec/SEECdemo/logic/storage/StorageProvider.java
  32. 32 0
      src/main/java/nju/seec/SEECdemo/logic/storage/StorageProviderConfiguration.java
  33. 43 0
      src/main/java/nju/seec/SEECdemo/logic/storage/impl/LocalStorageProvider.java
  34. 0 7
      src/main/java/nju/seec/SEECdemo/logic/vo/AssignmentVO.java
  35. 48 0
      src/main/java/nju/seec/SEECdemo/logic/vo/CodeWorkVO.java
  36. 27 0
      src/main/java/nju/seec/SEECdemo/logic/vo/UserVO.java
  37. 29 0
      src/main/java/nju/seec/SEECdemo/util/AsyncWrapper.java
  38. 6 0
      src/main/java/nju/seec/SEECdemo/util/Method.java
  39. 30 0
      src/main/java/nju/seec/SEECdemo/util/enums/AssignmentStatus.java
  40. 12 1
      src/main/java/nju/seec/SEECdemo/util/enums/AssignmentType.java
  41. 23 0
      src/main/java/nju/seec/SEECdemo/util/enums/UserRole.java
  42. 0 9
      src/main/java/nju/seec/SEECdemo/web/controller/AssignmentController.java
  43. 27 0
      src/main/java/nju/seec/SEECdemo/web/controller/CodeController.java
  44. 0 7
      src/main/java/nju/seec/SEECdemo/web/dto/AssignmentDTO.java
  45. 10 0
      src/main/java/nju/seec/SEECdemo/web/dto/CodeWorkDTO.java
  46. 18 0
      src/main/java/nju/seec/SEECdemo/web/response/EmptyResponse.java
  47. 17 0
      src/main/java/nju/seec/SEECdemo/web/response/ErrorResponse.java
  48. 33 0
      src/main/java/nju/seec/SEECdemo/web/response/PageResourceResponse.java
  49. 16 0
      src/main/java/nju/seec/SEECdemo/web/response/ResourceResponse.java
  50. 6 0
      src/main/java/nju/seec/SEECdemo/web/response/Response.java

+ 2 - 2
config.yml

@@ -2,9 +2,9 @@ server:
   port: 8080
 spring:
   datasource:
-    url: jdbc:mysql://10.1.1.198:13306/seec2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
+    url: jdbc:mysql://localhost:3306/seec2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
     username: root
-    password: NJU67software
+    password: 123456
   jpa:
     database-platform: org.hibernate.dialect.MySQL57InnoDBDialect
   mail:

BIN
problem/64-three-sum.zip


BIN
problem/66-two-sum.zip


+ 57 - 0
src/main/java/nju/seec/SEECdemo/data/convertor/ScoreDetailSetConverter.java

@@ -0,0 +1,57 @@
+package nju.seec.SEECdemo.data.convertor;
+
+import nju.seec.SEECdemo.data.entity.assignment.CodeResult.Score;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import javax.persistence.AttributeConverter;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ScoreDetailSetConverter implements AttributeConverter<Set<Score>, String> {
+	@Override
+	public String convertToDatabaseColumn(Set<Score> attribute) {
+		return convertSetToJsonArrayString(attribute);
+	}
+
+	private String convertSetToJsonArrayString(Set<Score> scoreSet){
+		JSONArray array = new JSONArray();
+		if(scoreSet.size()!=0){
+			for (Score score : scoreSet) {
+				JSONObject object = new JSONObject();
+				object.put("id", score.getId());
+				object.put("name", score.getName());
+				object.put("score", score.getScore());
+				object.put("detail", convertSetToJsonArrayString(score.getDetail()));
+				array.put(object);
+			}
+		}
+		return array.toString();
+	}
+
+	@Override
+	public Set<Score> convertToEntityAttribute(String dbData) {
+		return convertJsonArrayStringToSet(dbData);
+	}
+
+	private Set<Score> convertJsonArrayStringToSet(String jsonArrayString){
+		Set<Score> scores = new HashSet<>();
+		JSONArray array = new JSONArray(jsonArrayString);
+		if(array.length()!=0){
+			for (Object object : array) {
+				if (object instanceof JSONObject) {
+					try {
+						Score score = new Score();
+						score.setId(((JSONObject) object).getInt("id"));
+						score.setName(((JSONObject) object).getString("name"));
+						score.setScore(((JSONObject) object).getInt("score"));
+						score.setDetail(convertJsonArrayStringToSet(((JSONObject) object).getString("detail")));
+						scores.add(score);
+					} catch (JSONException ignore) { }
+				}
+			}
+		}
+		return scores;
+	}
+}

+ 8 - 0
src/main/java/nju/seec/SEECdemo/data/dao/CourseDAO.java

@@ -0,0 +1,8 @@
+package nju.seec.SEECdemo.data.dao;
+
+import nju.seec.SEECdemo.data.entity.Course;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface CourseDAO extends JpaRepository<Course, Integer> {
+    Course findById(int courseId);
+}

+ 8 - 0
src/main/java/nju/seec/SEECdemo/data/dao/GroupDAO.java

@@ -0,0 +1,8 @@
+package nju.seec.SEECdemo.data.dao;
+
+import nju.seec.SEECdemo.data.entity.Group;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface GroupDAO extends JpaRepository<Group, Integer> {
+    Group findById(int id);
+}

+ 19 - 0
src/main/java/nju/seec/SEECdemo/data/dao/UserDAO.java

@@ -0,0 +1,19 @@
+package nju.seec.SEECdemo.data.dao;
+
+import nju.seec.SEECdemo.data.user.User;
+import nju.seec.SEECdemo.util.enums.UserRole;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserDAO extends JpaRepository<User, Integer> {
+	User findById(int id);
+
+	User findByUsername(String username);
+
+	User findByEmail(String email);
+
+	Page<User> findAllByUsernameLike(String search, Pageable pageable);
+
+	Integer countByRole(UserRole role);
+}

+ 8 - 0
src/main/java/nju/seec/SEECdemo/data/dao/assignment/CodeDAO.java

@@ -0,0 +1,8 @@
+package nju.seec.SEECdemo.data.dao.assignment;
+
+import nju.seec.SEECdemo.data.entity.assignment.Code;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface CodeDAO extends JpaRepository<Code, Integer> {
+    Code findById(int id);
+}

+ 12 - 0
src/main/java/nju/seec/SEECdemo/data/dao/assignment/CodeResultDAO.java

@@ -0,0 +1,12 @@
+package nju.seec.SEECdemo.data.dao.assignment;
+
+import nju.seec.SEECdemo.data.entity.assignment.CodeResult;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Set;
+
+public interface CodeResultDAO extends JpaRepository<CodeResult, Integer> {
+    CodeResult findById(int id);
+
+//    Set<CodeResult> save(Set<CodeResult> codeResultSet);
+}

+ 51 - 0
src/main/java/nju/seec/SEECdemo/data/entity/Course.java

@@ -0,0 +1,51 @@
+package nju.seec.SEECdemo.data.entity;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import nju.seec.SEECdemo.data.entity.assignment.Code;
+import nju.seec.SEECdemo.data.user.User;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+
+import javax.persistence.*;
+import java.util.HashSet;
+import java.util.Set;
+
+@Data
+@EqualsAndHashCode(exclude = {"codeSet", "groupSet", "creator"})
+@ToString(exclude = {"codeSet", "groupSet", "creator"})
+@Entity
+@Table(name = "ENTITY_COURSE")
+public class Course {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Integer id;
+
+    @Basic
+    @Column(name = "name", nullable = false)
+    private String name;
+
+    @ManyToOne(targetEntity = User.class, fetch = FetchType.LAZY)
+    @JoinColumn(name = "creator", referencedColumnName = "id")
+    private User creator;
+
+    @OneToMany(targetEntity = Code.class, mappedBy = "course", cascade = CascadeType.REMOVE)
+    @LazyCollection(LazyCollectionOption.EXTRA)
+    private Set<Code> codeSet = new HashSet<>();
+
+    @ManyToMany(targetEntity = User.class, cascade = CascadeType.DETACH)
+    @JoinTable(name = "LINK_USER_COURSE",
+            joinColumns = @JoinColumn(name = "course", referencedColumnName = "id"),
+            inverseJoinColumns = @JoinColumn(name = "user", referencedColumnName = "id")
+    )
+    @LazyCollection(LazyCollectionOption.EXTRA)
+    private Set<User> memberSet = new HashSet<>();
+
+    @OneToMany(targetEntity = Group.class, mappedBy = "course", cascade = CascadeType.REMOVE)
+    @LazyCollection(LazyCollectionOption.EXTRA)
+    private Set<Group> groupSet = new HashSet<>();
+
+}

+ 54 - 0
src/main/java/nju/seec/SEECdemo/data/entity/Group.java

@@ -0,0 +1,54 @@
+package nju.seec.SEECdemo.data.entity;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import nju.seec.SEECdemo.data.entity.assignment.CodeResult;
+import nju.seec.SEECdemo.data.user.User;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+
+import javax.persistence.*;
+import java.util.HashSet;
+import java.util.Set;
+
+@Data
+@EqualsAndHashCode(exclude = {"course", "memberSet", "resultSet"})
+@ToString(exclude = {"course", "memberSet", "resultSet"})
+@Entity
+@Table(name = "ENTITY_GROUP")
+public class Group {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Integer id;
+
+    @Basic
+    @Column(name = "name", nullable = false)
+    private String name;
+
+    @Basic
+    @Column(name = "namespace", nullable = false)
+    private Integer namespace;
+
+    @Basic
+    @Column(name = "fullPath", nullable = false)
+    private String fullPath;
+
+    @ManyToMany(targetEntity = User.class, cascade = CascadeType.DETACH)
+    @JoinTable(name = "LINK_USER_GROUP",
+            joinColumns = @JoinColumn(name = "groupId", referencedColumnName = "id"),
+            inverseJoinColumns = @JoinColumn(name = "user", referencedColumnName = "id")
+    )
+    @LazyCollection(LazyCollectionOption.EXTRA)
+    private Set<User> memberSet = new HashSet<>();
+
+    @ManyToOne(targetEntity = Course.class, fetch = FetchType.LAZY)
+    @JoinColumn(name = "course", referencedColumnName = "id")
+    private Course course;
+
+    @OneToMany(targetEntity = CodeResult.class, mappedBy = "group", cascade = CascadeType.REMOVE)
+    @LazyCollection(LazyCollectionOption.EXTRA)
+    private Set<CodeResult> resultSet = new HashSet<>();
+
+}

+ 4 - 0
src/main/java/nju/seec/SEECdemo/data/entity/Problem.java

@@ -0,0 +1,4 @@
+package nju.seec.SEECdemo.data.entity;
+
+public class Problem {
+}

+ 63 - 0
src/main/java/nju/seec/SEECdemo/data/entity/assignment/Code.java

@@ -0,0 +1,63 @@
+package nju.seec.SEECdemo.data.entity.assignment;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import nju.seec.SEECdemo.data.entity.Course;
+import nju.seec.SEECdemo.util.enums.AssignmentStatus;
+import nju.seec.SEECdemo.util.enums.AssignmentType;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+
+import javax.persistence.*;
+import java.util.HashSet;
+import java.util.Set;
+
+@Data
+@EqualsAndHashCode(exclude = {"course", "resultSet"})
+@ToString(exclude = {"course", "resultSet"})
+@Entity
+@Table(name = "ENTITY_CODE")
+public class Code {
+
+    @Id
+    @Column(name = "id")
+    private Integer id;
+
+    @Basic
+    @Column(name = "name", nullable = false)
+    private String name;
+
+    @Basic
+    @Column(name = "description", nullable = false)
+    private String description;
+
+    @Enumerated(value = EnumType.STRING)
+    @Column(name = "type", columnDefinition = "VARCHAR(255) NOT NULL")
+    private AssignmentType type = AssignmentType.CODE;
+
+    @Enumerated(value = EnumType.STRING)
+    @Column(name = "status", columnDefinition = "VARCHAR(255) NOT NULL DEFAULT 'PREPARING'")
+    private AssignmentStatus status = AssignmentStatus.PREPARING;
+
+    @Basic
+    @Column(name = "question", nullable = false)
+    private int questionId;
+
+    @Basic
+    @Column(name = "hash")
+    private String initHash;
+
+    @Basic
+    @Column(name = "uuid", nullable = false)
+    private String uuid;
+
+    @ManyToOne(targetEntity = Course.class, fetch = FetchType.LAZY)
+    @JoinColumn(name = "course", referencedColumnName = "id")
+    private Course course;
+
+    @OneToMany(targetEntity = CodeResult.class, mappedBy = "code", cascade = CascadeType.REMOVE)
+    @LazyCollection(LazyCollectionOption.EXTRA)
+    private Set<CodeResult> resultSet = new HashSet<>();
+
+}

+ 61 - 0
src/main/java/nju/seec/SEECdemo/data/entity/assignment/CodeResult.java

@@ -0,0 +1,61 @@
+package nju.seec.SEECdemo.data.entity.assignment;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import nju.seec.SEECdemo.data.convertor.ScoreDetailSetConverter;
+import nju.seec.SEECdemo.data.entity.Group;
+
+import javax.persistence.*;
+import java.util.HashSet;
+import java.util.Set;
+
+@Data
+@EqualsAndHashCode(exclude = {"code", "group"})
+@ToString(exclude = {"code", "group"})
+@Entity
+@Table(name = "ENTITY_CODE_RESULT")
+public class CodeResult {
+
+    @Id
+    @Column(name = "id")
+    private Integer id;
+
+    @ManyToOne(targetEntity = Code.class, fetch = FetchType.LAZY)
+    @JoinColumn(name = "code", referencedColumnName = "id")
+    private Code code;
+
+    @ManyToOne(targetEntity = Group.class, fetch = FetchType.LAZY)
+    @JoinColumn(name = "groupId", referencedColumnName = "id")
+    private Group group;
+
+    @Basic
+    @Column(name = "buildJob")
+    private String buildJob;
+
+    @Basic
+    @Column(name = "deployNS")
+    private String deployNamespace;
+
+    @Basic
+    @Column(name = "testJob")
+    private String testJob;
+
+    @Basic
+    @Column(name = "score", columnDefinition = "INT(11) NOT NULL DEFAULT 0")
+    private Integer score = 0;
+
+    @Basic
+    @Column(name = "details", columnDefinition = "JSON NOT NULL")
+    @Convert(converter = ScoreDetailSetConverter.class)
+    private Set<Score> detailSet = new HashSet<>();
+
+    @Data
+    public static class Score {
+        private Integer id;
+        private String name;
+        private Integer score;
+        private Set<Score> detail = new HashSet<>();
+    }
+
+}

+ 79 - 0
src/main/java/nju/seec/SEECdemo/data/user/User.java

@@ -0,0 +1,79 @@
+package nju.seec.SEECdemo.data.user;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import nju.seec.SEECdemo.data.entity.assignment.Code;
+import nju.seec.SEECdemo.data.entity.Course;
+import nju.seec.SEECdemo.data.entity.Group;
+import nju.seec.SEECdemo.util.enums.UserRole;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+import java.util.HashSet;
+import java.util.Set;
+
+@Data
+@EqualsAndHashCode(exclude = { "groupSet", "teaching", "courseSet"})
+@ToString(exclude = { "groupSet", "teaching", "courseSet"})
+@Entity
+@Table(name = "ENTITY_USER", indexes = {@Index(columnList = "username"), @Index(columnList = "email")})
+@EntityListeners(AuditingEntityListener.class)
+public class User {
+	@Id
+	@Column(name = "id")
+	private Integer id;
+
+	@Basic
+	@Column(name = "username", nullable = false)
+	private String username;
+
+	@Basic
+	@Column(name = "password", nullable = false)
+	private String password;
+
+	@Basic
+	@Column(name = "email", nullable = false)
+	private String email;
+
+	@Basic
+	@Column(name = "namespace", nullable = false)
+	private Integer namespace;
+
+	@Basic
+	@Column(name = "token", nullable = false)
+	private String token;
+
+	@Enumerated(EnumType.STRING)
+	@Column(name = "role", nullable = false)
+	private UserRole role;
+
+	@Basic
+	@Column(name = "created_at", nullable = false)
+	@CreatedDate
+	private LocalDateTime createdAt;
+
+    @OneToMany(targetEntity = Course.class, mappedBy = "creator", cascade = CascadeType.REMOVE)
+    @LazyCollection(LazyCollectionOption.EXTRA)
+    private Set<Course> teaching = new HashSet<>();
+
+	@ManyToMany(targetEntity = Course.class, cascade = CascadeType.DETACH)
+	@JoinTable(name = "LINK_USER_COURSE",
+			joinColumns = @JoinColumn(name = "user", referencedColumnName = "id"),
+			inverseJoinColumns = @JoinColumn(name = "course", referencedColumnName = "id")
+	)
+	@LazyCollection(LazyCollectionOption.EXTRA)
+	private Set<Course> courseSet = new HashSet<>();
+
+	@ManyToMany(targetEntity = Group.class, cascade = CascadeType.DETACH)
+	@JoinTable(name = "LINK_USER_GROUP",
+			joinColumns = @JoinColumn(name = "user", referencedColumnName = "id"),
+			inverseJoinColumns = @JoinColumn(name = "groupId", referencedColumnName = "id")
+	)
+	@LazyCollection(LazyCollectionOption.EXTRA)
+	private Set<Group> groupSet = new HashSet<>();
+}

+ 5 - 0
src/main/java/nju/seec/SEECdemo/logic/api/GitlabApi.java

@@ -1,5 +1,6 @@
 package nju.seec.SEECdemo.logic.api;
 
+import nju.seec.SEECdemo.logic.api.vo.GitlabGroup;
 import nju.seec.SEECdemo.logic.api.vo.GitlabUser;
 import org.gitlab4j.api.GitLabApiException;
 import org.gitlab4j.api.models.CommitAction;
@@ -11,6 +12,10 @@ import java.util.Set;
 public interface GitlabApi {
 	GitlabUser createUser(String username, String email, String password) throws GitLabApiException;
 
+	GitlabGroup createGroup(String groupName) throws GitLabApiException;
+
+	void addMemberToGroup(int groupId, int userId) throws GitLabApiException;
+
 	Integer createProject(String name) throws GitLabApiException;
 
 	Integer forkProject(int userId, int projectId, int namespaceId) throws GitLabApiException;

+ 4 - 0
src/main/java/nju/seec/SEECdemo/logic/api/JenkinsApi.java

@@ -15,4 +15,8 @@ public interface JenkinsApi {
 
 	// waitFinished: 是否等待直到构建完全完成
 	BuildDetails fetchBuildDetails(int id, int buildNumber, String target, boolean waitFinished) throws IOException;
+
+	void createBuildJob(String jobName) throws IOException;
+
+	void createTestJob(String jobName) throws IOException;
 }

+ 24 - 0
src/main/java/nju/seec/SEECdemo/logic/api/impl/GitlabApiImpl.java

@@ -1,6 +1,7 @@
 package nju.seec.SEECdemo.logic.api.impl;
 
 import nju.seec.SEECdemo.logic.api.GitlabApi;
+import nju.seec.SEECdemo.logic.api.vo.GitlabGroup;
 import nju.seec.SEECdemo.logic.api.vo.GitlabUser;
 import nju.seec.SEECdemo.util.ApplicationProperties;
 import nju.seec.SEECdemo.util.ApplicationProperties.Gitlab;
@@ -73,6 +74,29 @@ public class GitlabApiImpl implements GitlabApi {
 		return project != null ? project.getId() : null;
 	}
 
+	public synchronized GitlabGroup createGroup(String name) throws GitLabApiException {
+		Group group = server.getGroupApi().addGroup(name, name);
+		GitlabGroup gitlabGroup = new GitlabGroup();
+		gitlabGroup.setId(group.getId());
+		gitlabGroup.setNamespace(fetchGroupNamespace(group.getName()));
+		gitlabGroup.setFullPath(group.getFullPath());
+		return gitlabGroup;
+	}
+
+	private Integer fetchGroupNamespace(String groupName) throws GitLabApiException {
+		Namespace namespace;
+		namespace = server.getNamespaceApi().findNamespaces(groupName)
+				.stream()
+				.filter(ns -> "group".equals(ns.getKind()))
+				.reduce((ns1, ns2) -> ns1.getId() > ns2.getId() ? ns1 : ns2)
+				.orElse(null);
+		return namespace != null ? namespace.getId() : null;
+	}
+
+	public synchronized void addMemberToGroup(int groupId, int userId) throws GitLabApiException {
+	    Member member = server.getGroupApi().addMember(groupId, userId, AccessLevel.OWNER);
+    }
+
 	@Override
 	public synchronized Integer forkProject(int userId, int projectId, int namespaceId) throws GitLabApiException {
 		Project project;

+ 11 - 0
src/main/java/nju/seec/SEECdemo/logic/api/impl/JenkinsApiImpl.java

@@ -55,6 +55,17 @@ public class JenkinsApiImpl implements JenkinsApi {
 		server.createJob(String.valueOf(id), configTemplate);
 	}
 
+	@Override
+	public void createBuildJob(String jobName) throws IOException{
+	    // TODO 构建脚本
+		server.createJob(jobName, configTemplate);
+	}
+
+	@Override
+	public void createTestJob(String jobName) throws IOException{
+		server.createJob(jobName, configTemplate);
+	}
+
 	@Override
 	public QueueItem invokeBuild(int id, Map<String, String> paramMap) throws IOException {
 		QueueReference reference;

+ 10 - 0
src/main/java/nju/seec/SEECdemo/logic/api/vo/GitlabGroup.java

@@ -0,0 +1,10 @@
+package nju.seec.SEECdemo.logic.api.vo;
+
+import lombok.Data;
+
+@Data
+public class GitlabGroup {
+    private int id;
+    private Integer namespace;
+    private String fullPath;
+}

+ 0 - 7
src/main/java/nju/seec/SEECdemo/logic/service/AssignmentService.java

@@ -1,7 +0,0 @@
-package nju.seec.SEECdemo.logic.service;
-
-public interface AssignmentService {
-
-
-
-}

+ 10 - 0
src/main/java/nju/seec/SEECdemo/logic/service/CodeService.java

@@ -0,0 +1,10 @@
+package nju.seec.SEECdemo.logic.service;
+
+import nju.seec.SEECdemo.logic.vo.CodeWorkVO;
+import nju.seec.SEECdemo.web.dto.CodeWorkDTO;
+
+public interface CodeService {
+
+    CodeWorkVO createAssignment(int courseId, CodeWorkDTO codeWorkDTO);
+
+}

+ 9 - 0
src/main/java/nju/seec/SEECdemo/logic/service/UserService.java

@@ -0,0 +1,9 @@
+package nju.seec.SEECdemo.logic.service;
+
+import nju.seec.SEECdemo.logic.vo.UserVO;
+import nju.seec.SEECdemo.util.enums.UserRole;
+import org.hibernate.service.spi.ServiceException;
+
+public interface UserService {
+    public UserVO register(String username, String email, String password, UserRole role) throws ServiceException;
+}

+ 0 - 4
src/main/java/nju/seec/SEECdemo/logic/service/impl/AssignmentServiceImpl.java

@@ -1,4 +0,0 @@
-package nju.seec.SEECdemo.logic.service.impl;
-
-public class AssignmentServiceImpl {
-}

+ 66 - 0
src/main/java/nju/seec/SEECdemo/logic/service/impl/UserServiceImpl.java

@@ -0,0 +1,66 @@
+package nju.seec.SEECdemo.logic.service.impl;
+
+import nju.seec.SEECdemo.data.dao.UserDAO;
+import nju.seec.SEECdemo.data.user.User;
+import nju.seec.SEECdemo.logic.api.GitlabApi;
+import nju.seec.SEECdemo.logic.api.vo.GitlabUser;
+import nju.seec.SEECdemo.logic.service.UserService;
+import nju.seec.SEECdemo.logic.vo.UserVO;
+import nju.seec.SEECdemo.util.ToolKit;
+import nju.seec.SEECdemo.util.enums.UserRole;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.gitlab4j.api.GitLabApiException;
+import org.hibernate.service.spi.ServiceException;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+import org.springframework.ui.ModelMap;
+
+@Service
+public class UserServiceImpl implements UserService {
+	private static final Sort PAGE_SORT = Sort.by(Sort.Direction.ASC, "id");
+
+	private final UserDAO userDAO;
+	private final GitlabApi gitlabApi;
+
+	@Autowired
+	public UserServiceImpl(UserDAO userDAO , GitlabApi gitlabApi) {
+		this.userDAO = userDAO;
+		this.gitlabApi = gitlabApi;
+	}
+
+	public UserVO register(String username, String email, String password, UserRole role) throws ServiceException {
+		User user = userDAO.findByUsername(username);
+		if(user!=null){
+			System.out.println("用户名已被使用:"+username);
+			return null;
+		}
+		user = userDAO.findByEmail(email);
+		if(user!=null){
+			System.out.println("邮箱已被使用:"+email);
+			return null;
+		}
+		String realPassword = password == null ? ToolKit.randomPassword() : password;
+		GitlabUser gitlabUser;
+		try {
+			gitlabUser = gitlabApi.createUser(username, email, realPassword);
+		} catch (GitLabApiException e) {
+			throw new ServiceException("创建GitLab用户时发生异常[" + e.getMessage() + "]");
+		}
+		user = new User();
+		BeanUtils.copyProperties(gitlabUser, user);
+		user.setUsername(username);
+		user.setEmail(email);
+		user.setPassword(DigestUtils.sha256Hex(realPassword));
+		user.setRole(role);
+		ModelMap model = new ModelMap();
+		model.addAttribute("username", username);
+		if (password == null) {
+			model.addAttribute("password", realPassword);
+		}
+		model.addAttribute("role", role);
+		return new UserVO(userDAO.save(user));
+	}
+
+}

+ 100 - 0
src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/AssignmentPaperBuilder.java

@@ -0,0 +1,100 @@
+package nju.seec.SEECdemo.logic.service.impl.assignment;
+
+import nju.seec.SEECdemo.data.dao.assignment.CodeDAO;
+import nju.seec.SEECdemo.data.dao.assignment.CodeResultDAO;
+import nju.seec.SEECdemo.data.entity.assignment.Code;
+import nju.seec.SEECdemo.data.entity.assignment.CodeResult;
+import nju.seec.SEECdemo.data.entity.Group;
+import nju.seec.SEECdemo.logic.api.GitlabApi;
+import nju.seec.SEECdemo.logic.api.JenkinsApi;
+import nju.seec.SEECdemo.logic.storage.StorageProvider;
+import nju.seec.SEECdemo.util.enums.AssignmentStatus;
+import org.apache.commons.io.FileUtils;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.gitlab4j.api.GitLabApiException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.zeroturnaround.zip.ZipUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@Component
+public class AssignmentPaperBuilder {
+
+    private static final String TEMP_PREFIX = "MOOCODER-PAPER-";
+    private final GitlabApi gitlabApi;
+    private final GitHelper helper;
+    private final JenkinsApi jenkinsApi;
+    private final CodeDAO codeDAO;
+    private final CodeResultDAO codeResultDAO;
+    private final StorageProvider storageProvider;
+
+    private final QuestionServiceStub questionServiceStub;
+
+    @Autowired
+    public AssignmentPaperBuilder(GitlabApi gitlabApi, GitHelper helper, JenkinsApi jenkinsApi, CodeDAO codeDAO, CodeResultDAO codeResultDAO, StorageProvider storageProvider,
+                                  QuestionServiceStub questionServiceStub){
+        this.gitlabApi = gitlabApi;
+        this.helper = helper;
+        this.jenkinsApi = jenkinsApi;
+        this.codeDAO = codeDAO;
+        this.codeResultDAO = codeResultDAO;
+        this.storageProvider = storageProvider;
+        this.questionServiceStub = questionServiceStub;
+    }
+
+    @Transactional
+    public void buildCodePaper(int codeId) throws IOException, GitLabApiException, GitAPIException {
+        Code code = codeDAO.findById(codeId);
+        if (code.getStatus() != AssignmentStatus.PREPARING) return;
+        File tempDir = Files.createTempDirectory(TEMP_PREFIX).toFile();
+        try {
+            releaseQuestion(code.getQuestionId(), tempDir);
+            code.setInitHash(helper.push(tempDir, code.getUuid()));
+            // 为群组创建项目,并将组员加入到项目中
+            for(Group group:code.getCourse().getGroupSet()){
+                int projectId = gitlabApi.forkProject(group.getMemberSet().iterator().next().getId(), code.getId(), group.getNamespace());
+                CodeResult codeResult = new CodeResult();
+                codeResult.setId(projectId);
+                codeResult.setGroup(group);
+                codeResult.setCode(code);
+                // TODO
+                // 创建jenkins上的构建job
+                String buildJob = group.getName() + "-" + projectId + "-build";
+                jenkinsApi.createBuildJob(buildJob);
+                codeResult.setBuildJob(buildJob);
+                // 调用kube Api创建该项目的应用空间,并记录其id
+
+                // 创建jenkins上的测试job
+                String testJob = group.getName() + "-" + projectId + "-test";
+                jenkinsApi.createTestJob(testJob);
+                codeResult.setTestJob(testJob);
+                codeResultDAO.save(codeResult);
+            }
+            code.setStatus(AssignmentStatus.AVAILABLE);
+        } catch (Exception e) {
+            code.setStatus(AssignmentStatus.UNAVAILABLE);
+            throw e;
+        } finally {
+            codeDAO.save(code);
+            FileUtils.deleteDirectory(tempDir);
+        }
+    }
+
+    private void releaseQuestion(int questionId, File codeDir) throws IOException {
+        FileUtils.cleanDirectory(codeDir);
+        String problemName = questionServiceStub.getQuestionUniqueName(questionId);
+        byte[] content = storageProvider.fetch(problemName + ".zip");
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
+        ZipUtil.unpack(inputStream, codeDir);
+    }
+
+
+}

+ 53 - 0
src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/CodeServiceImpl.java

@@ -0,0 +1,53 @@
+package nju.seec.SEECdemo.logic.service.impl.assignment;
+
+import nju.seec.SEECdemo.data.dao.assignment.CodeDAO;
+import nju.seec.SEECdemo.data.entity.assignment.Code;
+import nju.seec.SEECdemo.logic.api.GitlabApi;
+import nju.seec.SEECdemo.logic.service.CodeService;
+import nju.seec.SEECdemo.logic.vo.CodeWorkVO;
+import nju.seec.SEECdemo.util.AsyncWrapper;
+import nju.seec.SEECdemo.util.ToolKit;
+import nju.seec.SEECdemo.web.dto.CodeWorkDTO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CodeServiceImpl implements CodeService {
+
+    private final CodeDAO assignmentDAO;
+    private final GitlabApi gitlabApi;
+    private final AssignmentPaperBuilder assignmentPaperBuilder;
+    private final AsyncWrapper asyncWrapper;
+    private final CourseServiceStub courseServiceStub;
+
+    @Autowired
+    public CodeServiceImpl(CodeDAO assignmentDAO, GitlabApi gitlabApi, AssignmentPaperBuilder assignmentPaperBuilder,
+                           AsyncWrapper asyncWrapper, CourseServiceStub courseServiceStub){
+        this.assignmentDAO = assignmentDAO;
+        this.gitlabApi = gitlabApi;
+        this.assignmentPaperBuilder = assignmentPaperBuilder;
+        this.asyncWrapper = asyncWrapper;
+        this.courseServiceStub = courseServiceStub;
+    }
+
+
+    @Override
+    public CodeWorkVO createAssignment(int courseId, CodeWorkDTO codeWorkDTO) {
+        Code assignment = new Code();
+        assignment.setName(codeWorkDTO.getName());
+        assignment.setDescription(codeWorkDTO.getDescription());
+        assignment.setQuestionId(codeWorkDTO.getQuestion());
+        assignment.setUuid(ToolKit.randomUUID());
+        assignment.setCourse(courseServiceStub.getCourse(courseId));
+        try {
+            assignment.setId(gitlabApi.createProject(assignment.getUuid()));
+        } catch (Exception e) {
+            System.out.println("创建作业时发生异常" + ToolKit.format(e));
+        }
+        asyncWrapper.asyncInvoke(() -> assignmentPaperBuilder.buildCodePaper(assignment.getId()), 2000);
+        return new CodeWorkVO(assignmentDAO.save(assignment));
+    }
+
+
+
+}

+ 22 - 0
src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/CourseServiceStub.java

@@ -0,0 +1,22 @@
+package nju.seec.SEECdemo.logic.service.impl.assignment;
+
+import nju.seec.SEECdemo.data.dao.CourseDAO;
+import nju.seec.SEECdemo.data.entity.Course;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CourseServiceStub {
+
+    @Autowired
+    private final CourseDAO courseDAO;
+
+    public CourseServiceStub(CourseDAO courseDAO){
+        this.courseDAO = courseDAO;
+    }
+
+    public Course getCourse(int courseId){
+        return courseDAO.findById(courseId);
+    }
+
+}

+ 50 - 0
src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/GitHelper.java

@@ -0,0 +1,50 @@
+package nju.seec.SEECdemo.logic.service.impl.assignment;
+
+import nju.seec.SEECdemo.util.ApplicationProperties;
+import nju.seec.SEECdemo.util.ApplicationProperties.Gitlab;
+import org.apache.commons.io.FileUtils;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+@Component
+public class GitHelper {
+	private static final String TEMP_PREFIX = "MOOCODER-REPOSITORY-";
+	private static final String COMMIT_MESSAGE = "初始化";
+
+	private final Gitlab gitlab;
+	private final CredentialsProvider provider;
+
+	@Autowired
+	public GitHelper(ApplicationProperties properties) {
+		this.gitlab = properties.getGitlab();
+		this.provider = new UsernamePasswordCredentialsProvider(gitlab.getUsername(), gitlab.getToken());
+	}
+
+	public String push(File sourceDir, String project) throws GitAPIException, IOException {
+		String uri = gitlab.getHost() + "/" + gitlab.getUsername() + "/" + project + ".git";
+		File tempDir = Files.createTempDirectory(TEMP_PREFIX).toFile();
+		try {
+			return push(sourceDir, tempDir, uri);
+		} finally {
+			FileUtils.deleteDirectory(tempDir);
+		}
+	}
+
+	private String push(File sourceDir, File tempDir, String uri) throws GitAPIException, IOException {
+		Git targetRepo = Git.cloneRepository().setURI(uri).setDirectory(tempDir).setCredentialsProvider(provider).call();
+		FileUtils.copyDirectory(sourceDir, tempDir);
+		targetRepo.add().addFilepattern(".").call();
+		String hash = targetRepo.commit().setMessage(COMMIT_MESSAGE).call().getName();
+		targetRepo.push().setRemote("origin").setForce(true).setPushAll().setCredentialsProvider(provider).call();
+		targetRepo.close();
+		return hash;
+	}
+}

+ 12 - 0
src/main/java/nju/seec/SEECdemo/logic/service/impl/assignment/QuestionServiceStub.java

@@ -0,0 +1,12 @@
+package nju.seec.SEECdemo.logic.service.impl.assignment;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class QuestionServiceStub {
+
+    public String getQuestionUniqueName(long questionId){
+        return "64-three-sum";
+    }
+
+}

+ 12 - 0
src/main/java/nju/seec/SEECdemo/logic/storage/StorageProvider.java

@@ -0,0 +1,12 @@
+package nju.seec.SEECdemo.logic.storage;
+
+import java.io.IOException;
+import java.util.Map;
+
+public interface StorageProvider {
+	void initialize(Map<String, String> properties) throws IOException;
+
+	void save(byte[] content, String name) throws IOException;
+
+	byte[] fetch(String name) throws IOException;
+}

+ 32 - 0
src/main/java/nju/seec/SEECdemo/logic/storage/StorageProviderConfiguration.java

@@ -0,0 +1,32 @@
+package nju.seec.SEECdemo.logic.storage;
+
+import nju.seec.SEECdemo.logic.storage.impl.LocalStorageProvider;
+import nju.seec.SEECdemo.util.ApplicationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.IOException;
+
+@Configuration
+public class StorageProviderConfiguration {
+	private final ApplicationProperties properties;
+
+	public StorageProviderConfiguration(ApplicationProperties properties) {
+		this.properties = properties;
+	}
+
+	@Bean
+	public StorageProvider storageProvider() throws IOException {
+		ApplicationProperties.Storage storage = properties.getStorage();
+		StorageProvider storageProvider;
+		switch (storage.getType()) {
+			case "local":
+				storageProvider = new LocalStorageProvider();
+				break;
+			default:
+				throw new IllegalArgumentException("没有类型为[" + storage.getType() + "]的文件存储实现!");
+		}
+		storageProvider.initialize(storage.getProperties());
+		return storageProvider;
+	}
+}

+ 43 - 0
src/main/java/nju/seec/SEECdemo/logic/storage/impl/LocalStorageProvider.java

@@ -0,0 +1,43 @@
+package nju.seec.SEECdemo.logic.storage.impl;
+
+import nju.seec.SEECdemo.logic.storage.StorageProvider;
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+public class LocalStorageProvider implements StorageProvider {
+	private String location;
+
+	@Override
+	public void initialize(Map<String, String> properties) throws IOException {
+		location = properties.get("location");
+		if (location == null) {
+			throw new IllegalArgumentException("未设定文件存储位置!");
+		}
+		while (location.endsWith(File.separator)) {
+			location = location.substring(0, location.length() - 1);
+		}
+		File file = new File(location);
+		if (file.exists()) {
+			if (!file.isDirectory()) {
+				throw new IOException("存储目标不能为非文件夹!");
+			}
+		} else if (!file.mkdirs()) {
+			throw new IOException("创建文件夹失败!");
+		}
+	}
+
+	@Override
+	public void save(byte[] content, String name) throws IOException {
+		File file = new File(location + File.separator + name);
+		FileUtils.writeByteArrayToFile(file, content);
+	}
+
+	@Override
+	public byte[] fetch(String name) throws IOException {
+		File file = new File(location + File.separator + name);
+		return FileUtils.readFileToByteArray(file);
+	}
+}

+ 0 - 7
src/main/java/nju/seec/SEECdemo/logic/vo/AssignmentVO.java

@@ -1,7 +0,0 @@
-package nju.seec.SEECdemo.logic.vo;
-
-import lombok.Data;
-
-@Data
-public class AssignmentVO {
-}

+ 48 - 0
src/main/java/nju/seec/SEECdemo/logic/vo/CodeWorkVO.java

@@ -0,0 +1,48 @@
+package nju.seec.SEECdemo.logic.vo;
+
+import lombok.Data;
+import nju.seec.SEECdemo.data.entity.assignment.Code;
+import nju.seec.SEECdemo.data.entity.assignment.CodeResult;
+import nju.seec.SEECdemo.data.entity.Group;
+import nju.seec.SEECdemo.util.enums.AssignmentStatus;
+import org.springframework.beans.BeanUtils;
+
+import java.util.Set;
+
+@Data
+public class CodeWorkVO {
+    private Integer id;
+    private String name;
+    private String description;
+    private AssignmentStatus status;
+    private String courseName;
+    private Set<NestedRecord> records;
+
+    public CodeWorkVO(Code assignment){
+        BeanUtils.copyProperties(assignment, this);
+        this.courseName = assignment.getCourse().getName();
+    }
+
+    @Data
+    public static class NestedRecord {
+        private Integer id;
+        private NestedGroup group;
+
+        public NestedRecord(CodeResult assignmentResult){
+            this.id = assignmentResult.getId();
+            this.group = new NestedGroup(assignmentResult.getGroup());
+        }
+    }
+
+    @Data
+    public static class NestedGroup {
+        private Integer id;
+        private String name;
+
+        public NestedGroup(Group group){
+            this.id = group.getId();
+            this.name = group.getName();
+        }
+    }
+
+}

+ 27 - 0
src/main/java/nju/seec/SEECdemo/logic/vo/UserVO.java

@@ -0,0 +1,27 @@
+package nju.seec.SEECdemo.logic.vo;
+
+import lombok.Data;
+import nju.seec.SEECdemo.data.user.User;
+import nju.seec.SEECdemo.util.enums.UserRole;
+import org.springframework.beans.BeanUtils;
+
+import java.time.LocalDateTime;
+
+@Data
+public class UserVO {
+	private Integer id;
+	private String username;
+	private String email;
+	private UserRole role;
+
+	private UserVO() {
+		this.id = 0;
+		this.username = "管理员";
+	}
+
+	public UserVO(User user) {
+		BeanUtils.copyProperties(user, this);
+	}
+
+	public static final UserVO ADMIN = new UserVO();
+}

+ 29 - 0
src/main/java/nju/seec/SEECdemo/util/AsyncWrapper.java

@@ -0,0 +1,29 @@
+package nju.seec.SEECdemo.util;
+
+import lombok.extern.apachecommons.CommonsLog;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+@Component
+@CommonsLog
+public class AsyncWrapper {
+
+	@Async
+	public void asyncInvoke(Method method) {
+		try {
+			method.invoke();
+		} catch (Exception e) {
+			log.error("进行异步调用时发生异常[" + e.getClass() + "]:" + e.getMessage());
+		}
+	}
+
+	@Async
+	public void asyncInvoke(Method method, long delay) {
+		try {
+			Thread.sleep(delay);
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+		}
+		asyncInvoke(method);
+	}
+}

+ 6 - 0
src/main/java/nju/seec/SEECdemo/util/Method.java

@@ -0,0 +1,6 @@
+package nju.seec.SEECdemo.util;
+
+@FunctionalInterface
+public interface Method {
+	void invoke() throws Exception;
+}

+ 30 - 0
src/main/java/nju/seec/SEECdemo/util/enums/AssignmentStatus.java

@@ -0,0 +1,30 @@
+package nju.seec.SEECdemo.util.enums;
+
+/**
+ * 考试状态
+ * 考试生命周期中对外表现的状态的顺序为PREPARING->READY->AVAILABLE->FINISHED->CLOSED
+ * 考试生命周期中持久化的状态的顺序为PREPARING->AVAILABLE->CLOSED
+ * UNAVAILABLE表示考试初始化失败
+ */
+public enum AssignmentStatus {
+	/**
+	 * 正在准备试题,通常是正在PUSH试题
+	 */
+	PREPARING,
+	/**
+	 * 考试不可用,通常是PUSH试题失败
+	 */
+	UNAVAILABLE,
+	/**
+	 * 考试已准备好,在考试尚未开始时使用,不持久化到数据库中
+	 */
+	READY,
+	/**
+	 * 考试可用,表示考试正在进行,持久化时考试从准备好到考试被关闭均为此状态
+	 */
+	AVAILABLE,
+	/**
+	 * 考试已完成,在考试结束后使用,不持久化到数据库中
+	 */
+	FINISHED
+}

+ 12 - 1
src/main/java/nju/seec/SEECdemo/util/enums/AssignmentType.java

@@ -1,5 +1,16 @@
 package nju.seec.SEECdemo.util.enums;
 
+import lombok.Getter;
+
+@Getter
 public enum AssignmentType {
-    CODE
+    CODE("CODE", "团队代码作业");
+
+    AssignmentType(String name, String value){
+        this.name = name;
+        this.value = value;
+    }
+
+    private String name;
+    private String value;
 }

+ 23 - 0
src/main/java/nju/seec/SEECdemo/util/enums/UserRole.java

@@ -0,0 +1,23 @@
+package nju.seec.SEECdemo.util.enums;
+
+public enum UserRole {
+	/**
+	 * 学生角色
+	 */
+	STUDENT("学生"),
+	/**
+	 * 教师角色
+	 */
+	TEACHER("教师");
+
+	private final String name;
+
+	UserRole(String name) {
+		this.name = name;
+	}
+
+	@Override
+	public String toString() {
+		return name;
+	}
+}

+ 0 - 9
src/main/java/nju/seec/SEECdemo/web/controller/AssignmentController.java

@@ -1,9 +0,0 @@
-package nju.seec.SEECdemo.web.controller;
-
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/api")
-public class AssignmentController {
-}

+ 27 - 0
src/main/java/nju/seec/SEECdemo/web/controller/CodeController.java

@@ -0,0 +1,27 @@
+package nju.seec.SEECdemo.web.controller;
+
+import nju.seec.SEECdemo.logic.service.CodeService;
+import nju.seec.SEECdemo.web.dto.CodeWorkDTO;
+import nju.seec.SEECdemo.web.response.ResourceResponse;
+import nju.seec.SEECdemo.web.response.Response;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api")
+public class CodeController {
+
+    @Autowired
+    private final CodeService codeService;
+
+    public CodeController(CodeService codeService){
+        this.codeService = codeService;
+    }
+
+    @PostMapping("/teacher/course/{courseId:\\d+}/assignment/code")
+    public Response create(@PathVariable int courseId, @RequestBody @Validated(PostMapping.class) CodeWorkDTO codeWorkDTO){
+        return new ResourceResponse(codeService.createAssignment(courseId, codeWorkDTO));
+    }
+
+}

+ 0 - 7
src/main/java/nju/seec/SEECdemo/web/dto/AssignmentDTO.java

@@ -1,7 +0,0 @@
-package nju.seec.SEECdemo.web.dto;
-
-import lombok.Data;
-
-@Data
-public class AssignmentDTO {
-}

+ 10 - 0
src/main/java/nju/seec/SEECdemo/web/dto/CodeWorkDTO.java

@@ -0,0 +1,10 @@
+package nju.seec.SEECdemo.web.dto;
+
+import lombok.Data;
+
+@Data
+public class CodeWorkDTO {
+    private String name;
+    private String description;
+    private int question;
+}

+ 18 - 0
src/main/java/nju/seec/SEECdemo/web/response/EmptyResponse.java

@@ -0,0 +1,18 @@
+package nju.seec.SEECdemo.web.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class EmptyResponse implements Response {
+	@JsonProperty("err")
+	private int error;
+
+	public EmptyResponse() {
+		this.error = 0;
+	}
+
+	protected EmptyResponse(int error) {
+		this.error = error;
+	}
+}

+ 17 - 0
src/main/java/nju/seec/SEECdemo/web/response/ErrorResponse.java

@@ -0,0 +1,17 @@
+package nju.seec.SEECdemo.web.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ErrorResponse extends EmptyResponse {
+	@JsonProperty("msg")
+	private String message;
+
+	public ErrorResponse(int err, String message) {
+		super(err);
+		this.message = message;
+	}
+}

+ 33 - 0
src/main/java/nju/seec/SEECdemo/web/response/PageResourceResponse.java

@@ -0,0 +1,33 @@
+package nju.seec.SEECdemo.web.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.data.domain.Page;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PageResourceResponse extends ResourceResponse {
+	@JsonProperty("page")
+	private PageInfo pageInfo;
+
+	public PageResourceResponse(Page page) {
+		super(page.getContent());
+		this.pageInfo = new PageInfo(page);
+	}
+
+	@Data
+	private static class PageInfo {
+		private int page;
+		private int limit;
+		private int size;
+		private long total;
+
+		PageInfo(Page page) {
+			this.page = page.getNumber() + 1;
+			this.limit = page.getSize();
+			this.size = page.getContent().size();
+			this.total = page.getTotalElements();
+		}
+	}
+}

+ 16 - 0
src/main/java/nju/seec/SEECdemo/web/response/ResourceResponse.java

@@ -0,0 +1,16 @@
+package nju.seec.SEECdemo.web.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ResourceResponse extends EmptyResponse {
+	@JsonProperty("res")
+	private Object resource;
+
+	public ResourceResponse(Object resource) {
+		this.resource = resource;
+	}
+}

+ 6 - 0
src/main/java/nju/seec/SEECdemo/web/response/Response.java

@@ -0,0 +1,6 @@
+package nju.seec.SEECdemo.web.response;
+
+@FunctionalInterface
+public interface Response {
+	int getError();
+}