Ver Fonte

merge dev

Chen YZ há 5 anos atrás
pai
commit
eff58b7850
97 ficheiros alterados com 2708 adições e 803 exclusões
  1. 2 1
      api/pom.xml
  2. 18 47
      api/src/main/java/seecoder/devcloud/api/ApplicationProperties.java
  3. 126 0
      api/src/main/java/seecoder/devcloud/api/DevcloudApiAutoConfiguration.java
  4. 1 1
      api/src/main/java/seecoder/devcloud/api/docker/impl/DockerApiImpl.java
  5. 3 0
      api/src/main/java/seecoder/devcloud/api/gitlab/GitlabApi.java
  6. 8 1
      api/src/main/java/seecoder/devcloud/api/gitlab/impl/GitlabApiImpl.java
  7. 2 0
      api/src/main/java/seecoder/devcloud/api/k8s/K8sApiConfiguration.java
  8. 12 1
      api/src/main/java/seecoder/devcloud/api/k8s/K8sConstants.java
  9. 6 1
      api/src/main/java/seecoder/devcloud/api/k8s/impl/SecretApiImpl.java
  10. 6 1
      api/src/main/java/seecoder/devcloud/api/k8s/impl/ServiceApiImpl.java
  11. 3 6
      api/src/main/java/seecoder/devcloud/api/k8s/model/ConfigMap.java
  12. 4 0
      api/src/main/java/seecoder/devcloud/api/k8s/model/Container.java
  13. 8 4
      api/src/main/java/seecoder/devcloud/api/k8s/model/Deployment.java
  14. 3 0
      api/src/main/java/seecoder/devcloud/api/k8s/model/Ingress.java
  15. 2 0
      api/src/main/java/seecoder/devcloud/api/k8s/model/K8sObjectRequest.java
  16. 3 4
      api/src/main/java/seecoder/devcloud/api/k8s/model/PersistentVolumeClaim.java
  17. 6 7
      api/src/main/java/seecoder/devcloud/api/k8s/model/Service.java
  18. 2 0
      api/src/main/resources/META-INF/spring.factories
  19. 5 0
      common/pom.xml
  20. 20 0
      common/src/main/java/seecoder/devcloud/common/CommonAutoConfiguration.java
  21. 1 1
      common/src/main/java/seecoder/devcloud/common/exceptions/ServiceException.java
  22. 13 0
      common/src/main/java/seecoder/devcloud/common/serializer/CustomLocalDateTimeDeserializer.java
  23. 13 0
      common/src/main/java/seecoder/devcloud/common/serializer/CustomLocalDateTimeSerializer.java
  24. 32 0
      common/src/main/java/seecoder/devcloud/common/serializer/TimestampLocalDateDeserializer.java
  25. 29 0
      common/src/main/java/seecoder/devcloud/common/serializer/TimestampLocalDateTimeSerializer.java
  26. 0 9
      common/src/main/java/seecoder/devcloud/common/util/SpringUtil.java
  27. 41 0
      common/src/main/java/seecoder/devcloud/common/validate/FieldCompare.java
  28. 39 0
      common/src/main/java/seecoder/devcloud/common/validate/FieldEquals.java
  29. 38 0
      common/src/main/java/seecoder/devcloud/common/validate/validator/FieldCompareValidator.java
  30. 30 0
      common/src/main/java/seecoder/devcloud/common/validate/validator/FieldEqualsValidator.java
  31. 2 0
      common/src/main/resources/META-INF/spring.factories
  32. 1 4
      core/src/main/java/seecoder/devcloud/core/CoreApplication.java
  33. 41 0
      core/src/main/java/seecoder/devcloud/core/pipeline/Context.java
  34. 8 2
      core/src/main/java/seecoder/devcloud/core/pipeline/Handler.java
  35. 5 15
      core/src/main/java/seecoder/devcloud/core/pipeline/Pipeline.java
  36. 15 0
      core/src/main/java/seecoder/devcloud/core/pipeline/PipelineConfig.java
  37. 0 15
      core/src/main/java/seecoder/devcloud/core/pipeline/TestHandler.java
  38. 39 0
      core/src/main/java/seecoder/devcloud/core/pipeline/handler/AbstractHandler.java
  39. 48 0
      core/src/main/java/seecoder/devcloud/core/pipeline/handler/DockerImageBuildHandler.java
  40. 120 0
      core/src/main/java/seecoder/devcloud/core/pipeline/handler/K8sDeploymentHandler.java
  41. 11 9
      core/src/main/java/seecoder/devcloud/core/pipeline/handler/K8sIngressHandler.java
  42. 55 0
      core/src/main/java/seecoder/devcloud/core/pipeline/handler/K8sNamespaceHandler.java
  43. 67 0
      core/src/main/java/seecoder/devcloud/core/pipeline/handler/K8sServiceHandler.java
  44. 1 1
      core/src/main/java/seecoder/devcloud/core/pipeline/handler/MavenPackageHandler.java
  45. 1 1
      core/src/main/java/seecoder/devcloud/core/pipeline/handler/MavenTestHandler.java
  46. 0 15
      core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/CommonServiceHandler.java
  47. 0 16
      core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/DockerImageBuildHandler.java
  48. 0 41
      core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/JavaAppDeploymentHandler.java
  49. 0 10
      core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/MysqlDeploymentHandler.java
  50. 55 0
      core/src/main/resources/application-local.yml
  51. 4 44
      core/src/main/resources/application.yml
  52. 35 14
      core/src/test/java/seecoder/devcloud/core/CoreApplicationTests.java
  53. 20 23
      web/pom.xml
  54. 6 2
      web/src/main/java/seecoder/devcloud/web/DevcloudWebApplication.java
  55. 57 0
      web/src/main/java/seecoder/devcloud/web/controller/user/UserController.java
  56. 156 0
      web/src/main/java/seecoder/devcloud/web/dao/GeneralInsertUpdateSqlProvider.java
  57. 1 1
      web/src/main/java/seecoder/devcloud/web/dao/provider/TreeNodeProvider.java
  58. 2 3
      web/src/main/java/seecoder/devcloud/web/dao/tree/TreeNodeMapper.java
  59. 45 0
      web/src/main/java/seecoder/devcloud/web/dao/user/GroupMapper.java
  60. 35 0
      web/src/main/java/seecoder/devcloud/web/dao/user/GroupMemberMapper.java
  61. 35 0
      web/src/main/java/seecoder/devcloud/web/dao/user/UserMapper.java
  62. 27 0
      web/src/main/java/seecoder/devcloud/web/infrastructure/GlobalExceptionHandler.java
  63. 1 7
      web/src/main/java/seecoder/devcloud/web/infrastructure/config/SwaggerConfig.java
  64. 81 0
      web/src/main/java/seecoder/devcloud/web/infrastructure/security/WebSecurityConfiguration.java
  65. 16 0
      web/src/main/java/seecoder/devcloud/web/infrastructure/security/WebSecurityConstants.java
  66. 7 1
      web/src/main/java/seecoder/devcloud/web/po/user/Group.java
  67. 20 20
      web/src/main/java/seecoder/devcloud/web/po/user/User.java
  68. 1 1
      web/src/main/java/seecoder/devcloud/web/service/impl/tree/TreeNodeServiceImpl.java
  69. 110 0
      web/src/main/java/seecoder/devcloud/web/service/impl/user/GroupServiceImpl.java
  70. 86 84
      web/src/main/java/seecoder/devcloud/web/service/impl/user/MailServiceImpl.java
  71. 155 232
      web/src/main/java/seecoder/devcloud/web/service/impl/user/UserServiceImpl.java
  72. 30 0
      web/src/main/java/seecoder/devcloud/web/service/user/GroupService.java
  73. 11 1
      web/src/main/java/seecoder/devcloud/web/service/user/MailService.java
  74. 28 37
      web/src/main/java/seecoder/devcloud/web/service/user/UserService.java
  75. 10 10
      web/src/main/java/seecoder/devcloud/web/vo/Response.java
  76. 25 0
      web/src/main/java/seecoder/devcloud/web/vo/user/ChangePasswordVO.java
  77. 85 0
      web/src/main/java/seecoder/devcloud/web/vo/user/CustomUserDetails.java
  78. 24 0
      web/src/main/java/seecoder/devcloud/web/vo/user/GroupDTO.java
  79. 37 0
      web/src/main/java/seecoder/devcloud/web/vo/user/GroupVO.java
  80. 31 0
      web/src/main/java/seecoder/devcloud/web/vo/user/InnerGroupVO.java
  81. 49 0
      web/src/main/java/seecoder/devcloud/web/vo/user/RegisterVO.java
  82. 24 0
      web/src/main/java/seecoder/devcloud/web/vo/user/UserDTO.java
  83. 32 0
      web/src/main/java/seecoder/devcloud/web/vo/user/UserVO.java
  84. 10 0
      web/src/main/resources/SQLscripts/group.sql
  85. 11 0
      web/src/main/resources/SQLscripts/group_member.sql
  86. 14 0
      web/src/main/resources/SQLscripts/user.sql
  87. 55 0
      web/src/main/resources/application-cyz.yml
  88. 6 6
      web/src/main/resources/application-dev.yml
  89. 28 100
      web/src/main/resources/application-wph.yml
  90. 1 1
      web/src/main/resources/application.yml
  91. 1 1
      web/src/test/java/seecoder/devcloud/web/DevcloudWebApplicationTests.java
  92. 2 2
      web/src/test/java/seecoder/devcloud/web/dao/tree/TreeNodeMapperTest.java
  93. 31 0
      web/src/test/java/seecoder/devcloud/web/dao/user/GroupMapperTest.java
  94. 45 0
      web/src/test/java/seecoder/devcloud/web/dao/user/GroupMemberMapperTest.java
  95. 111 0
      web/src/test/java/seecoder/devcloud/web/service/user/GroupServiceTest.java
  96. 49 0
      web/src/test/java/seecoder/devcloud/web/service/user/MailServiceTest.java
  97. 113 0
      web/src/test/java/seecoder/devcloud/web/service/user/UserServiceTest.java

+ 2 - 1
api/pom.xml

@@ -51,7 +51,8 @@
         <dependency>
             <groupId>org.gitlab4j</groupId>
             <artifactId>gitlab4j-api</artifactId>
-            <version>4.8.7</version>
+            <version>4.15.7</version>
+<!--            <version>4.8.7</version>-->
         </dependency>
         <dependency>
             <groupId>com.offbytwo.jenkins</groupId>

+ 18 - 47
api/src/main/java/seecoder/devcloud/api/ApplicationProperties.java

@@ -20,9 +20,7 @@ import java.util.Map;
 @ConfigurationProperties("seecoder")
 @Validated
 public class ApplicationProperties {
-	@Pattern(regexp = "^[a-zA-Z0-9]+$")
-	@NotEmpty
-	private String secret;
+
 	@Valid
 	private Gitlab gitlab = new Gitlab();
 	@Valid
@@ -32,33 +30,39 @@ public class ApplicationProperties {
 	@Valid
 	private Mail mail = new Mail();
 	@Valid
-	private Storage storage = new Storage();
-	@Valid
 	private K8s k8s = new K8s();
 	@Valid
 	private Registry registry = new Registry();
-	@Valid
-	private Auth auth = new Auth();
+
 
 	@Data
 	public static class Gitlab {
-		@URL
+
+		//@URL todo 测试环境没有真实host,先注释,生产环境需要还原
 		@NotEmpty
 		private String host;
-		private String gitProxy;
+
+		//private String gitProxy;
+		/**
+		 * 回调地址
+		 */
 		@URL
 		@NotEmpty
 		private String webHookProxy;
-		@NotEmpty
-		private String username = "root";
+
+
         @NotNull
         private Integer userId = 1;
+
+		/**
+		 * 采用的Personal/project access tokens的认证方式
+		 * 这里使用管理员的token
+		 * https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
+		 */
 		@NotEmpty
 		private String token;
 
-		public String getGitProxy() {
-			return StringUtils.defaultString(gitProxy, host);
-		}
+
 	}
 
 	@Data
@@ -70,8 +74,6 @@ public class ApplicationProperties {
 		private String username = "root";
 		@NotEmpty
 		private String token;
-		@NotEmpty
-		private String baseImage = "172.19.51.9:18082/build-base";
 	}
 
 	@Data
@@ -91,11 +93,6 @@ public class ApplicationProperties {
 		private String personal = "MOOCODER";
 	}
 
-	@Data
-	public static class Storage {
-		private String type = "local";
-		private Map<String, String> properties = new HashMap<>();
-	}
 
 	@Data
 	public static class K8s{
@@ -104,18 +101,6 @@ public class ApplicationProperties {
 		private String apiServer;
 		@NotEmpty
 		private String token;
-		@NotEmpty
-		private String ingressHost;
-		@NotEmpty
-		private String publicNamespace;
-		@NotEmpty
-		private String publicMysqlUrl;
-		@NotEmpty
-		private String publicMysqlPod;
-		@NotEmpty
-		private String publicMysqlUsername;
-
-		private String publicMysqlPassword;
 
 		private Boolean validateSSL = false;
 
@@ -143,18 +128,4 @@ public class ApplicationProperties {
 		private String cleanUpTask = "4da7e1e1-2b28-46b5-a080-9d834cc96019";
 	}
 
-	@Data
-	public static class Auth {
-		@NotEmpty
-		private String signingKey;
-		@NotEmpty
-		private String iss = "p.seecoder.cn";
-		@NotEmpty
-		private String aud = "seec-seec";
-		@NotEmpty
-		private String portal = "http://p.seecoder.cn";
-		@NotEmpty
-		private String redirectUri;
-
-	}
 }

+ 126 - 0
api/src/main/java/seecoder/devcloud/api/DevcloudApiAutoConfiguration.java

@@ -0,0 +1,126 @@
+package seecoder.devcloud.api;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import seecoder.devcloud.api.docker.DockerApi;
+import seecoder.devcloud.api.docker.impl.DockerApiImpl;
+import seecoder.devcloud.api.gitlab.GitlabApi;
+import seecoder.devcloud.api.gitlab.impl.GitlabApiImpl;
+import seecoder.devcloud.api.jenkins.JenkinsApi;
+import seecoder.devcloud.api.jenkins.impl.JenkinsApiImpl;
+import seecoder.devcloud.api.k8s.*;
+import seecoder.devcloud.api.k8s.impl.*;
+import seecoder.devcloud.api.nexus.NexusApi;
+import seecoder.devcloud.api.nexus.model.ApiVersion;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/5
+ * @description:
+ */
+@Configuration
+public class DevcloudApiAutoConfiguration {
+
+    @Bean
+    public ApplicationProperties applicationProperties(){
+        return new ApplicationProperties();
+    }
+
+    @Bean
+    @ConditionalOnBean(ApplicationProperties.class)
+    public GitlabApi gitlabApi(){
+        return new GitlabApiImpl(applicationProperties());
+    }
+
+    @Bean
+    @ConditionalOnBean(ApplicationProperties.class)
+    public K8sApiConfiguration k8sApiConfiguration(){
+        return new K8sApiConfiguration(applicationProperties());
+    }
+
+    @Bean
+    @ConditionalOnBean(ApplicationProperties.class)
+    public JenkinsApi jenkinsApi(){
+        return new JenkinsApiImpl(applicationProperties());
+    }
+
+    @Bean
+    @ConditionalOnBean(ApplicationProperties.class)
+    public NexusApi nexusApi(){
+        ApplicationProperties properties = applicationProperties();
+        return new NexusApi(properties.getRegistry().getNexusServer(), ApiVersion.V1, properties.getRegistry().getUsername(), properties.getRegistry().getPassword());
+    }
+
+    @Bean
+    @ConditionalOnBean(ApplicationProperties.class)
+    public DockerApi dockerApi(){
+        return new DockerApiImpl(applicationProperties());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public ConfigMapApi configMapApi(){
+        return new ConfigMapApiImpl(k8sApiConfiguration().coreV1Api());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public DeploymentApi deploymentApi(){
+        return new DeploymentApiImpl(k8sApiConfiguration().appsV1Api());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public ExecApi execApi(){
+        return new ExecApiImpl(k8sApiConfiguration().exec());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public NamespaceApi namespaceApi(){
+        return new NamespaceApiImpl(k8sApiConfiguration().coreV1Api());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public IngressApi ingressApi(){
+        return new IngressApiImpl(k8sApiConfiguration().extensionsV1beta1Api());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public LogApi logApi(){
+        return new LogApiImpl(k8sApiConfiguration().coreV1Api());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public PersistentVolumeClaimApi persistentVolumeClaimApi(){
+        return new PersistentVolumeClaimImpl(k8sApiConfiguration().coreV1Api());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public PodApi podApi(){
+        return new PodApiImpl(k8sApiConfiguration().coreV1Api());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public SecretApi secretApi(){
+        return new SecretApiImpl(k8sApiConfiguration().coreV1Api());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public ServiceApi serviceApi(){
+        return new ServiceApiImpl(k8sApiConfiguration().coreV1Api());
+    }
+
+    @Bean
+    @ConditionalOnBean(K8sApiConfiguration.class)
+    public WatchApiImpl watchApi(){
+        return new WatchApiImpl(k8sApiConfiguration().watchApiClient(),k8sApiConfiguration().watchAppsV1Api());
+    }
+}

+ 1 - 1
api/src/main/java/seecoder/devcloud/api/docker/impl/DockerApiImpl.java

@@ -34,7 +34,7 @@ public class DockerApiImpl implements DockerApi {
     private final String registry;
 
     @Autowired
-    public DockerApiImpl(ApplicationProperties properties) throws IOException {
+    public DockerApiImpl(ApplicationProperties properties){
         registry = properties.getDocker().getRegistry();
         client = new DefaultDockerClient(properties.getDocker().getHost());
         defaultHandler = new NoActionProgressHandler();

+ 3 - 0
api/src/main/java/seecoder/devcloud/api/gitlab/GitlabApi.java

@@ -24,8 +24,11 @@ public interface GitlabApi {
 	//50 => Owner access # Only valid for groups
 	GitlabGroup createGroup(String groupName) throws GitLabApiException;
 
+
 	void addMemberToGroup(Integer groupId, Integer userId) throws GitLabApiException;
 
+	void quitGroup(Integer groupId, Integer userId) throws GitLabApiException;
+
 	Integer addProjectToGroup(Integer groupId, String projectName) throws GitLabApiException;
 
 	/**

+ 8 - 1
api/src/main/java/seecoder/devcloud/api/gitlab/impl/GitlabApiImpl.java

@@ -76,6 +76,12 @@ public class GitlabApiImpl implements GitlabApi {
         server.getGroupApi().addMember(groupId, userId, AccessLevel.MASTER);
     }
 
+    @Override
+    public void quitGroup(Integer groupId, Integer userId) throws GitLabApiException {
+        server.getGroupApi().removeMember(groupId,userId);
+    }
+
+
     @Override
     public synchronized Integer addProjectToGroup(Integer groupId, String projectName) throws GitLabApiException {
         final Project project = server.getProjectApi().createProject(groupId, projectName);
@@ -178,7 +184,8 @@ public class GitlabApiImpl implements GitlabApi {
     @Override
     public synchronized void changePassword(int userId, String password) throws GitLabApiException {
         User user = server.getUserApi().getUser(userId);
-        server.getUserApi().modifyUser(user, password, user.getProjectsLimit());
+        //server.getUserApi().modifyUser(user, password, user.getProjectsLimit());
+        server.getUserApi().updateUser(user, password);
     }
 
     @Override

+ 2 - 0
api/src/main/java/seecoder/devcloud/api/k8s/K8sApiConfiguration.java

@@ -8,11 +8,13 @@ import io.kubernetes.client.openapi.apis.ExtensionsV1beta1Api;
 import io.kubernetes.client.util.Config;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 import seecoder.devcloud.api.ApplicationProperties;
 
 
 @Configuration
+@ComponentScan("seecoder.devcloud.api.k8s.impl")
 public class K8sApiConfiguration {
 
     private final ApiClient apiClient;

+ 12 - 1
api/src/main/java/seecoder/devcloud/api/k8s/K8sConstants.java

@@ -5,7 +5,7 @@ public class K8sConstants {
     public static final String NAME_PATTERN = "^[0-9a-zA-Z-]{8,}$";
 
     //标签域名前缀
-    private static final String LABEL_PREFIX = "demo.seec.nju.cn";
+    private static final String LABEL_PREFIX = "devcloud";
     //课程前缀
     public static final String TYPE_LABEL = LABEL_PREFIX + "/type";
     //描述
@@ -43,4 +43,15 @@ public class K8sConstants {
     public static final String METADATA_NAME_SELECTOR = "metadata.name=";
 
     public static final String DB_SECRET_NAME = "db-secret";
+
+    /**
+     * 部署应用时的名字后缀
+     */
+    public final static String CONTAINER_SUFFIX = "-container";
+
+    public final static String DEPLOYMENT_SUFFIX = "-deployment";
+
+    public final static String SERVICE_SUFFIX = "-service";
+
+    public final static String INGRESS_SUFFIX = "-suffix";
 }

+ 6 - 1
api/src/main/java/seecoder/devcloud/api/k8s/impl/SecretApiImpl.java

@@ -27,8 +27,13 @@ public class SecretApiImpl implements SecretApi {
 
     private static final Logger logger = LoggerUtil.getLogger(SecretApi.class);
 
+
+    private final CoreV1Api coreV1Api;
+
     @Autowired
-    private CoreV1Api coreV1Api;
+    public SecretApiImpl(CoreV1Api coreV1Api) {
+        this.coreV1Api = coreV1Api;
+    }
 
     @Override
     public SecretVO createGenericSecret(String namespace, String name, Map<String, byte[]> data) throws K8sApiException {

+ 6 - 1
api/src/main/java/seecoder/devcloud/api/k8s/impl/ServiceApiImpl.java

@@ -28,8 +28,13 @@ import static seecoder.devcloud.api.k8s.K8sConstants.PRETTY_FORMAT;
 public class ServiceApiImpl implements ServiceApi {
     private static final Logger logger = LoggerUtil.getLogger(ServiceApi.class);
 
+
+    private final CoreV1Api coreV1Api;
+
     @Autowired
-    private CoreV1Api coreV1Api;
+    public ServiceApiImpl(CoreV1Api coreV1Api) {
+        this.coreV1Api = coreV1Api;
+    }
 
     /**
      * 创建Service

+ 3 - 6
api/src/main/java/seecoder/devcloud/api/k8s/model/ConfigMap.java

@@ -4,10 +4,7 @@ package seecoder.devcloud.api.k8s.model;
 
 import io.kubernetes.client.openapi.models.V1ConfigMap;
 import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -16,18 +13,18 @@ import java.util.Map;
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
+@EqualsAndHashCode(callSuper=false)
 public class ConfigMap extends K8sAbstractObject<V1ConfigMap> {
 
     public static final String API_VERSION = "v1";
 
     public static final String KIND = "ConfigMap";
-
+    @Builder.Default
     private Map<String, String> data = new HashMap<>();
 
 
     public ConfigMap(V1ConfigMap v1ConfigMap) {
         super(v1ConfigMap);
-        this.data = v1ConfigMap.getData();
     }
 
     public void merge(V1ConfigMap v1ConfigMap) {

+ 4 - 0
api/src/main/java/seecoder/devcloud/api/k8s/model/Container.java

@@ -32,11 +32,14 @@ public class Container implements K8sObjectTransformer<V1Container> {
     /* 启动指令 */
     private List<String> command;
 
+    @Builder.Default
     /* 环境变量 */
     private Map<String, String> env = new HashMap<>();
 
+    @Builder.Default
     private Set<ContainerPort> ports = new HashSet<>();
 
+    @Builder.Default
     private List<VolumeMount> volumeMounts = new ArrayList<>();
 
     @Data
@@ -51,6 +54,7 @@ public class Container implements K8sObjectTransformer<V1Container> {
 
         String subPath;
 
+        @Builder.Default
         Boolean readOnly = true;
 
         public VolumeMount(V1VolumeMount v1VolumeMount) {

+ 8 - 4
api/src/main/java/seecoder/devcloud/api/k8s/model/Deployment.java

@@ -1,10 +1,7 @@
 package seecoder.devcloud.api.k8s.model;
 
 import io.kubernetes.client.openapi.models.*;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.*;
 import org.hibernate.validator.constraints.Range;
 import org.springframework.data.annotation.ReadOnlyProperty;
 
@@ -22,6 +19,7 @@ import java.util.stream.Collectors;
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
+@EqualsAndHashCode(callSuper=false)
 public class Deployment extends K8sAbstractObject<V1Deployment> {
 
     public static final String API_VERSION = "apps/v1";
@@ -34,12 +32,16 @@ public class Deployment extends K8sAbstractObject<V1Deployment> {
 
     public static final String DEFAULT_UPDATE_STRATEGY = "Recreate";
 
+    @Builder.Default
     private List<Container> containers = new ArrayList<>();
 
+    @Builder.Default
     private List<String> imagePullSecrets = new ArrayList<>();
 
+    @Builder.Default
     private List<V1Volume> volumes = new ArrayList<>();
 
+    @Builder.Default
     private Map<String, String> nodeSelectors = new HashMap<>();
     @Range(min = 1, max = 3)
     private int replicas;
@@ -47,11 +49,13 @@ public class Deployment extends K8sAbstractObject<V1Deployment> {
     /**
      * 超过timeout时间未完成启动则提示启动超时
      */
+    @Builder.Default
     private int timeout = DEFAULT_PROGRESS_DEADLINE_SECONDS;
 
     /**
      * 保留历史replica set的个数
      */
+    @Builder.Default
     private int revisionHistoryLimit = DEFAULT_REVISION_HISTORY_LIMIT;
 
     @ReadOnlyProperty

+ 3 - 0
api/src/main/java/seecoder/devcloud/api/k8s/model/Ingress.java

@@ -16,6 +16,7 @@ import java.util.stream.Collectors;
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
+@EqualsAndHashCode(callSuper=false)
 public class Ingress extends K8sAbstractObject<ExtensionsV1beta1Ingress> {
 
     public static final String API_VERSION = "extensions/v1beta1";
@@ -29,6 +30,7 @@ public class Ingress extends K8sAbstractObject<ExtensionsV1beta1Ingress> {
     //重定向的路径,默认为根路径
     private static final String redirectPath = DEFAULT_REWRITE_PATH;
 
+    @Builder.Default
     /*Ingress能暴露HTTP和HTTPS协议口,目前暂不支持HTTPS协议*/
     private List<IngressRule> httpRules = new ArrayList<>();
 
@@ -100,6 +102,7 @@ public class Ingress extends K8sAbstractObject<ExtensionsV1beta1Ingress> {
 
         private String host;
 
+        @Builder.Default
         private Set<IngressPath> ruleValues = new HashSet<>();
 
         public IngressRule(String host) {

+ 2 - 0
api/src/main/java/seecoder/devcloud/api/k8s/model/K8sObjectRequest.java

@@ -16,8 +16,10 @@ public class K8sObjectRequest {
 
     private String namespace;
 
+    @Builder.Default
     private Map<String, String> labels = new HashMap<>();
 
+    @Builder.Default
     private Map<String, String> annotations = new HashMap<>();
 
     private LabelSelector selector;

+ 3 - 4
api/src/main/java/seecoder/devcloud/api/k8s/model/PersistentVolumeClaim.java

@@ -5,10 +5,7 @@ import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim;
 import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimBuilder;
 import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimSpecBuilder;
 import io.kubernetes.client.openapi.models.V1ResourceRequirementsBuilder;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -18,6 +15,7 @@ import java.util.List;
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
+@EqualsAndHashCode(callSuper=false)
 public class PersistentVolumeClaim extends K8sAbstractObject<V1PersistentVolumeClaim> {
 
     public static final String API_VERSION = "v1";
@@ -28,6 +26,7 @@ public class PersistentVolumeClaim extends K8sAbstractObject<V1PersistentVolumeC
 
     private String volumeMode;
 
+    @Builder.Default
     private List<String> accessModes = new ArrayList<>();
 
     private Quantity quantity;

+ 6 - 7
api/src/main/java/seecoder/devcloud/api/k8s/model/Service.java

@@ -4,10 +4,7 @@ import io.kubernetes.client.openapi.models.V1Service;
 import io.kubernetes.client.openapi.models.V1ServiceBuilder;
 import io.kubernetes.client.openapi.models.V1ServiceSpec;
 import io.kubernetes.client.openapi.models.V1ServiceSpecBuilder;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -19,6 +16,7 @@ import java.util.stream.Collectors;
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
+@EqualsAndHashCode(callSuper=false)
 public class Service extends K8sAbstractObject<V1Service> {
 
     public static final String API_VERSION = "v1";
@@ -29,12 +27,13 @@ public class Service extends K8sAbstractObject<V1Service> {
 
     private String namespace;
 
+    @Builder.Default
     private Map<String, String> labels = new HashMap<>();
-
+    @Builder.Default
     private Map<String, String> annotations = new HashMap<>();
-
+    @Builder.Default
     private Set<ServicePort> servicePorts = new HashSet<>();
-
+    @Builder.Default
     private Map<String, String> selectors = new HashMap<>();
 
     public Service(V1Service v1Service) {

+ 2 - 0
api/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+seecoder.devcloud.api.DevcloudApiAutoConfiguration

+ 5 - 0
common/pom.xml

@@ -29,6 +29,11 @@
             <scope>compile</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>

+ 20 - 0
common/src/main/java/seecoder/devcloud/common/CommonAutoConfiguration.java

@@ -0,0 +1,20 @@
+package seecoder.devcloud.common;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import seecoder.devcloud.common.util.SpringUtil;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/5
+ * @description:
+ */
+@Configuration
+public class CommonAutoConfiguration {
+
+    @Bean
+    SpringUtil springUtil(){
+        return new SpringUtil();
+    }
+
+}

+ 1 - 1
common/src/main/java/seecoder/devcloud/common/exceptions/ServiceException.java

@@ -6,7 +6,7 @@ import org.springframework.http.HttpStatus;
 
 @Getter
 @ToString
-public class ServiceException extends Exception {
+public class ServiceException extends RuntimeException {
 	private int error;
 
 	public ServiceException() {

+ 13 - 0
common/src/main/java/seecoder/devcloud/common/serializer/CustomLocalDateTimeDeserializer.java

@@ -0,0 +1,13 @@
+package seecoder.devcloud.common.serializer;
+
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+
+import java.time.format.DateTimeFormatter;
+
+public class CustomLocalDateTimeDeserializer extends LocalDateTimeDeserializer {
+	private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
+
+	public CustomLocalDateTimeDeserializer() {
+		super(FORMATTER);
+	}
+}

+ 13 - 0
common/src/main/java/seecoder/devcloud/common/serializer/CustomLocalDateTimeSerializer.java

@@ -0,0 +1,13 @@
+package seecoder.devcloud.common.serializer;
+
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+
+import java.time.format.DateTimeFormatter;
+
+public class CustomLocalDateTimeSerializer extends LocalDateTimeSerializer {
+	private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
+
+	public CustomLocalDateTimeSerializer() {
+		super(FORMATTER);
+	}
+}

+ 32 - 0
common/src/main/java/seecoder/devcloud/common/serializer/TimestampLocalDateDeserializer.java

@@ -0,0 +1,32 @@
+package seecoder.devcloud.common.serializer;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+
+public class TimestampLocalDateDeserializer extends JsonDeserializer<LocalDateTime> {
+	@Override
+	public LocalDateTime deserialize(JsonParser p, DeserializationContext context) throws IOException {
+		Long epochSecond = null;
+		if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+			epochSecond = p.getLongValue();
+		} else if (p.hasToken(JsonToken.VALUE_STRING)) {
+			String value = p.getText().trim();
+			try {
+				epochSecond = Long.valueOf(value);
+			} catch (NumberFormatException e) {
+				epochSecond = null;
+			}
+		}
+		if (epochSecond != null) {
+			return LocalDateTime.ofInstant(Instant.ofEpochSecond(epochSecond), ZoneId.systemDefault());
+		}
+		return null;
+	}
+}

+ 29 - 0
common/src/main/java/seecoder/devcloud/common/serializer/TimestampLocalDateTimeSerializer.java

@@ -0,0 +1,29 @@
+package seecoder.devcloud.common.serializer;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+public class TimestampLocalDateTimeSerializer extends LocalDateTimeSerializer {
+	@Override
+	protected LocalDateTimeSerializer withFormat(Boolean useTimestamp, DateTimeFormatter f, JsonFormat.Shape shape) {
+		return this;
+	}
+
+	@Override
+	public void serialize(LocalDateTime value, JsonGenerator g, SerializerProvider provider) throws IOException {
+		g.writeNumber(value.atZone(ZoneId.systemDefault()).toEpochSecond());
+	}
+
+	@Override
+	public void serializeWithType(LocalDateTime value, JsonGenerator g, SerializerProvider provider, TypeSerializer typeSer) throws IOException {
+		serialize(value, g, provider);
+	}
+}

+ 0 - 9
common/src/main/java/seecoder/devcloud/common/util/SpringUtil.java

@@ -12,7 +12,6 @@ import javax.annotation.PostConstruct;
  * @date 2021/1/19
  * @description:
  */
-@Component
 public class SpringUtil implements ApplicationContextAware {
 
 
@@ -30,15 +29,7 @@ public class SpringUtil implements ApplicationContextAware {
         if(SpringUtil.applicationContext == null) {
             SpringUtil.applicationContext = applicationContext;
         }
-        System.out.println("---------------------------------------------------------------------");
-
-        System.out.println("---------------------------------------------------------------------");
-
-        System.out.println("---------------me.shijunjie.util.SpringUtil------------------------------------------------------");
-
         System.out.println("========ApplicationContext配置成功,在普通类可以通过调用SpringUtil.getAppContext()获取applicationContext对象,applicationContext="+SpringUtil.applicationContext+"========");
-
-        System.out.println("---------------------------------------------------------------------");
     }
 
     //获取applicationContext

+ 41 - 0
common/src/main/java/seecoder/devcloud/common/validate/FieldCompare.java

@@ -0,0 +1,41 @@
+package seecoder.devcloud.common.validate;
+
+
+import seecoder.devcloud.common.validate.validator.FieldCompareValidator;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({TYPE, ANNOTATION_TYPE})
+@Retention(RUNTIME)
+@Repeatable(FieldCompare.List.class)
+@Constraint(validatedBy = FieldCompareValidator.class)
+@Documented
+public @interface FieldCompare {
+	String message() default "{com.moekr.moocoder.util.validate.FieldCompare}";
+
+	Class<?>[] groups() default {};
+
+	Class<? extends Payload>[] payload() default {};
+
+	String lessField();
+
+	String greaterField();
+
+	boolean allowEqual() default false;
+
+	@Target({TYPE, ANNOTATION_TYPE})
+	@Retention(RUNTIME)
+	@Documented
+	@interface List {
+		FieldCompare[] value();
+	}
+}

+ 39 - 0
common/src/main/java/seecoder/devcloud/common/validate/FieldEquals.java

@@ -0,0 +1,39 @@
+package seecoder.devcloud.common.validate;
+
+import seecoder.devcloud.common.validate.validator.FieldEqualsValidator;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({TYPE, ANNOTATION_TYPE})
+@Retention(RUNTIME)
+@Repeatable(FieldEquals.List.class)
+@Constraint(validatedBy = FieldEqualsValidator.class)
+@Documented
+public @interface FieldEquals {
+	String message() default "{com.moekr.moocoder.util.validate.FieldEquals}";
+
+	Class<?>[] groups() default {};
+
+	Class<? extends Payload>[] payload() default {};
+
+	String firstField();
+
+	String secondField();
+
+	@Target({TYPE, ANNOTATION_TYPE})
+	@Retention(RUNTIME)
+	@Documented
+	@interface List {
+		FieldEquals[] value();
+	}
+
+}

+ 38 - 0
common/src/main/java/seecoder/devcloud/common/validate/validator/FieldCompareValidator.java

@@ -0,0 +1,38 @@
+package seecoder.devcloud.common.validate.validator;
+
+import org.springframework.beans.BeanUtils;
+import seecoder.devcloud.common.validate.FieldCompare;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+public class FieldCompareValidator implements ConstraintValidator<FieldCompare, Object> {
+	private String lessField;
+	private String greaterField;
+	private boolean allowEqual;
+
+
+	@Override
+	public void initialize(FieldCompare constraintAnnotation) {
+		this.lessField = constraintAnnotation.lessField();
+		this.greaterField = constraintAnnotation.greaterField();
+		this.allowEqual = constraintAnnotation.allowEqual();
+	}
+
+	@Override
+	@SuppressWarnings("unchecked")
+	public boolean isValid(Object value, ConstraintValidatorContext context) {
+		try {
+			Comparable lessObject = (Comparable) BeanUtils.getPropertyDescriptor(value.getClass(), lessField).getReadMethod().invoke(value);
+			Comparable greaterObject = (Comparable) BeanUtils.getPropertyDescriptor(value.getClass(), greaterField).getReadMethod().invoke(value);
+			int result = lessObject.compareTo(greaterObject);
+			if (allowEqual && result == 0) {
+				return true;
+			} else {
+				return result < 0;
+			}
+		} catch (Exception e) {
+			return false;
+		}
+	}
+}

+ 30 - 0
common/src/main/java/seecoder/devcloud/common/validate/validator/FieldEqualsValidator.java

@@ -0,0 +1,30 @@
+package seecoder.devcloud.common.validate.validator;
+
+import org.springframework.beans.BeanUtils;
+import seecoder.devcloud.common.validate.FieldEquals;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.util.Objects;
+
+public class FieldEqualsValidator implements ConstraintValidator<FieldEquals, Object> {
+	private String firstField;
+	private String secondField;
+
+	@Override
+	public void initialize(FieldEquals constraintAnnotation) {
+		this.firstField = constraintAnnotation.firstField();
+		this.secondField = constraintAnnotation.secondField();
+	}
+
+	@Override
+	public boolean isValid(Object value, ConstraintValidatorContext context) {
+		try {
+			Object firstObject = BeanUtils.getPropertyDescriptor(value.getClass(), firstField).getReadMethod().invoke(value);
+			Object secondObject = BeanUtils.getPropertyDescriptor(value.getClass(), secondField).getReadMethod().invoke(value);
+			return Objects.equals(firstObject, secondObject);
+		} catch (Exception e) {
+			return false;
+		}
+	}
+}

+ 2 - 0
common/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+seecoder.devcloud.common.CommonAutoConfiguration

+ 1 - 4
core/src/main/java/seecoder/devcloud/core/CoreApplication.java

@@ -4,7 +4,6 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
-import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
@@ -17,11 +16,9 @@ import java.util.TimeZone;
  */
 @EnableScheduling
 @EnableAsync
-@ComponentScan({"seecoder.devcloud.api","seecoder.devcloud.common"})
 @SpringBootApplication(exclude = {
         DataSourceAutoConfiguration.class,
-        DataSourceTransactionManagerAutoConfiguration.class,
-        HibernateJpaAutoConfiguration.class})
+        DataSourceTransactionManagerAutoConfiguration.class})
 public class CoreApplication {
 
     public static void main(String[] args) {

+ 41 - 0
core/src/main/java/seecoder/devcloud/core/pipeline/Context.java

@@ -0,0 +1,41 @@
+package seecoder.devcloud.core.pipeline;
+
+import lombok.Builder;
+import lombok.Data;
+import seecoder.devcloud.api.ApplicationProperties;
+
+import java.util.HashMap;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/2/2
+ * @description: 流水线上下文
+ */
+@Data
+@Builder
+public class Context {
+
+    private ApplicationProperties applicationProperties;
+
+    private String pipelineId;
+
+    private String pipelineName;
+
+    /**
+     * 流水线所属小组
+     */
+    private String groupName;
+
+    /**
+     * 项目名称
+     */
+    private String projectName;
+
+
+    /**
+     * 用来存放一些杂项信息,根据key去取
+     */
+    private HashMap<String, String> configs;
+
+
+}

+ 8 - 2
core/src/main/java/seecoder/devcloud/core/pipeline/Handler.java

@@ -5,6 +5,12 @@ package seecoder.devcloud.core.pipeline;
  * @date 2021/1/18
  * @description:
  */
-public interface Handler<I,O> {
-    O process(I input);
+public interface Handler {
+
+    void invoke(Context context);
+
+    void process(Context context);
+
+    void invokeNext(Context context);
+
 }

+ 5 - 15
core/src/main/java/seecoder/devcloud/core/pipeline/Pipeline.java

@@ -3,21 +3,11 @@ package seecoder.devcloud.core.pipeline;
 /**
  * @author PuHong Weng
  * @date 2021/1/18
- * @description: 流水线 I代表in,流水线的输入、O代表out,流水线的输出
+ * @description:
  */
-public class Pipeline<I, O> {
+public interface Pipeline {
 
-    private final Handler<I, O> currentHandler;
-
-    public Pipeline(Handler<I, O> currentHandler) {
-        this.currentHandler = currentHandler;
-    }
-
-    public <K> Pipeline<I, K> addHandler(Handler<O, K> newHandler) {
-        return new Pipeline<>(input -> newHandler.process(currentHandler.process(input)));
-    }
-
-    public O execute(I input) {
-        return currentHandler.process(input);
-    }
+    void init(PipelineConfig config);
+    void start();
+    Context getContext();
 }

+ 15 - 0
core/src/main/java/seecoder/devcloud/core/pipeline/PipelineConfig.java

@@ -0,0 +1,15 @@
+package seecoder.devcloud.core.pipeline;
+
+import lombok.Data;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/2/2
+ * @description:
+ */
+@Data
+public class PipelineConfig {
+
+
+    private String configJson;
+}

+ 0 - 15
core/src/main/java/seecoder/devcloud/core/pipeline/TestHandler.java

@@ -1,15 +0,0 @@
-package seecoder.devcloud.core.pipeline;
-
-/**
- * @author PuHong Weng
- * @date 2021/1/18
- * @description:
- */
-public class TestHandler implements Handler<String,String> {
-
-    @Override
-    public String process(String input) {
-        System.out.println("test");
-        return input.substring(1);
-    }
-}

+ 39 - 0
core/src/main/java/seecoder/devcloud/core/pipeline/handler/AbstractHandler.java

@@ -0,0 +1,39 @@
+package seecoder.devcloud.core.pipeline.handler;
+
+import lombok.Data;
+import seecoder.devcloud.core.pipeline.Context;
+import seecoder.devcloud.core.pipeline.Handler;
+
+import java.util.HashMap;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/2/2
+ * @description:
+ */
+@Data
+public abstract class AbstractHandler implements Handler {
+
+
+    public AbstractHandler nextHandler;
+
+//    /**
+//     * 用来存放Handler的配置
+//     */
+//    public HashMap<String,String> configs = new HashMap<>();
+
+    @Override
+    public void invoke(Context context) {
+        process(context);
+        invokeNext(context);
+    }
+
+    @Override
+    public void invokeNext(Context context) {
+        nextHandler.invoke(context);
+    }
+
+    public void setNextHandler(AbstractHandler nextHandler) {
+        this.nextHandler = nextHandler;
+    }
+}

+ 48 - 0
core/src/main/java/seecoder/devcloud/core/pipeline/handler/DockerImageBuildHandler.java

@@ -0,0 +1,48 @@
+package seecoder.devcloud.core.pipeline.handler;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import seecoder.devcloud.api.docker.DockerApi;
+import seecoder.devcloud.common.util.SpringUtil;
+import seecoder.devcloud.core.pipeline.Context;
+import seecoder.devcloud.core.pipeline.Handler;
+
+import java.io.*;
+import java.nio.file.Files;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/1/18
+ * @description: docker镜像构建
+ */
+@Slf4j
+public class DockerImageBuildHandler extends AbstractHandler {
+
+    private static final String TEMP_PREFIX = "SEECODER-DEVCLOUD-";
+
+    public DockerApi dockerApi;
+
+    public DockerImageBuildHandler(){
+        dockerApi = SpringUtil.getBean(DockerApi.class);
+    }
+
+    @Override
+    public void process(Context context){
+//        File tempDir = null;
+//        try {
+//            tempDir = Files.createTempDirectory(TEMP_PREFIX).toFile();
+//            //
+//            File dockerFile = new File(tempDir, "Dockerfile");
+//            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dockerFile)))) {
+//                writer.write(question.getQuestionType().getHelper().dockerFile(question.getName()));
+//            }
+//            //dockerApi.build(tempDir.getAbsolutePath(), question.getImageName(), question.getImageTag());
+//        } catch (IOException e){
+//            e.printStackTrace();
+//            log.error("镜像构建失败, 产生IO错误, {}", e.getMessage());
+//        } finally {
+//            FileUtils.deleteDirectory(tempDir);
+//        }
+    }
+
+}

+ 120 - 0
core/src/main/java/seecoder/devcloud/core/pipeline/handler/K8sDeploymentHandler.java

@@ -0,0 +1,120 @@
+package seecoder.devcloud.core.pipeline.handler;
+
+import lombok.extern.slf4j.Slf4j;
+import seecoder.devcloud.api.ApplicationProperties;
+import seecoder.devcloud.api.k8s.DeploymentApi;
+import seecoder.devcloud.api.k8s.K8sConstants;
+import seecoder.devcloud.api.k8s.SecretApi;
+import seecoder.devcloud.api.k8s.exception.K8sApiException;
+import seecoder.devcloud.api.k8s.model.Container;
+import seecoder.devcloud.api.k8s.model.ContainerPort;
+import seecoder.devcloud.api.k8s.model.Deployment;
+import seecoder.devcloud.api.k8s.model.K8sObjectRequest;
+import seecoder.devcloud.api.k8s.util.SecretTypeEnum;
+import seecoder.devcloud.api.k8s.vo.SecretVO;
+import seecoder.devcloud.common.util.SpringUtil;
+import seecoder.devcloud.core.pipeline.Context;
+import seecoder.devcloud.core.pipeline.Handler;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/1/18
+ * @description: 创建一个k8s Deployment
+ * 因为是学生的Deployment构建的过程,有以下约束:
+ * 1. 每个Deployment只允许创建一个Pod,防止资源消耗太多
+ * 2. 以Group Name,作为Namespace, Project Name来作为Deployment name
+ * 3. 镜像存放在seecoder的内部私有镜像仓库,所以需要设置
+ */
+@Slf4j
+public class K8sDeploymentHandler extends AbstractHandler {
+
+    private final SecretApi secretApi;
+
+    private final DeploymentApi deploymentApi;
+
+
+    //容器所使用的镜像
+    private String image;
+
+    //副本数
+    private int replicas;
+
+    //
+    private int port;
+
+
+    public K8sDeploymentHandler() {
+        secretApi = SpringUtil.getBean(SecretApi.class);
+        deploymentApi = SpringUtil.getBean(DeploymentApi.class);
+    }
+
+    /**
+     * 部署一个java项目
+     * 以小组名称作为namespace
+     *
+     * @param context
+     */
+    @Override
+    public void process(Context context) {
+        //配置初始化
+        initConfig(context);
+        String groupName = context.getGroupName();
+        String projectName = context.getProjectName();
+
+        //配置容器暴露端口
+        ContainerPort containerPort = ContainerPort.builder()
+                .port(port)
+                .build();
+        //配置容器
+        Container container = Container.builder()
+                .image(image)
+                .name(projectName + K8sConstants.CONTAINER_SUFFIX)
+                .ports(Collections.singleton(containerPort))
+                .build();
+
+        //配置deployment
+        Deployment deployment = Deployment.builder()
+                .replicas(replicas)
+                .containers(Collections.singletonList(container))
+                .build();
+        deployment.setName(projectName + K8sConstants.DEPLOYMENT_SUFFIX);
+        deployment.setNamespace(groupName);
+        deployment.setLabel(K8sConstants.APPLICATION_LABEL, projectName);
+
+        // 密钥来源:在命名空间创建时,创建对应的secret存放密钥,见 K8sNamespaceHandler.class
+        // 将registry密钥作为imagePullSecrets注入, 使得此deployment可以访问私有镜像仓库
+        List<String> registrySecrets = secretApi
+                .getSecretListByType(
+                        groupName,
+                        SecretTypeEnum.REGISTRY)
+                .stream()
+                .map(SecretVO::getName)
+                .collect(Collectors.toList()
+                );
+        deployment.setImagePullSecrets(registrySecrets);
+
+
+        try {
+            deploymentApi.create(deployment);
+            log.info("deployment创建成功");
+        } catch (K8sApiException e){
+            if (e.getCode() == K8sApiException.ALREADY_EXIST){
+                deploymentApi.update(deployment);
+                log.info("deployment更新成功");
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    private void initConfig(Context context) {
+        this.image = context.getConfigs().getOrDefault("image", "");
+        this.port = Integer.parseInt(context.getConfigs().getOrDefault("port", "8000"));
+        this.replicas = Integer.parseInt(context.getConfigs().getOrDefault("replicas", "1"));
+    }
+}

+ 11 - 9
core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/CommonIngressHandler.java → core/src/main/java/seecoder/devcloud/core/pipeline/handler/K8sIngressHandler.java

@@ -1,7 +1,9 @@
-//package seecoder.devcloud.core.pipeline.handlerImpl;
+//package seecoder.devcloud.core.pipeline.handler;
 //
 //import seecoder.devcloud.api.k8s.model.Ingress;
-//import Handler;
+//import seecoder.devcloud.core.pipeline.Context;
+//import seecoder.devcloud.core.pipeline.Handler;
+//
 //
 //import java.util.Collection;
 //import java.util.HashSet;
@@ -14,20 +16,20 @@
 // * @date 2021/1/18
 // * @description:
 // */
-//public class CommonIngressHandler implements Handler<String,String> {
+//public class K8sIngressHandler extends AbstractHandler {
 //
 //    @Override
-//    public String process(String input) {
+//    public void process(Context context) {
+//
+//
 //
+//        String groupName = context.getGroupName();
 //
-//        String appName = applicationDTO.getName();
-//        String projectName = applicationDTO.getProjectName();
+//        String projectName = context.getProjectName();
 //
 //        Ingress ingress = new Ingress();
-//        ingress.setName(appName + "-" + applicationDTO.getDeployEnvironment());
-//        String environment = applicationDTO.getDeployEnvironment() == DeployEnvironment.PRODUCTION ? "prod" : "test";
-//        ingress.setNamespace(projectName);
 //
+//        ingress.setName(appName + "-" + applicationDTO.getDeployEnvironment());
 //        Set<String> alreadyTaken = new HashSet<>();
 //        List<Ingress.IngressRule> ingressRules = applicationDTO.getContainers()
 //                .stream()

+ 55 - 0
core/src/main/java/seecoder/devcloud/core/pipeline/handler/K8sNamespaceHandler.java

@@ -0,0 +1,55 @@
+package seecoder.devcloud.core.pipeline.handler;
+
+import com.sun.xml.internal.stream.events.NamespaceImpl;
+import lombok.extern.slf4j.Slf4j;
+import seecoder.devcloud.api.ApplicationProperties;
+import seecoder.devcloud.api.k8s.NamespaceApi;
+import seecoder.devcloud.api.k8s.SecretApi;
+import seecoder.devcloud.api.k8s.exception.K8sApiException;
+import seecoder.devcloud.api.k8s.model.K8sObjectRequest;
+import seecoder.devcloud.api.k8s.model.Namespace;
+import seecoder.devcloud.common.util.SpringUtil;
+import seecoder.devcloud.core.pipeline.Context;
+
+import javax.swing.*;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/2/2
+ * @description: 创建k8sNamespace。这里以小组名称为单位创建。
+ */
+@Slf4j
+public class K8sNamespaceHandler extends AbstractHandler {
+
+    private NamespaceApi namespaceApi;
+
+    private SecretApi secretApi;
+
+    public K8sNamespaceHandler() {
+        namespaceApi = SpringUtil.getBean(NamespaceApi.class);
+        secretApi = SpringUtil.getBean(SecretApi.class);
+    }
+
+    @Override
+    public void process(Context context) {
+        Namespace namespace = new Namespace();
+        namespace.setName(context.getGroupName());
+
+        //todo namespace在底下资源未删除时,不能轻易删除重建,只能直接通过异常查是否已创建,丑陋,待修改
+        try {
+            namespace = namespaceApi.create(namespace);
+            log.info("Namespace创建成功, {}", namespace);
+            //为命名空间配置私有镜像仓库密钥,使得此命名空间可以访问镜像仓库来部署容器
+            ApplicationProperties.Registry registry = context.getApplicationProperties().getRegistry();
+            secretApi.createPrivateRegistrySecret(context.getGroupName(), registry.getHost(), registry.getUsername(), registry.getPassword());
+        } catch (K8sApiException e){
+            if (e.getCode() == K8sApiException.ALREADY_EXIST){
+                log.info("Namespace已经存在, 无需新建");
+            } else{
+                throw e;
+            }
+        }
+
+
+    }
+}

+ 67 - 0
core/src/main/java/seecoder/devcloud/core/pipeline/handler/K8sServiceHandler.java

@@ -0,0 +1,67 @@
+package seecoder.devcloud.core.pipeline.handler;
+
+import lombok.extern.slf4j.Slf4j;
+import seecoder.devcloud.api.k8s.K8sConstants;
+import seecoder.devcloud.api.k8s.ServiceApi;
+import seecoder.devcloud.api.k8s.exception.K8sApiException;
+import seecoder.devcloud.api.k8s.model.K8sObject;
+import seecoder.devcloud.api.k8s.model.K8sObjectRequest;
+import seecoder.devcloud.api.k8s.model.Service;
+import seecoder.devcloud.api.k8s.model.ServicePort;
+import seecoder.devcloud.common.util.SpringUtil;
+import seecoder.devcloud.core.pipeline.Context;
+import seecoder.devcloud.core.pipeline.Handler;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+import static seecoder.devcloud.api.k8s.K8sConstants.APPLICATION_LABEL;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/1/18
+ * @description: 通用的k8s建立service
+ */
+@Slf4j
+public class K8sServiceHandler extends AbstractHandler {
+
+    private ServiceApi serviceApi;
+
+    public K8sServiceHandler() {
+        serviceApi = SpringUtil.getBean(ServiceApi.class);
+    }
+
+
+    @Override
+    public void process(Context context){
+
+        //pod内容器暴露的端口
+        int targetPort =  Integer.parseInt(context.getConfigs().get("port"));
+        //service对集群内暴露的端口,这里默认与targetPort一致
+        int servicePort = targetPort;
+        context.getConfigs().put("servicePort", String.valueOf(servicePort));
+
+        //配置K8s的service
+        Service service = new Service();
+        service.setNamespace(context.getGroupName());
+        service.setName(context.getProjectName()+ K8sConstants.SERVICE_SUFFIX);
+        //标签选择器选择 此项目对应的deployment的name
+        service.setSelectors(Collections.singletonMap(K8sConstants.APPLICATION_LABEL, context.getProjectName()));
+        service.setServicePorts(
+                Collections.singleton(ServicePort.builder().port(servicePort).targetPort(targetPort).build())
+        );
+
+        try {
+            serviceApi.create(service);
+            log.info("service创建成功");
+        } catch (K8sApiException e){
+            if (e.getCode() == K8sApiException.ALREADY_EXIST){
+                serviceApi.update(service);
+                log.info("service更新成功");
+            } else {
+                throw e;
+            }
+        }
+    }
+}

+ 1 - 1
core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/MavenPackageHandler.java → core/src/main/java/seecoder/devcloud/core/pipeline/handler/MavenPackageHandler.java

@@ -1,4 +1,4 @@
-package seecoder.devcloud.core.pipeline.handlerImpl;
+package seecoder.devcloud.core.pipeline.handler;
 
 /**
  * @author PuHong Weng

+ 1 - 1
core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/MavenTestHandler.java → core/src/main/java/seecoder/devcloud/core/pipeline/handler/MavenTestHandler.java

@@ -1,4 +1,4 @@
-package seecoder.devcloud.core.pipeline.handlerImpl;
+package seecoder.devcloud.core.pipeline.handler;
 
 /**
  * @author PuHong Weng

+ 0 - 15
core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/CommonServiceHandler.java

@@ -1,15 +0,0 @@
-package seecoder.devcloud.core.pipeline.handlerImpl;
-
-import seecoder.devcloud.core.pipeline.Handler;
-
-/**
- * @author PuHong Weng
- * @date 2021/1/18
- * @description: 通用的k8s建立service
- */
-public class CommonServiceHandler implements Handler<String,String> {
-    @Override
-    public String process(String input) {
-        return null;
-    }
-}

+ 0 - 16
core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/DockerImageBuildHandler.java

@@ -1,16 +0,0 @@
-package seecoder.devcloud.core.pipeline.handlerImpl;
-
-import seecoder.devcloud.core.pipeline.Handler;
-
-/**
- * @author PuHong Weng
- * @date 2021/1/18
- * @description: docker镜像构建
- */
-public class DockerImageBuildHandler implements Handler<String,String> {
-
-    @Override
-    public String process(String input) {
-        return null;
-    }
-}

+ 0 - 41
core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/JavaAppDeploymentHandler.java

@@ -1,41 +0,0 @@
-package seecoder.devcloud.core.pipeline.handlerImpl;
-
-import seecoder.devcloud.api.k8s.DeploymentApi;
-import seecoder.devcloud.api.k8s.SecretApi;
-import seecoder.devcloud.api.k8s.impl.DeploymentApiImpl;
-import seecoder.devcloud.api.k8s.impl.SecretApiImpl;
-import seecoder.devcloud.api.k8s.model.Container;
-import seecoder.devcloud.api.k8s.model.Deployment;
-import seecoder.devcloud.common.util.SpringUtil;
-import seecoder.devcloud.core.pipeline.Handler;
-
-import java.util.Collections;
-
-/**
- * @author PuHong Weng
- * @date 2021/1/18
- * @description:
- */
-public class JavaAppDeploymentHandler implements Handler<String,String> {
-
-    private final SecretApi secretApi;
-
-    private final DeploymentApi deploymentApi;
-
-    public JavaAppDeploymentHandler() {
-        secretApi = SpringUtil.getBean(SecretApi.class);
-        deploymentApi = SpringUtil.getBean(DeploymentApi.class);
-    }
-
-    @Override
-    public String process(String input) {
-
-
-        Deployment deployment = Deployment.builder()
-                .replicas(1)
-                .containers(Collections.singletonList(new Container()))
-                .build();
-        deploymentApi.create(deployment);
-        return null;
-    }
-}

+ 0 - 10
core/src/main/java/seecoder/devcloud/core/pipeline/handlerImpl/MysqlDeploymentHandler.java

@@ -1,10 +0,0 @@
-package seecoder.devcloud.core.pipeline.handlerImpl;
-
-/**
- * @author PuHong Weng
- * @date 2021/1/18
- * @description:
- */
-public class MysqlDeploymentHandler {
-
-}

+ 55 - 0
core/src/main/resources/application-local.yml

@@ -0,0 +1,55 @@
+spring:
+  datasource:
+    url: jdbc:mysql://192.168.99.105:30006/devcloud?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
+    username: root
+    password: root
+
+  mail:
+    host: smtp.qq.com
+    username: 370774330@qq.com
+    password: qkoecnzivukvbibi
+    port: 465
+    protocol: smtp
+    properties:
+      mail:
+        smtp:
+          ssl:
+            enable: true
+          socketFactory.class: javax.net.ssl.SSLSocketFactory
+        debug: true
+server:
+  port: 8080
+
+mybatis:
+  configuration:
+    map-underscore-to-camel-case: true
+
+seecoder:
+  k8s:
+    api-server: https://192.168.99.105:8443
+    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IjBmSXc4RlRjNUpBYVpGYWc4QzRDY1MzUWJad1JMdFhKcDI1cngzWGtiRlkifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXZjbG91ZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLXNicXY4Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5NmIxNzU1ZS0zZjE3LTQ1ZTctODFmYi01MTRhODA3NDRhMzQiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGV2Y2xvdWQ6ZGVmYXVsdCJ9.W0Y1lR6LBi1ZjylBx35YeqYuq2u2AqH-ayK4FD-LDfC26E89DanOwSv9XLrRMnnDBZFS_ZCRBumbnDeMiS5vw2x7-QnrDqj1M5u9FB2fIJsz2LyUkOhATZZmTzj_Uhewvvg09aDKh16ttj6ZX27m-KVk_6MP-VfiuvZnCVxDbqRkRjK-zsor4ty1CEATPyKK-tvugAwS0RFNHUL94DamfZAq_t1PSnH5ZtEHq0uEMZxY2c7j6u9d_aD9lphm_ZpCy_C8OCHfanM21CQPDHlcJTW_aiOO6bAp8qdc1egPO5uIamHl6Zm0N3RkcsRp4DGOHrltTV9y_cGZHXSWmRHfxw
+    debugging: true
+  gitlab:
+    host: http://gitlab.192.168.99.105.nip.io
+    webHookProxy: http://localhost:3000
+    token: wXDeETv2Kmt-JhiApJ9H
+  mail:
+    from: 370774330@qq.com
+
+
+  jenkins:
+    host: http://192.168.68.79:8082
+    username: root
+    token: 11d7b9e4c1acac481904c25813e2ec1f0a
+  docker:
+    registry: 192.168.68.79:18082
+
+
+
+  registry:
+    host: 192.168.68.79:18082
+    username: admin
+    password: admin123
+    nexus-server: 192.168.68.79:18081
+    repo-name: sec-repo
+    clean-up-task: 83bb8249-71e8-4367-83b3-50c6f7518860

+ 4 - 44
core/src/main/resources/application.yml

@@ -1,44 +1,4 @@
-seecoder:
-  secret: 1qaz2wsx
-  auth:
-    signingKey: SEEC-1919810-OIDC-114514
-    iss: p.seecoder.cn
-    aud: seec-seec
-    portal: http://p.seecoder.cn/api/interaction
-    redirectUri: http://seec.seecoder.cn/api/auth/receive
-  gitlab:
-    host: http://gitlab.seec.seecoder.cn
-    git-proxy: http://gitlab.seec.seecoder.cn
-    webHookProxy: http://localhost:3000
-    username: root
-    token: x7Hwx-zq8LFkHznGku9a
-  jenkins:
-    host: http://192.168.68.79:8082
-    username: root
-    token: 11d7b9e4c1acac481904c25813e2ec1f0a
-    baseImage: 172.19.51.9:18082/build-base
-  docker:
-    registry: 192.168.68.79:18082
-  mail:
-    from: seecoder@163.com
-  storage:
-    type: local
-    properties:
-      location: ./problem
-  k8s:
-    api-server: https://192.168.99.101:8443
-    token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IkM0R1hBTlZ3UjU1eVlKemNocUxBMUtxcUEwaWR3NW9Tb0pzYjA3RktTcjAifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tZHo5bHYiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjE5NTg0N2NhLTlhYjEtNGQ0Yi05Yjk3LTFhYmQ0MmFlMTQwYyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.bEBgxEKWiATzkFu3gb2pljEHYcg5SZXd3fEH9RREbUGCzMQ7T2mibJ-MEMXnUJbEmft7L2OobUs1w_qPe2DSKgBbW97R6XYsillDhsORoDEhHbT-LIn2uK8hKa-1BYMoZ8g3Eo0FR2Ltkod5OrLEhldlkTMcylF_wtg64LlNHfG8MbGHFOZyjhHJRlMeZRrVDg9_c2KBHb_t42tD4NeaUcVrV8t3rA6qT69H8SvNCD4tioE1Pe3jJ840ZDXiJSzo7Qqk9zfyzkGsxC8ND6tc8UbaTt3tZqU9_TmDcptWGlE5wf1cu-F56PmotaQjOwTF1q4_XeY5Vbg_pVknR8-d7Q"
-    ingress-host: seecii.cn
-    public-namespace: dev
-    public-mysql-url: jdbc:mysql://seecii-mysql.dev.svc.cluster.local:3306
-    public-mysql-pod: seecii-mysql-579dc7c857-8m2fd
-    public-mysql-username: root
-    public-mysql-password: NJU67software
-    debugging: false
-  registry:
-    host: 192.168.68.79:18082
-    username: admin
-    password: admin123
-    nexus-server: 192.168.68.79:18081
-    repo-name: sec-repo
-    clean-up-task: 83bb8249-71e8-4367
+spring:
+  profiles:
+    active: local
+

+ 35 - 14
core/src/test/java/seecoder/devcloud/core/CoreApplicationTests.java

@@ -1,15 +1,22 @@
 package seecoder.devcloud.core;
 
+import org.junit.Before;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.xml.NamespaceHandler;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 import seecoder.devcloud.api.ApplicationProperties;
 import seecoder.devcloud.api.k8s.DeploymentApi;
-import seecoder.devcloud.core.pipeline.handlerImpl.JavaAppDeploymentHandler;
+import seecoder.devcloud.core.pipeline.Context;
+import seecoder.devcloud.core.pipeline.handler.K8sDeploymentHandler;
 import seecoder.devcloud.core.pipeline.Pipeline;
-import seecoder.devcloud.core.pipeline.TestHandler;
+import seecoder.devcloud.core.pipeline.handler.K8sNamespaceHandler;
+
+import java.util.Collection;
 
 @RunWith(SpringRunner.class)
 @SpringBootTest
@@ -21,20 +28,34 @@ class CoreApplicationTests {
     @Autowired
     private DeploymentApi deploymentApi;
 
+    private Context context;
+
+
+    @BeforeEach
+    void setUp() {
+        mockContext();
+    }
+
     @Test
-    void contextLoads() {
-
-        Pipeline<String,String> filters = new Pipeline(new TestHandler())
-                .addHandler(new TestHandler())
-                .addHandler(new TestHandler());
-        filters.execute("GoYankees123!");
-//        System.out.printf(applicationProperties.toString());
-//        System.out.println("tset");
-////        System.out.printf(deploymentApi.toString());
-////        System.out.printf(SpringUtil.getBean(DeploymentApi.class).toString());
-        JavaAppDeploymentHandler handler = new JavaAppDeploymentHandler();
-//        handler.process("test");
+    void k8sDeployment() {
+
 
     }
 
+    @Test
+    void k8sNamespace() {
+
+        K8sNamespaceHandler k8sNamespaceHandler = new K8sNamespaceHandler();
+        k8sNamespaceHandler.process(context);
+
+    }
+
+
+    private void mockContext(){
+        this.context = Context.builder()
+                .groupName("test-group")
+                .projectName("test-project")
+                .applicationProperties(applicationProperties)
+                .configs(null).build();
+    }
 }

+ 20 - 23
web/pom.xml

@@ -22,18 +22,6 @@
     </properties>
 
     <dependencies>
-        <!--MybatisTest依赖包-->
-        <dependency>
-            <groupId>org.mybatis.spring.boot</groupId>
-            <artifactId>mybatis-spring-boot-starter-test</artifactId>
-            <version>2.1.0</version>
-        </dependency>
-        <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
-            <scope>runtime</scope>
-        </dependency>
-
         <!-- Swagger -->
         <!-- https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter -->
         <dependency>
@@ -53,10 +41,6 @@
             <artifactId>api</artifactId>
             <version>0.0.1-SNAPSHOT</version>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-jpa</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-jdbc</artifactId>
@@ -75,23 +59,35 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-mail</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-tomcat</artifactId>
-            <scope>provided</scope>
-        </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>
-
         <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
             <version>2.1.4</version>
         </dependency>
-
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter-test</artifactId>
+            <version>2.1.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
     </dependencies>
 
     <dependencyManagement>
@@ -124,6 +120,7 @@
                 <version>2.3.4.RELEASE</version>
                 <configuration>
                     <mainClass>seecoder.devcloud.web.devcloudWebApplication</mainClass>
+                    <!--  todo 打包时需排除一些无法回滚的测试类  -->
                 </configuration>
                 <executions>
                     <execution>

+ 6 - 2
web/src/main/java/seecoder/devcloud/web/DevcloudWebApplication.java

@@ -1,10 +1,13 @@
 package seecoder.devcloud.web;
 
+import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 import java.time.ZoneId;
 import java.util.TimeZone;
@@ -12,8 +15,9 @@ import java.util.TimeZone;
 
 @EnableScheduling
 @EnableAsync
-@SpringBootApplication
-@ComponentScan({"seecoder.devcloud.api","seecoder.devcloud.common","seecoder.devcloud.web"})
+@SpringBootApplication(exclude = {HibernateJpaAutoConfiguration.class})
+@MapperScan(basePackages = {"seecoder.devcloud.web.dao"})
+@EnableTransactionManagement //开始事务
 public class DevcloudWebApplication {
 
     public static void main(String[] args) {

+ 57 - 0
web/src/main/java/seecoder/devcloud/web/controller/user/UserController.java

@@ -0,0 +1,57 @@
+package seecoder.devcloud.web.controller.user;
+
+import jnr.ffi.annotations.In;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import seecoder.devcloud.web.service.user.UserService;
+import seecoder.devcloud.web.vo.Response;
+import seecoder.devcloud.web.vo.user.ChangePasswordVO;
+import seecoder.devcloud.web.vo.user.RegisterVO;
+import seecoder.devcloud.web.vo.user.UserVO;
+
+import javax.validation.Validation;
+import javax.validation.constraints.Pattern;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/5
+ * @description:
+ */
+@RestController("/user")
+@ResponseBody
+public class UserController {
+
+    private final UserService userService;
+
+    @Autowired
+    public UserController(UserService userService) {
+        this.userService = userService;
+    }
+
+    @PostMapping("/register")
+    public Response<UserVO> register(@RequestBody RegisterVO registerVO){
+        return Response.ok(userService.register(registerVO)) ;
+    }
+
+    @PutMapping("/password")
+    public Response changePassword(@RequestBody ChangePasswordVO vo){
+        userService.changePassword(vo);
+        return Response.ok();
+    }
+
+    @PutMapping("nickname")
+    public Response<UserVO> changeUserInfo(
+            @RequestParam("userId") Integer userid,
+            @RequestParam("nickname")
+            @Pattern(regexp = "^\\w+${4,23}", message = "昵称只能包含大小写字母、数字和下划线,且必须为4-23位!")
+                    String nickName) {
+        return Response.ok(userService.changeUserInfo(userid, nickName));
+    }
+
+    @GetMapping("/id")
+    public Response<UserVO> findUserById(
+            @RequestParam("userId") Integer userId){
+        return Response.ok(userService.getUserById(userId));
+    }
+
+}

+ 156 - 0
web/src/main/java/seecoder/devcloud/web/dao/GeneralInsertUpdateSqlProvider.java

@@ -0,0 +1,156 @@
+package seecoder.devcloud.web.dao;
+
+import org.apache.ibatis.jdbc.SQL;
+
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/1
+ * @description: 避免写繁琐的插入和更新的方法的,注意 pojo类的字段要和数据库表对应, 用法示例见 UserMapper.class
+ * 插入单个参数(除了ignoredCols以外的参数)时,mapper写方法就行,实例见 UserMapper.insert
+ * 插入多个参数(除了ignoredCols以外的参数)时,mapper方法参数要加@Param说明,实例见 GroupMemberMapper.insert
+ */
+public class GeneralInsertUpdateSqlProvider {
+
+    /**
+     * @param obj 插入对象, 用来获取字段和表名
+     * @param ignoredCols 插入时需要忽略的列名 列名格式是Underscore,如created_at
+     * @return sql
+     */
+    public static String insert(Object obj,String... ignoredCols){
+        Map<String, String> map;
+        try {
+            map = getFieldsMap(obj, true, false);
+            // 创建 需要赋当前的时间的timestamp字段 在这里排除,否则无法正常赋值
+            for (String col: ignoredCols){
+                map.remove(camelCase2Underscore(col));
+            }
+        } catch (IllegalArgumentException | IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+        return getInsertSQL(obj, map);
+    }
+
+    private static String getInsertSQL(Object obj, Map<String, String> map){
+        return new SQL() {
+            {
+                INSERT_INTO(getTableName(obj));
+                for (String col : map.keySet()) {
+                    VALUES(col, map.get(col));
+                }
+            }
+        }.toString();
+    }
+
+    /**
+     * 更新obj所含的字段,但是不包括为null的字段
+     */
+    public static String updateById(Object obj) {
+        return updateById(obj, false);
+    }
+
+    /**
+     * 更新obj所含的字段,但是包括为null的字段
+     */
+    public static String updateWithNullById(Object obj) {
+        return updateById(obj, true);
+    }
+
+    /**
+     * 按传入的列名更新字段
+     */
+    public static String updateWithColsById(Object obj, String... cols) {
+        return updateById(obj, true, cols);
+    }
+
+    private static String updateById(Object obj, boolean includeNullValueField, String... cols) {
+        Map<String, String> map;
+        try {
+            map = getFieldsMap(obj, includeNullValueField, cols.length == 0);
+            map.remove("id");
+            //有可变长参数就保留可边长参数
+            if (cols.length!=0){
+                Set<String> colsSet = Arrays.stream(cols).collect(Collectors.toSet());
+                map = map.entrySet().stream()
+                        .filter(e -> colsSet.contains(e.getKey()))
+                        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+            }
+        } catch (IllegalArgumentException | IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+        return getUpdateSQL(obj,map);
+    }
+
+    private static String getUpdateSQL(Object obj, Map<String, String> map){
+        return new SQL() {
+            {
+                UPDATE(getTableName(obj));
+                for (String col : map.keySet()) {
+                    SET(col + "=" + map.get(col));
+                }
+                WHERE("id = #{id}");
+            }
+        }.toString();
+    }
+
+
+    /**
+     *
+     * @param obj 获取字段名的对象
+     * @param includeNullValue 是否要获取为null的字段名
+     * @param singleParam sql参数只有一个实体类的时候 设置为true,多个参数的时候设置false
+     * @return
+     * @throws IllegalArgumentException
+     * @throws IllegalAccessException
+     */
+    private static Map<String, String> getFieldsMap(Object obj, boolean includeNullValue, boolean singleParam)
+            throws IllegalArgumentException, IllegalAccessException {
+        HashMap<String, String> result = new HashMap<>();
+        Class<?> cls = obj.getClass();
+        Field[] fields = cls.getDeclaredFields(); // getDeclaredFields
+        for (Field f : fields) {
+            String col = f.getName();
+            String colName = col.substring(0, 1).toUpperCase() + col.substring(1);
+            f.setAccessible(true);
+            try {
+                cls.getMethod("get" + colName);
+                cls.getMethod("set" + colName, f.getType());
+            } catch (NoSuchMethodException | SecurityException e) {
+                continue;
+            }
+            /**
+             * 单参数的时候
+             * #{colname}可以直接取到实体的属性
+             * 多参数的时候
+             * #{param1.colname}来取实体的属性
+             */
+            String paramPrefix =  singleParam? "#{" : "#{param1.";
+            if ((!"id".equals(col) && includeNullValue) || f.get(obj) != null) {
+
+
+
+                result.put(camelCase2Underscore(col), paramPrefix + col + "}");
+            }
+        }
+        return result;
+    }
+
+    public static String getTableName(Object obj) {
+        return camelCase2Underscore(obj.getClass().getSimpleName());
+    }
+
+    public static String camelCase2Underscore(String s) {
+        StringBuffer buf = new StringBuffer();
+        for (String w : s.split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")) {
+            buf.append("_").append(w.toLowerCase());
+        }
+
+        return "`"+ buf.substring(1) + "`";
+    }
+
+}

+ 1 - 1
web/src/main/java/seecoder/devcloud/web/dataMapper/provider/TreeNodeProvider.java → web/src/main/java/seecoder/devcloud/web/dao/provider/TreeNodeProvider.java

@@ -1,4 +1,4 @@
-package seecoder.devcloud.web.dataMapper.provider;
+package seecoder.devcloud.web.dao.provider;
 
 import org.apache.ibatis.jdbc.SQL;
 import seecoder.devcloud.web.po.TreeNodePO;

+ 2 - 3
web/src/main/java/seecoder/devcloud/web/dataMapper/TreeNodeMapper.java → web/src/main/java/seecoder/devcloud/web/dao/tree/TreeNodeMapper.java

@@ -1,11 +1,10 @@
-package seecoder.devcloud.web.dataMapper;
+package seecoder.devcloud.web.dao.tree;
 
 
 import org.apache.ibatis.annotations.*;
-import org.apache.ibatis.type.EnumOrdinalTypeHandler;
 import org.springframework.stereotype.Repository;
-import seecoder.devcloud.web.dataMapper.provider.TreeNodeProvider;
 import seecoder.devcloud.web.po.TreeNodePO;
+import seecoder.devcloud.web.dao.provider.TreeNodeProvider;
 
 import java.util.List;
 

+ 45 - 0
web/src/main/java/seecoder/devcloud/web/dao/user/GroupMapper.java

@@ -0,0 +1,45 @@
+package seecoder.devcloud.web.dao.user;
+
+
+
+import org.apache.ibatis.annotations.*;
+import seecoder.devcloud.web.dao.GeneralInsertUpdateSqlProvider;
+import seecoder.devcloud.web.po.user.Group;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/4
+ * @description:
+ * todo 因为组和gitlab相连,删除组的功能如要实现需要把gitlab资源也删除
+ */
+@Mapper
+public interface GroupMapper {
+
+    /**
+     * id并非由此后端生成,而是gitlab创建后,将gitlab groupid填入
+     */
+    @InsertProvider(type = GeneralInsertUpdateSqlProvider.class, method = "insert")
+    int insert(Group group,String... ignoredCols);
+
+    @UpdateProvider(type= GeneralInsertUpdateSqlProvider.class, method="updateById")
+    int update(Group group);
+
+    @Select("select * from `group` where id = #{id}")
+    Group findGroupById(Integer id);
+
+    @Select("select * from `group` where name = #{name}")
+    Group findGroupByName(String name);
+
+    @Select("select *, id as group_id from `group` where id = #{id}")
+    @Result(column = "group_id", property = "members", many = @Many(select = "seecoder.devcloud.web.dao.user.GroupMemberMapper.selectUsersByGroupId"))
+    Group findWithMembersById(Integer id);
+
+    @Deprecated
+    Group findGroupByShareLink(String shareLink);
+
+
+
+
+
+
+}

+ 35 - 0
web/src/main/java/seecoder/devcloud/web/dao/user/GroupMemberMapper.java

@@ -0,0 +1,35 @@
+package seecoder.devcloud.web.dao.user;
+
+import jnr.ffi.annotations.In;
+import org.apache.ibatis.annotations.*;
+import seecoder.devcloud.web.po.user.Group;
+import seecoder.devcloud.web.po.user.User;
+
+import java.util.List;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/4
+ * @description:
+ */
+public interface GroupMemberMapper {
+
+    @Insert("insert into group_member (group_id,user_id) values (#{groupId}, #{userId}) ")
+    int insert(@Param("groupId") int groupId,@Param("userId") int userId);
+
+    @Delete("delete from group_member where group_id = #{groupId} and user_id = #{userId}")
+    int delete(@Param("groupId") int groupId,@Param("userId")  int userId);
+
+    @Select("select user_id from group_member where group_id = #{groupId}")
+    List<Integer> selectUserIdsByGroupId(int groupId);
+
+    @Select("select group_id from group_member where user_id = #{userId}")
+    List<Integer> selectGroupIdsByUserId(int groupId);
+
+    @Select("select u.* from group_member gm, user u where u.id = gm.user_id and gm.group_id = #{groupId}")
+    List<User> selectUsersByGroupId(int groupId);
+
+    @Select("select g.* from group_member gm, group g where g.id = gm.group_id and gm.userId = #{userId}")
+    List<Group> selectGroupsByUserId(int userId);
+
+}

+ 35 - 0
web/src/main/java/seecoder/devcloud/web/dao/user/UserMapper.java

@@ -0,0 +1,35 @@
+package seecoder.devcloud.web.dao.user;
+
+
+import org.apache.ibatis.annotations.*;
+import seecoder.devcloud.web.dao.GeneralInsertUpdateSqlProvider;
+import seecoder.devcloud.web.po.user.User;
+
+
+@Mapper
+public interface UserMapper {
+
+	/**
+	 * 这里的id是取自gitlab的用户id,所以不需要自增和回填
+	 */
+	@InsertProvider(type = GeneralInsertUpdateSqlProvider.class, method = "insert")
+	int insert(User user,String... ignoredCols);
+
+	@UpdateProvider(type= GeneralInsertUpdateSqlProvider.class, method="updateById")
+	int updateById(User user);
+
+	@Delete("delete from user where id = #{id}")
+	int delete(User user);
+
+
+	@Select("select * from user where id = #{id}")
+	User findById(int id);
+
+	@Select("select * from user where username = #{username}")
+	User findByUsername(String username);
+
+	@Select("select * from user where email = #{email}")
+	User findByEmail(String email);
+
+
+}

+ 27 - 0
web/src/main/java/seecoder/devcloud/web/infrastructure/GlobalExceptionHandler.java

@@ -0,0 +1,27 @@
+package seecoder.devcloud.web.infrastructure;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import seecoder.devcloud.common.exceptions.ServiceException;
+import seecoder.devcloud.web.vo.Response;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/5
+ * @description: 统一异常返回处理
+ * todo 应为整个系统使用了学长的老API,是否有没捕捉的异常,待进一步验证
+ */
+@ControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(ServiceException.class)
+    @ResponseBody
+    public Response serviceExceptionHandler(ServiceException e){
+        log.error(e.getMessage(),e);
+        return Response.serverInternalError(e.getMessage());
+    }
+
+}

+ 1 - 7
web/src/main/java/seecoder/devcloud/web/config/SwaggerConfig.java → web/src/main/java/seecoder/devcloud/web/infrastructure/config/SwaggerConfig.java

@@ -1,4 +1,4 @@
-package seecoder.devcloud.web.config;
+package seecoder.devcloud.web.infrastructure.config;
 
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
@@ -12,12 +12,6 @@ import springfox.documentation.oas.annotations.EnableOpenApi;
 import springfox.documentation.service.ApiInfo;
 import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
-
-import java.util.function.Predicate;
-
-import static springfox.documentation.builders.PathSelectors.regex;
 
 /**
  * @author PuHong Weng

+ 81 - 0
web/src/main/java/seecoder/devcloud/web/infrastructure/security/WebSecurityConfiguration.java

@@ -0,0 +1,81 @@
+//package seecoder.devcloud.web.infrastructure.security;
+//
+//
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.context.annotation.Configuration;
+//import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+//import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+//import org.springframework.security.config.annotation.web.builders.WebSecurity;
+//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+//import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+//import org.springframework.security.core.userdetails.UserDetailsService;
+//import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+//import seecoder.devcloud.api.ApplicationProperties;
+//import seecoder.devcloud.web.dao.user.UserMapper;
+//import seecoder.devcloud.web.service.user.UserService;
+//
+//
+//@EnableWebSecurity
+//@EnableGlobalMethodSecurity(jsr250Enabled = true)
+//public class WebSecurityConfiguration {
+////	@Configuration
+////	@Order(Ordered.LOWEST_PRECEDENCE)
+////	public static class ApiWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+////		@Override
+////		protected void configure(HttpSecurity http) throws Exception {
+////			http.antMatcher("/api/**")
+////					.authorizeRequests()
+////					.anyRequest().authenticated()
+////					.and()
+////					.httpBasic()
+////					.and()
+////					.csrf().disable()
+////					.cors();
+////		}
+////	}
+//
+//	@Configuration
+//	public static class DefaultWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+//		private final UserDetailsService userDetailsService;
+//		private final LoginRedirectHandler loginRedirectHandler;
+//		@Autowired
+//		private UserMapper userMapper;
+//		@Autowired
+//		private ApplicationProperties applicationProperties;
+//		@Autowired
+//		private UserService userService;
+//
+//		@Autowired
+//		public DefaultWebSecurityConfiguration(UserDetailsServiceImpl userDetailsService, LoginRedirectHandler loginRedirectHandler) {
+//			this.userDetailsService = userDetailsService;
+//			this.loginRedirectHandler = loginRedirectHandler;
+//		}
+//		@Override
+//		protected void configure(HttpSecurity http) throws Exception {
+//			JwtAuthenticationFilter jwtAuthenticationFilter =
+//					new JwtAuthenticationFilter(authenticationManager(),
+//							userDetailsService, new RestAuthorEntry(),
+//							new AntPathRequestMatcher("/api/auth/receive", "GET"),
+//							applicationProperties,
+//							userMapper,
+//							userService);
+//			http
+//					.authorizeRequests()
+//						.antMatchers("/api/auth/register","/test" ,"/internal/**").permitAll()
+//			 			.antMatchers("/file/**").hasAnyAuthority(TEACHER_AUTHORITY.getAuthority(), ADMIN_AUTHORITY.getAuthority())
+//						.anyRequest().authenticated()
+//						.and()
+//						.addFilter(jwtAuthenticationFilter)
+//						.cors()
+//						.and()
+//						.csrf().disable();
+//		}
+//
+//		@Override
+//		public void configure(WebSecurity web) throws Exception {
+//			web
+//					.ignoring()
+//					.antMatchers("/api/auth/register","/test" ,"/internal/**");
+//		}
+//	}
+//}

+ 16 - 0
web/src/main/java/seecoder/devcloud/web/infrastructure/security/WebSecurityConstants.java

@@ -0,0 +1,16 @@
+package seecoder.devcloud.web.infrastructure.security;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+public abstract class WebSecurityConstants {
+	private static final String ROLE_PREFIX = "ROLE_";
+
+	public static final String STUDENT_ROLE = "STUDENT";
+	public static final String TEACHER_ROLE = "TEACHER";
+	public static final String ADMIN_ROLE = "ADMIN";
+
+	public static final GrantedAuthority STUDENT_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + STUDENT_ROLE);
+	public static final GrantedAuthority TEACHER_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + TEACHER_ROLE);
+	public static final GrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + ADMIN_ROLE);
+}

+ 7 - 1
web/src/main/java/seecoder/devcloud/web/po/user/Group.java

@@ -3,6 +3,10 @@ package seecoder.devcloud.web.po.user;
 
 import lombok.Builder;
 import lombok.Data;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 import seecoder.devcloud.web.enums.GroupType;
 
 import java.util.List;
@@ -16,6 +20,8 @@ import java.util.List;
  */
 @Data
 @Builder
+@NoArgsConstructor
+@AllArgsConstructor
 public class Group {
 
     /**
@@ -47,4 +53,4 @@ public class Group {
      */
     private List<User> members;
 
-}
+}

+ 20 - 20
web/src/main/java/seecoder/devcloud/web/po/user/User.java

@@ -22,32 +22,32 @@ import java.util.Set;
 @Data
 public class User {
 
-   /**
-    * 注意用户id并非由此后端数据库生成,而是gitlab创建后填入,以确保两者的关联性的正确
-    */
-   private Integer id;
+	/**
+	 * 注意用户id并非由此后端数据库生成,而是gitlab创建后填入,以确保两者的关联性的正确
+	 */
+	private Integer id;
 
-   private String username;
+	private String username;
 
-   private String nickname="";
+	private String nickname="";
 
-   private String password;
+	private String password;
 
-   private String phone;
+	private String phone;
 
-   private String email;
+	private String email;
 
-   private UserIdentity role;
+	private UserIdentity role;
 
-   private Timestamp createdAt;
+	private Timestamp createdAt;
 
-   /**
-    * 这个属性来自于gitlab
-    */
-   private Integer namespace;
+	/**
+	 * 这个属性来自于gitlab
+	 */
+	private Integer namespace;
 
-   /**
-    * 这个属性来自于gitlab
-    */
-   private String token;
-}
+	/**
+	 * 这个属性来自于gitlab
+	 */
+	private String token;
+}

+ 1 - 1
web/src/main/java/seecoder/devcloud/web/service/impl/tree/TreeNodeServiceImpl.java

@@ -4,11 +4,11 @@ package seecoder.devcloud.web.service.impl.tree;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import seecoder.devcloud.web.dataMapper.TreeNodeMapper;
 import seecoder.devcloud.web.po.TreeNodePO;
 import seecoder.devcloud.web.service.tree.TreeNodeService;
 import seecoder.devcloud.web.vo.Response;
 import seecoder.devcloud.web.vo.TreeNodeVO;
+import seecoder.devcloud.web.dao.tree.TreeNodeMapper;
 
 import java.util.List;
 

+ 110 - 0
web/src/main/java/seecoder/devcloud/web/service/impl/user/GroupServiceImpl.java

@@ -0,0 +1,110 @@
+package seecoder.devcloud.web.service.impl.user;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.gitlab4j.api.GitLabApiException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import seecoder.devcloud.api.gitlab.GitlabApi;
+import seecoder.devcloud.api.gitlab.model.GitlabGroup;
+import seecoder.devcloud.common.exceptions.EntityNotFoundException;
+import seecoder.devcloud.common.exceptions.ServiceException;
+import seecoder.devcloud.web.dao.user.GroupMapper;
+import seecoder.devcloud.web.dao.user.GroupMemberMapper;
+import seecoder.devcloud.web.dao.user.UserMapper;
+import seecoder.devcloud.web.enums.GroupType;
+import seecoder.devcloud.web.po.user.Group;
+import seecoder.devcloud.web.po.user.User;
+import seecoder.devcloud.web.service.user.GroupService;
+import seecoder.devcloud.web.vo.user.CustomUserDetails;
+import seecoder.devcloud.web.vo.user.GroupVO;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+
+@Service
+@Slf4j
+public class GroupServiceImpl implements GroupService {
+    private final GroupMapper groupMapper;
+    private final UserMapper userMapper;
+    private final GroupMemberMapper groupMemberMapper;
+    private final GitlabApi gitlabApi;
+
+    @Autowired
+    public GroupServiceImpl(GroupMapper groupMapper, UserMapper userMapper, GroupMemberMapper groupMemberMapper, GitlabApi gitlabApi) {
+        this.groupMapper = groupMapper;
+        this.userMapper = userMapper;
+        this.groupMemberMapper = groupMemberMapper;
+        this.gitlabApi = gitlabApi;
+    }
+
+    @Override
+    @Transactional
+    public GroupVO createGroup(String name, Integer userId) throws ServiceException {
+        if (groupMapper.findGroupByName(name) != null){
+            throw new ServiceException("您创建的小组名字已被使用!");
+        }
+        User user = userMapper.findById(userId);
+        List<User> members = new ArrayList<>();
+        members.add(user);
+        GitlabGroup gitGroup;
+        try {
+            gitGroup = gitlabApi.createGroup(name);
+            gitlabApi.addMemberToGroup(gitGroup.getId(), user.getId());
+        } catch (GitLabApiException e) {
+            log.error(e.getMessage(), e);
+            throw new RuntimeException("创建Group时发生异常[" + e.getMessage() + "]");
+        }
+        Group group = Group.builder()
+                .id(gitGroup.getId())
+                .name(name)
+                .namespace(gitGroup.getNamespace())
+                .shareLink(UUID.randomUUID().toString().replace("-", ""))
+                .type(GroupType.GROUP)
+                .members(members)
+                .build();
+        groupMapper.insert(group, "members");
+        groupMemberMapper.insert(gitGroup.getId(), userId);
+        return new GroupVO(group);
+    }
+
+
+    @Override
+    //@Transactional
+    public GroupVO addMember(Integer userId, Integer invitedUserId, Integer groupId) throws EntityNotFoundException {
+        List<Integer> userIds = groupMemberMapper.selectUserIdsByGroupId(groupId);
+        if (!userIds.contains(userId)) {
+            throw new EntityNotFoundException("小组邀请发生错误,用户没有权限!");
+        }
+        if (userIds.contains(invitedUserId)) {
+            throw new RuntimeException("邀请的用户已经加入小组!");
+        }
+        try {
+            gitlabApi.addMemberToGroup(groupId, invitedUserId);
+        } catch (GitLabApiException e) {
+            log.error(e.getMessage(), e);
+            throw new RuntimeException("将成员加入Group时发生异常[" + e.getMessage() + "]");
+        }
+        groupMemberMapper.insert(groupId, invitedUserId);
+        Group group = groupMapper.findWithMembersById(groupId);
+        return new GroupVO(group);
+    }
+
+    @Override
+    public void quitGroup(Integer groupId, Integer userId) throws ServiceException {
+
+        try {
+            gitlabApi.quitGroup(groupId,userId);
+        } catch (GitLabApiException e) {
+            log.error(e.getMessage(), e);
+            throw new ServiceException(e.getMessage());
+        }
+        groupMemberMapper.delete(groupId,userId);
+
+    }
+}
+

+ 86 - 84
web/src/main/java/seecoder/devcloud/web/service/impl/user/MailServiceImpl.java

@@ -1,84 +1,86 @@
-//package seecoder.devcloud.web.service.impl.user;
-//
-//
-//import lombok.extern.apachecommons.CommonsLog;
-//import org.apache.commons.lang3.StringUtils;
-//import org.springframework.beans.factory.annotation.Autowired;
-//
-//import org.springframework.mail.MailException;
-//import org.springframework.mail.SimpleMailMessage;
-//import org.springframework.mail.javamail.JavaMailSender;
-//import org.springframework.mail.javamail.MimeMessageHelper;
-//import org.springframework.scheduling.annotation.Async;
-//import org.springframework.stereotype.Service;
-//import org.springframework.web.servlet.ModelAndView;
-//import org.thymeleaf.TemplateEngine;
-//import org.thymeleaf.context.Context;
-//import seecoder.devcloud.api.ApplicationProperties;
-//import seecoder.devcloud.web.service.user.MailService;
-//
-//import javax.mail.MessagingException;
-//import javax.mail.internet.InternetAddress;
-//import javax.mail.internet.MimeMessage;
-//import java.io.UnsupportedEncodingException;
-//import java.util.Locale;
-//
-//@Service
-//@CommonsLog
-//public class MailServiceImpl implements MailService {
-//	private final TemplateEngine templateEngine;
-//	private final JavaMailSender mailSender;
-//
-//	private final String from;
-//	private final String personal;
-//
-//	@Autowired
-//	public MailServiceImpl(TemplateEngine templateEngine, JavaMailSender mailSender, ApplicationProperties properties) {
-//		this.templateEngine = templateEngine;
-//		this.mailSender = mailSender;
-//
-//		ApplicationProperties.Mail mail = properties.getMail();
-//		this.from = mail.getFrom();
-//		this.personal = mail.getPersonal();
-//	}
-//
-//	@Override
-//	@Async
-//	public void send(String to, String personal, String subject, ModelAndView modelAndView) {
-//		if (StringUtils.isBlank(from)) return;
-//		MimeMessage message = mailSender.createMimeMessage();
-//		MimeMessageHelper helper = new MimeMessageHelper(message);
-//		try {
-//			try {
-//				helper.setFrom(from, this.personal);
-//			} catch (UnsupportedEncodingException e) {
-//				helper.setFrom(from);
-//			}
-//			try {
-//				helper.setTo(new InternetAddress(to, personal));
-//			} catch (UnsupportedEncodingException e) {
-//				helper.setTo(to);
-//			}
-//			helper.setSubject(subject);
-//			helper.setText(templateEngine.process(modelAndView.getViewName(), new Context(Locale.CHINA, modelAndView.getModel())), true);
-//		} catch (MessagingException e) {
-//			log.warn("生成发送给[" + to + "(" + personal + ")]的邮件[" + subject + "]时发生异常:" + e.getMessage());
-//			return;
-//		}
-//		try {
-//			mailSender.send(message);
-//		} catch (MailException e) {
-//			log.warn("投递发送给[" + to + "(" + personal + ")]的邮件[" + subject + "]时发生异常:" + e.getMessage());
-//		}
-//	}
-//
-//	@Async
-//	public void send(String text, String subject,String... to) {
-//		SimpleMailMessage message = new SimpleMailMessage();
-//		message.setFrom(this.from);
-//		message.setTo(to);
-//		message.setSubject(subject);
-//		message.setText(text);
-//		mailSender.send(message);
-//	}
-//}
+package seecoder.devcloud.web.service.impl.user;
+
+
+import lombok.extern.apachecommons.CommonsLog;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import org.springframework.mail.MailException;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.web.servlet.ModelAndView;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.context.Context;
+import seecoder.devcloud.api.ApplicationProperties;
+import seecoder.devcloud.web.service.user.MailService;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import java.io.UnsupportedEncodingException;
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+
+@Service
+@CommonsLog
+public class MailServiceImpl implements MailService {
+	private final TemplateEngine templateEngine;
+	private final JavaMailSender mailSender;
+
+	private final String from;
+	private final String personal;
+
+	@Autowired
+	public MailServiceImpl(TemplateEngine templateEngine, JavaMailSender mailSender, ApplicationProperties properties) {
+		this.templateEngine = templateEngine;
+		this.mailSender = mailSender;
+
+		ApplicationProperties.Mail mail = properties.getMail();
+		this.from = mail.getFrom();
+		this.personal = mail.getPersonal();
+	}
+
+	@Override
+	@Async
+	public void send(String to, String personal, String subject, ModelAndView modelAndView) {
+		if (StringUtils.isBlank(from)) return;
+		MimeMessage message = mailSender.createMimeMessage();
+		MimeMessageHelper helper = new MimeMessageHelper(message);
+		try {
+			try {
+				helper.setFrom(from, this.personal);
+			} catch (UnsupportedEncodingException e) {
+				helper.setFrom(from);
+			}
+			try {
+				helper.setTo(new InternetAddress(to, personal));
+			} catch (UnsupportedEncodingException e) {
+				helper.setTo(to);
+			}
+			helper.setSubject(subject);
+			helper.setText(templateEngine.process(modelAndView.getViewName(), new Context(Locale.CHINA, modelAndView.getModel())), true);
+		} catch (MessagingException e) {
+			log.warn("生成发送给[" + to + "(" + personal + ")]的邮件[" + subject + "]时发生异常:" + e.getMessage());
+			return;
+		}
+		try {
+			mailSender.send(message);
+		} catch (MailException e) {
+			log.warn("投递发送给[" + to + "(" + personal + ")]的邮件[" + subject + "]时发生异常:" + e.getMessage());
+		}
+	}
+
+	@Async
+	public CompletableFuture<Void> send(String text, String subject, String... to) {
+		SimpleMailMessage message = new SimpleMailMessage();
+		message.setFrom(this.from);
+		message.setTo(to);
+		message.setSubject(subject);
+		message.setText(text);
+		mailSender.send(message);
+		return null;
+	}
+}

+ 155 - 232
web/src/main/java/seecoder/devcloud/web/service/impl/user/UserServiceImpl.java

@@ -1,232 +1,155 @@
-//package seecoder.devcloud.web.service.impl.user;
-//
-//
-//import org.apache.commons.codec.digest.DigestUtils;
-//import org.gitlab4j.api.GitLabApiException;
-//import org.springframework.beans.BeanUtils;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.data.domain.Page;
-//import org.springframework.data.domain.PageRequest;
-//import org.springframework.data.domain.Pageable;
-//import org.springframework.data.domain.Sort;
-//import org.springframework.stereotype.Service;
-//import org.springframework.transaction.annotation.Transactional;
-//import org.springframework.util.Assert;
-//import seecoder.devcloud.api.gitlab.GitlabApi;
-//import seecoder.devcloud.api.gitlab.model.GitlabUser;
-//import seecoder.devcloud.api.jenkins.JenkinsApi;
-//import seecoder.devcloud.common.exceptions.Asserts;
-//import seecoder.devcloud.common.exceptions.ServiceException;
-//import seecoder.devcloud.common.util.ToolKit;
-//import seecoder.devcloud.web.service.user.MailService;
-//
-//@Service
-//public class UserServiceImpl implements UserService {
-//	private static final Sort PAGE_SORT = Sort.by(Sort.Direction.ASC, "id");
-//
-//	private final UserDAO userDAO;
-//	private final MailService mailService;
-//	private final GitlabApi gitlabApi;
-//	private final JenkinsApi jenkinsApi;
-//
-//	@Autowired
-//	public UserServiceImpl(UserDAO userDAO, MailService mailService, GitlabApi gitlabApi, JenkinsApi jenkinsApi) {
-//		this.userDAO = userDAO;
-//		this.mailService = mailService;
-//		this.gitlabApi = gitlabApi;
-//		this.jenkinsApi = jenkinsApi;
-//	}
-//
-//	@Override
-//	@Transactional
-//	public UserVO create(UserDTO userDTO) throws ServiceException {
-//		return register(userDTO.getUsername(), userDTO.getEmail(), null,userDTO.getNickname(), userDTO.getRole());
-//	}
-//
-//	@Override
-//	@Transactional
-//	public User register(String username, String name, String email, String password, String phone, UserIdentity role, Integer pid) throws ServiceException {
-//		User user = userDAO.findByUsername(username);
-//		Asserts.isNull(user, "用户名已被使用!");
-//		user = userDAO.findByEmail(email);
-//		Asserts.isNull(user, "邮箱已被使用!");
-//		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);
-//		if(name!=null){
-//			user.setNickname(name);
-//		}
-//
-//		user.setUsername(username);
-//		user.setEmail(email);
-//		user.setPassword(DigestUtils.sha256Hex(realPassword));
-//		user.setPid(pid);
-//		user.setPhone(phone);
-//		user.setRole(role);
-//		user = userDAO.save(user);
-//		if (password == null) {
-//			mailService.send("尊敬的" + username + "您好, 您的初始密码为" + realPassword + ", 请及时修改", "注册成功 | SEECODER", email);
-//		}
-//		return user;
-//	}
-//
-//	@Override
-//	public Page<UserVO> retrievePage(int page, int limit, String search) {
-//		Pageable pageable = PageRequest.of(page, limit, PAGE_SORT);
-//		Page<User> pageResult;
-//		if (search == null || search.isEmpty()) {
-//			pageResult = userDAO.findAll(pageable);
-//		} else {
-//			pageResult = userDAO.findAllByUsernameLike("%" + search + "%", pageable);
-//		}
-//		return pageResult.map(UserVO::new);
-//	}
-//
-//	@Override
-//	public UserVO retrieve(int userId) throws ServiceException {
-//		User user = userDAO.findById(userId);
-//		Asserts.notNull(user, "所选用户不存在");
-//		return new UserVO(user);
-//	}
-//
-//	@Override
-//	@Transactional
-//	public void delete(int userId) throws ServiceException {
-//		User user = userDAO.findById(userId);
-//		Assert.notNull(user, "找不到要删除的用户");
-//		Assert.isTrue(user.getRole() == UserIdentity.STUDENT, "目标用户只能是学生");
-//		try {
-//			gitlabApi.deleteUser(userId);
-//		} catch (Exception e) {
-//			throw new ServiceException("删除GitLab用户时发生异常[" + e.getMessage() + "]");
-//		}
-//		for (Result result : user.getResultSet()) {
-//			if (!result.isDeleted()) {
-//				try {
-//					jenkinsApi.deleteJob(result.getId());
-//				} catch (Exception e) {
-//					throw new ServiceException("删除Jenkins项目时发生异常[" + e.getMessage() + "]");
-//				}
-//			}
-//		}
-//		userDAO.delete(user);
-//	}
-//
-//	@Override
-//	@Transactional
-//	public UserSerializer register(RegisterForm form) throws ServiceException {
-//		return new UserSerializer(register(form.getUsername(), form.getEmail(), form.getPassword(),form.getNickname(), UserIdentity.STUDENT));
-//	}
-//	@Override
-//	@Transactional
-//	public UserSerializer registerTeacher(RegisterForm form) throws ServiceException {
-//		return new UserSerializer(register(form.getUsername(), form.getEmail(), form.getPassword(),form.getNickname(), UserIdentity.TEACHER));
-//	}
-//	private UserVO register(String username, String email, String password, String nickname,UserIdentity role) throws ServiceException {
-//		User user = userDAO.findByUsername(username);
-//		Asserts.isNull(user, "用户名已被使用!");
-//		user = userDAO.findByEmail(email);
-//		Asserts.isNull(user, "邮箱已被使用!");
-//		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);
-//		if(nickname!=null){
-//			user.setNickname(nickname);
-//		}
-//
-//		user.setUsername(username);
-//		user.setEmail(email);
-//		user.setPassword(DigestUtils.sha256Hex(realPassword));
-//		user.setRole(role);
-//		final UserVO userVO = new UserVO(userDAO.save(user));
-//		if (password == null) {
-//			mailService.send("尊敬的" + username + "您好, 您的初始密码为" + realPassword + ", 请及时修改", "注册成功 | SEECODER", email);
-//		}
-//		return userVO;
-//	}
-//
-//
-//	@Override
-//	public UserSerializer getUserById(Integer userid) {
-//		return new UserSerializer(userDAO.getOne(userid));
-//	}
-//
-//	@Override
-//	public UserSerializer changeUserInfo(Integer userid,String nickName, String bio) throws ServiceException {
-//		User one = userDAO.findById(userid).orElse(null);
-//		if (one == null) {
-//			throw new ServiceException("用户不存在");
-//		}
-//		if(nickName!=null){
-//			one.setNickname(nickName);
-//		}
-//		if(bio!=null){
-//			one.setBio(bio);
-//		}
-//		return new UserSerializer(userDAO.save(one));
-//	}
-//
-//	@Override
-//	public UserSerializer changeUserPassword(Integer userid,String oldPassword, String newPassword) throws ServiceException {
-//		User one = userDAO.findById(userid).orElse(null);
-//		if (one == null) {
-//			throw new ServiceException("用户不存在");
-//		}
-//		if(!one.getPassword().equals(DigestUtils.sha256Hex(oldPassword))){
-//			throw new ServiceException("原密码错误");
-//		}else{
-//			one.setPassword(DigestUtils.sha256Hex(newPassword));
-//		}
-//		try {
-//			gitlabApi.changePassword(userid, newPassword);
-//		} catch (GitLabApiException e) {
-//			throw new ServiceException("修改GitLab用户密码时发生异常[" + e.getMessage() + "]");
-//		}
-//		mailService.send( "您的新密码为"+newPassword,"密码变更提醒 | SEECODER",one.getEmail());
-//		return new UserSerializer(userDAO.save(one));
-//	}
-//
-//	@Override
-//	public void changePassword(int userId, ChangePasswordForm form) throws ServiceException {
-//		User user = userDAO.findById(userId);
-//		if (!user.getPassword().equals(DigestUtils.sha256Hex(form.getOrigin()))) {
-//			throw new InvalidRequestException("原密码验证失败,如忘记密码请联系管理员重置");
-//		}
-//		changePassword(user, form.getPassword(), false);
-//	}
-//
-//	@Override
-//	public void resetPassword(int userId) throws ServiceException {
-//		User user = userDAO.findById(userId);
-//		Asserts.notNull(user, "所选用户不存在");
-//		String password = ToolKit.randomPassword();
-//		changePassword(user, password, true);
-//	}
-//
-//
-//
-//	private void changePassword(User user, String password, boolean reset) throws ServiceException {
-//		try {
-//			gitlabApi.changePassword(user.getId(), password);
-//		} catch (GitLabApiException e) {
-//			throw new ServiceException("修改GitLab用户密码时发生异常[" + e.getMessage() + "]");
-//		}
-//		user.setPassword(DigestUtils.sha256Hex(password));
-//		user = userDAO.save(user);
-//		if (reset) {
-//			mailService.send( "您的新密码为"+password,"密码变更提醒 | SEECODER",user.getEmail());
-//		}
-//	}
-//}
+package seecoder.devcloud.web.service.impl.user;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.gitlab4j.api.GitLabApiException;
+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.transaction.annotation.Transactional;
+import org.springframework.util.Assert;
+import seecoder.devcloud.api.gitlab.GitlabApi;
+import seecoder.devcloud.api.gitlab.model.GitlabUser;
+import seecoder.devcloud.api.jenkins.JenkinsApi;
+import seecoder.devcloud.common.exceptions.Asserts;
+import seecoder.devcloud.common.exceptions.InvalidRequestException;
+import seecoder.devcloud.common.exceptions.ServiceException;
+import seecoder.devcloud.common.util.ToolKit;
+import seecoder.devcloud.web.dao.user.UserMapper;
+import seecoder.devcloud.web.vo.user.ChangePasswordVO;
+import seecoder.devcloud.web.vo.user.RegisterVO;
+
+import seecoder.devcloud.web.enums.UserIdentity;
+import seecoder.devcloud.web.po.user.User;
+import seecoder.devcloud.web.vo.user.UserVO;
+import seecoder.devcloud.web.service.user.MailService;
+import seecoder.devcloud.web.service.user.UserService;
+
+@Service
+@Slf4j
+public class UserServiceImpl implements UserService {
+	private static final Sort PAGE_SORT = Sort.by(Sort.Direction.ASC, "id");
+
+	private final UserMapper userMapper;
+	private final MailService mailService;
+	private final GitlabApi gitlabApi;
+	private final JenkinsApi jenkinsApi;
+
+	@Autowired
+	public UserServiceImpl(UserMapper userMapper, MailService mailService, GitlabApi gitlabApi, JenkinsApi jenkinsApi) {
+		this.userMapper = userMapper;
+		this.mailService = mailService;
+		this.gitlabApi = gitlabApi;
+		this.jenkinsApi = jenkinsApi;
+	}
+
+	@Override
+	@Transactional
+	public UserVO register(RegisterVO form) throws ServiceException {
+		return register(form.getUsername(), form.getEmail(), form.getPassword(),form.getNickname(), UserIdentity.STUDENT);
+	}
+
+	@Override
+	@Transactional
+	public UserVO registerTeacher(RegisterVO form) throws ServiceException {
+		return register(form.getUsername(), form.getEmail(), form.getPassword(),form.getNickname(), UserIdentity.TEACHER);
+	}
+
+	private UserVO register(String username, String email, String password, String nickname,UserIdentity role) throws ServiceException {
+		User user = userMapper.findByUsername(username);
+		Asserts.isNull(user, "用户名已被使用!");
+		user = userMapper.findByEmail(email);
+		Asserts.isNull(user, "邮箱已被使用!");
+		String realPassword = password == null ? ToolKit.randomPassword() : password;
+		GitlabUser gitlabUser;
+		try {
+			gitlabUser = gitlabApi.createUser(username, email, realPassword);
+		} catch (GitLabApiException e) {
+			log.error(e.getMessage(), e);
+			throw new ServiceException("创建GitLab用户时发生异常[" + e.getMessage() + "]");
+		}
+		user = new User();
+		BeanUtils.copyProperties(gitlabUser, user);
+		if(nickname!=null){
+			user.setNickname(nickname);
+		}
+
+		user.setUsername(username);
+		user.setEmail(email);
+		//在gitlab注册后,直接加密
+		user.setPassword(DigestUtils.sha256Hex(realPassword));
+		user.setRole(role);
+		userMapper.insert(user, "created_at");
+		final UserVO userVO = new UserVO(user);
+		if (password == null) {
+			mailService.send("尊敬的" + username + "您好, 您的初始密码为" + realPassword + ", 请及时修改", "注册成功 | SEECODER", email);
+		}
+		return userVO;
+	}
+
+	@Override
+	@Transactional
+	public void delete(int userId) throws ServiceException {
+		User user = userMapper.findById(userId);
+		Assert.notNull(user, "找不到要删除的用户");
+		Assert.isTrue(user.getRole() == UserIdentity.STUDENT, "目标用户只能是学生");
+		try {
+			gitlabApi.deleteUser(userId);
+		} catch (Exception e) {
+			log.error(e.getMessage(), e);
+			throw new ServiceException("删除GitLab用户时发生异常[" + e.getMessage() + "]");
+		}
+		//todo 删除用户需要连带删除什么
+		userMapper.delete(user);
+	}
+
+	@Override
+	public UserVO getUserById(Integer userid) {
+		return new UserVO(userMapper.findById(userid));
+	}
+
+	@Override
+	public UserVO changeUserInfo(Integer userid, String nickName) throws ServiceException {
+		User one = userMapper.findById(userid);
+		if (one == null) {
+			throw new ServiceException("用户不存在");
+		}
+		one.setNickname(nickName);
+		userMapper.updateById(one);
+		return new UserVO(one);
+	}
+
+	@Override
+	public void changePassword(ChangePasswordVO form) throws ServiceException {
+		User user = userMapper.findById(form.getUserId());
+
+		if (!user.getPassword().equals(DigestUtils.sha256Hex(form.getOrigin()))) {
+			throw new InvalidRequestException("原密码验证失败,如忘记密码请联系管理员重置");
+		}
+		changePassword(user, form.getPassword(), false);
+	}
+
+	private void changePassword(User user, String password, boolean reset) throws ServiceException {
+		try {
+			/**
+			 * todo 修改密码容易导致gitlab与devcloud不一致
+			 * 管理员每次修改密码,会导致下次普通用户登陆gitlab的时候强制重新改密码
+			 * 如果这时候学生在gitlab那边改的密码与这里改的不一样,就产生了密码不一致的结果
+			 * 而且目前官方gitlab client api不支持普通用户通过api改密码
+			 * 见 https://docs.gitlab.com/ce/api/users.html
+			 * 所以这个bug暂时无解,最好是让学生建立用户完毕之后不要乱改密码
+			 */
+			gitlabApi.changePassword(user.getId(), password);
+		} catch (GitLabApiException e) {
+			log.error(e.getMessage(), e);
+			throw new ServiceException("修改GitLab用户密码时发生异常[" + e.getMessage() + "]");
+		}
+		user.setPassword(DigestUtils.sha256Hex(password));
+		userMapper.updateById(user);
+		if (reset) {
+			mailService.send( "您的新密码为"+password,"密码变更提醒 | SEECODER",user.getEmail());
+		}
+	}
+}

+ 30 - 0
web/src/main/java/seecoder/devcloud/web/service/user/GroupService.java

@@ -0,0 +1,30 @@
+package seecoder.devcloud.web.service.user;
+
+
+
+import org.springframework.stereotype.Service;
+import seecoder.devcloud.common.exceptions.EntityNotFoundException;
+import seecoder.devcloud.common.exceptions.ServiceException;
+import seecoder.devcloud.web.vo.user.CustomUserDetails;
+import seecoder.devcloud.web.vo.user.GroupVO;
+import seecoder.devcloud.web.vo.user.InnerGroupVO;
+
+import java.util.List;
+
+/**
+ * @InterfaceName GroupService
+ * @PackageName com.moekr.moocoder.logic.service.impl
+ * @Author sheen
+ * @Date 2019/1/9
+ * @Version 1.0
+ * @Description
+ **/
+@Service
+public interface GroupService {
+
+    GroupVO createGroup(String name,  Integer userId) throws ServiceException;
+
+    GroupVO addMember(Integer userId, Integer invitedUserId, Integer groupId)  throws EntityNotFoundException;
+
+    void quitGroup(Integer groupId, Integer userId) throws ServiceException;
+}

+ 11 - 1
web/src/main/java/seecoder/devcloud/web/service/user/MailService.java

@@ -2,7 +2,17 @@ package seecoder.devcloud.web.service.user;
 
 import org.springframework.web.servlet.ModelAndView;
 
+import java.util.concurrent.CompletableFuture;
+
 public interface MailService {
+
+	@Deprecated
 	void send(String to, String personal, String subject, ModelAndView modelAndView);
-	void send(String text, String subject, String ...to);
+
+	/**
+	 * @param text 正文
+	 * @param subject 标题
+	 * @param to 收件人
+	 */
+	CompletableFuture<Void> send(String text, String subject, String ...to);
 }

+ 28 - 37
web/src/main/java/seecoder/devcloud/web/service/user/UserService.java

@@ -1,37 +1,28 @@
-//package seecoder.devcloud.web.service.user;
-//
-//import com.moekr.moocoder.data.entity.user.User;
-//import com.moekr.moocoder.logic.Serializer.UserSerializer;
-//import com.moekr.moocoder.logic.vo.user.UserVO;
-//import com.moekr.moocoder.util.enums.UserIdentity;
-//import com.moekr.moocoder.util.exceptions.ServiceException;
-//import com.moekr.moocoder.web.dto.form.ChangePasswordForm;
-//import com.moekr.moocoder.web.dto.form.RegisterForm;
-//import com.moekr.moocoder.web.dto.user.UserDTO;
-//import org.springframework.data.domain.Page;
-//
-//public interface UserService {
-//	UserVO create(UserDTO userDTO) throws ServiceException;
-//
-//	User register(String username, String name, String email, String password, String phone, UserIdentity role, Integer pid) throws ServiceException;
-//
-//	Page<UserVO> retrievePage(int page, int limit, String search) throws ServiceException;
-//
-//	UserVO retrieve(int userId) throws ServiceException;
-//
-//	void delete(int userId) throws ServiceException;
-//
-//	UserSerializer register(RegisterForm form) throws ServiceException;
-//
-//	UserSerializer registerTeacher(RegisterForm form) throws ServiceException;
-//
-//	void changePassword(int userId, ChangePasswordForm form) throws ServiceException;
-//
-//	void resetPassword(int userId) throws ServiceException;
-//
-//	UserSerializer getUserById(Integer userid);
-//
-//	UserSerializer changeUserInfo(Integer userid,String nickName, String bio) throws ServiceException;
-//
-//	UserSerializer changeUserPassword(Integer userid,String oldPassword, String newPassword) throws ServiceException;
-//}
+package seecoder.devcloud.web.service.user;
+
+
+import seecoder.devcloud.common.exceptions.ServiceException;
+
+import seecoder.devcloud.web.vo.user.ChangePasswordVO;
+import seecoder.devcloud.web.vo.user.RegisterVO;
+import seecoder.devcloud.web.vo.user.UserVO;
+
+public interface UserService {
+
+	UserVO register(RegisterVO form) throws ServiceException;
+
+	/**
+	 * 暂时没这个需求,先保留
+	 */
+	@Deprecated
+	UserVO registerTeacher(RegisterVO form) throws ServiceException;
+
+	void delete(int userId) throws ServiceException;
+
+	UserVO getUserById(Integer userid);
+
+	UserVO changeUserInfo(Integer userid, String nickName) throws ServiceException;
+
+	void changePassword(ChangePasswordVO form) throws ServiceException;
+
+}

+ 10 - 10
web/src/main/java/seecoder/devcloud/web/vo/Response.java

@@ -47,24 +47,24 @@ public class Response<T> implements Serializable {
         return new Response<T>(ResponseEnums.OK,data);
     }
 
-    public static Response notFound() {
-        return new Response(ResponseEnums.NOT_FOUND);
+    public static Response notFound(String message) {
+        return new Response(ResponseEnums.NOT_FOUND, message);
     }
 
-    public static Response badRequest() {
-        return new Response(ResponseEnums.BAD_REQUEST);
+    public static Response badRequest(String message) {
+        return new Response(ResponseEnums.BAD_REQUEST, message);
     }
 
-    public static Response forbidden() {
-        return new Response(ResponseEnums.FORBIDDEN);
+    public static Response forbidden(String message) {
+        return new Response(ResponseEnums.FORBIDDEN, message);
     }
 
-    public static Response unauthorized() {
-        return new Response(ResponseEnums.UNAUTHORIZED);
+    public static Response unauthorized(String message) {
+        return new Response(ResponseEnums.UNAUTHORIZED, message);
     }
 
-    public static Response serverInternalError() {
-        return new Response(ResponseEnums.SERVER_INTERNAL_ERROR);
+    public static Response serverInternalError(String message) {
+        return new Response(ResponseEnums.SERVER_INTERNAL_ERROR, message);
     }
 
 }

+ 25 - 0
web/src/main/java/seecoder/devcloud/web/vo/user/ChangePasswordVO.java

@@ -0,0 +1,25 @@
+package seecoder.devcloud.web.vo.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import seecoder.devcloud.common.validate.FieldEquals;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+@Data
+@FieldEquals(firstField = "password", secondField = "confirm", message = "两次输入的新密码不一致!")
+@AllArgsConstructor
+public class ChangePasswordVO {
+
+	private Integer userId;
+	@NotNull(message = "请填写原密码!")
+	private String origin;
+	@Pattern(regexp = "[0-9a-zA-Z_]{8,16}", message = "密码只能包含大小写字母、数字和下划线,且必须为8-16位!")
+	@NotNull(message = "请填写新密码!")
+	private String password;
+	/**
+	 * 新密码要输入两遍,做确认
+	 */
+	private String confirm;
+}

+ 85 - 0
web/src/main/java/seecoder/devcloud/web/vo/user/CustomUserDetails.java

@@ -0,0 +1,85 @@
+package seecoder.devcloud.web.vo.user;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import lombok.*;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import seecoder.devcloud.web.po.user.User;
+
+import java.util.Collections;
+import java.util.Set;
+
+import static seecoder.devcloud.web.enums.UserIdentity.STUDENT;
+import static seecoder.devcloud.web.enums.UserIdentity.TEACHER;
+import static seecoder.devcloud.web.infrastructure.security.WebSecurityConstants.*;
+
+
+@Data
+@EqualsAndHashCode
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CustomUserDetails implements UserDetails {
+	private Integer id;
+	private String username;
+	private String password;
+	private Set<GrantedAuthority> authorities;
+
+	CustomUserDetails(User user) {
+		BeanUtils.copyProperties(user, this);
+		switch (user.getRole()) {
+			case STUDENT:
+				this.authorities = Collections.singleton(STUDENT_AUTHORITY);
+				break;
+			case TEACHER:
+				this.authorities = Collections.singleton(TEACHER_AUTHORITY);
+				break;
+			default:
+				throw new UsernameNotFoundException(username);
+		}
+	}
+
+	CustomUserDetails(String username, String password) {
+		this.id = 0;
+		this.username = username;
+		this.password = DigestUtils.sha256Hex(password);
+		this.authorities = Collections.singleton(ADMIN_AUTHORITY);
+	}
+
+	@Override
+	public boolean isAccountNonExpired() {
+		return true;
+	}
+
+	@Override
+	public boolean isAccountNonLocked() {
+		return true;
+	}
+
+	@Override
+	public boolean isCredentialsNonExpired() {
+		return true;
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return true;
+	}
+
+	public boolean isStudent() {
+		return authorities.contains(STUDENT_AUTHORITY);
+	}
+
+	public boolean isTeacher() {
+		return authorities.contains(TEACHER_AUTHORITY);
+	}
+
+	public boolean isAdmin() {
+		return authorities.contains(ADMIN_AUTHORITY);
+	}
+}

+ 24 - 0
web/src/main/java/seecoder/devcloud/web/vo/user/GroupDTO.java

@@ -0,0 +1,24 @@
+package seecoder.devcloud.web.vo.user;
+
+import lombok.Data;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @ClassName GroupDTO
+ * @PackageName com.moekr.moocoder.web.dto
+ * @Author sheen
+ * @Date 2019/1/9
+ * @Version 1.0
+ * @Description //TODO
+ **/
+@Data
+public class GroupDTO {
+    private Integer id;
+    @NotNull(message = "小组名称不能为空!", groups = {PostMapping.class, PutMapping.class})
+    private String name;
+    @NotNull(message = "课程ID不能为空!", groups = {PostMapping.class, PutMapping.class})
+    private Integer courseId;
+}

+ 37 - 0
web/src/main/java/seecoder/devcloud/web/vo/user/GroupVO.java

@@ -0,0 +1,37 @@
+package seecoder.devcloud.web.vo.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import seecoder.devcloud.web.po.user.Group;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @EnumName GroupVO
+ * @PackageName com.moekr.moocoder.logic.Serializer
+ * @Author sheen
+ * @Date 2019/1/9
+ * @Version 1.0
+ * @Description //TODO
+ **/
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class GroupVO {
+    private Integer id;
+    private String name;
+    private String code;
+    private List<UserVO> members;
+
+    public GroupVO(Group group) {
+        this.id=group.getId();
+        this.name=group.getName();
+        this.code=group.getShareLink();
+        this.members=group.getMembers().stream()
+                .map(UserVO::new)
+                .collect(Collectors.toList());
+    }
+
+}

+ 31 - 0
web/src/main/java/seecoder/devcloud/web/vo/user/InnerGroupVO.java

@@ -0,0 +1,31 @@
+package seecoder.devcloud.web.vo.user;
+
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import seecoder.devcloud.web.po.user.Group;
+import seecoder.devcloud.web.po.user.User;
+
+import java.util.List;
+
+/**
+ * 暂定为系统内部给作业部分使用的VO
+ */
+@Data
+@EqualsAndHashCode(exclude = {"members"})
+@ToString(exclude = {"members"})
+public class InnerGroupVO {
+    private Integer id;
+    private Integer namespace;
+    private String name;
+    private List<User> members;
+
+
+    public InnerGroupVO(Group group) {
+        this.id = group.getId();
+        this.namespace = group.getNamespace();
+        this.name = group.getName();
+        this.members = group.getMembers();
+    }
+}

+ 49 - 0
web/src/main/java/seecoder/devcloud/web/vo/user/RegisterVO.java

@@ -0,0 +1,49 @@
+package seecoder.devcloud.web.vo.user;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class RegisterVO {
+	//@Email(message = "用户名也要填邮箱名称!")
+	@NotNull(groups = {PostMapping.class},message = "请填写用户名!")
+	private String username="default";
+	@Email(message = "邮箱格式不正确!")
+//	@NotNull(message = "请填写邮箱!")
+	@JsonIgnore
+	private String email;
+	@Pattern(groups = {PostMapping.class},regexp = "[0-9a-zA-Z_]{8,16}", message = "密码只能包含大小写字母、数字和下划线,且必须为8-16位!")
+	@NotNull(groups = {PostMapping.class},message = "请填写密码!")
+	private String password;
+	@Pattern(regexp = "^\\w+${4,23}", message = "昵称只能包含大小写字母、数字和下划线,且必须为4-23位!")
+	private String nickname="nickname";
+
+	public RegisterVO(@Email(message = "邮箱格式不正确!") @NotNull(message = "请填写邮箱!") String username, @Pattern(regexp = "[0-9a-zA-Z_]{8,16}", message = "密码只能包含大小写字母、数字和下划线,且必须为8-16位!") @NotNull(message = "请填写密码!") String password, String nickname) {
+		this.email = username;
+		this.username=buildUsername(username);
+		this.password = password;
+		this.nickname = nickname;
+	}
+	public void buildUsername(){
+		this.username=buildUsername(this.email);
+	}
+	public static String buildUsername(@Email(groups = {PostMapping.class},message = "邮箱格式不正确!") @NotNull(groups = {PostMapping.class},message = "请填写用户名!")String email){
+		return email.replace("@","_").replaceAll("\\.","_");
+	}
+
+	public void setUsername(String username) {
+		this.email = username;
+		this.username=buildUsername(username);
+	}
+}

+ 24 - 0
web/src/main/java/seecoder/devcloud/web/vo/user/UserDTO.java

@@ -0,0 +1,24 @@
+package seecoder.devcloud.web.vo.user;
+
+import lombok.Data;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import seecoder.devcloud.web.enums.UserIdentity;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+@Data
+public class UserDTO {
+	@Pattern(regexp = "[0-9a-zA-Z_]{4,16}", message = "用户名只能包含大小写字母、数字和下划线,且必须为4-16位!", groups = PostMapping.class)
+	@NotNull(message = "请填写用户名!", groups = PostMapping.class)
+	private String username;
+	@Email(message = "邮箱格式不正确!", groups = {PostMapping.class, PutMapping.class})
+	@NotNull(message = "请填写邮箱!", groups = {PostMapping.class, PutMapping.class})
+	private String email;
+//	@NotNull(message = "请选择用户角色!", groups = PostMapping.class)
+	private UserIdentity role;
+	//string | null
+	private String nickname;
+}

+ 32 - 0
web/src/main/java/seecoder/devcloud/web/vo/user/UserVO.java

@@ -0,0 +1,32 @@
+package seecoder.devcloud.web.vo.user;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import seecoder.devcloud.common.serializer.TimestampLocalDateTimeSerializer;
+import seecoder.devcloud.web.enums.UserIdentity;
+import seecoder.devcloud.web.po.user.User;
+
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserVO {
+    private Integer id;
+    private UserIdentity role;
+    private String username;
+    private String nickname;
+    private String email;
+    private String phone;
+    @JsonProperty("created_at")
+    private Timestamp createdAt;
+
+    public UserVO(User user) {
+        BeanUtils.copyProperties(user,this);
+    }
+}

+ 10 - 0
web/src/main/resources/SQLscripts/group.sql

@@ -0,0 +1,10 @@
+create table if not exists devcloud.`group`
+(
+    id         int                      not null
+        primary key,
+    name       varchar(23) charset utf8 not null,
+    namespace  int                      not null,
+    share_link varchar(63)              not null,
+    type       varchar(15) charset utf8 null
+);
+

+ 11 - 0
web/src/main/resources/SQLscripts/group_member.sql

@@ -0,0 +1,11 @@
+create table if not exists devcloud.group_member
+(
+    group_id int not null,
+    user_id  int not null,
+    constraint group_member_group_id_fk
+        foreign key (group_id) references devcloud.`group` (id)
+            on delete cascade,
+    constraint group_member_user_id_fk
+        foreign key (user_id) references devcloud.user (id)
+            on delete cascade
+);

+ 14 - 0
web/src/main/resources/SQLscripts/user.sql

@@ -0,0 +1,14 @@
+create table if not exists devcloud.user
+(
+    id         int                                 not null
+        primary key,
+    username   varchar(23)                         not null,
+    nickname   varchar(23)                         null,
+    password   char(64)                            not null,
+    phone      varchar(23)                         null,
+    email      varchar(63)                         not null,
+    role       varchar(15) charset utf8            null,
+    created_at timestamp default CURRENT_TIMESTAMP null,
+    namespace  int                                 null,
+    token      varchar(63)                         not null
+);

+ 55 - 0
web/src/main/resources/application-cyz.yml

@@ -0,0 +1,55 @@
+spring:
+  datasource:
+    url: jdbc:mysql://localhost:3306/seecoder?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
+    username: root
+    password: 123456
+
+  mail:
+    host: smtp.qq.com
+    username: 370774330@qq.com
+    password: qkoecnzivukvbibi
+    port: 465
+    protocol: smtp
+    properties:
+      mail:
+        smtp:
+          ssl:
+            enable: true
+          socketFactory.class: javax.net.ssl.SSLSocketFactory
+        debug: true
+server:
+  port: 8080
+
+mybatis:
+  configuration:
+    map-underscore-to-camel-case: true
+
+seecoder:
+  k8s:
+    api-server: https://192.168.99.105:8443
+    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IjBmSXc4RlRjNUpBYVpGYWc4QzRDY1MzUWJad1JMdFhKcDI1cngzWGtiRlkifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXZjbG91ZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLXNicXY4Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5NmIxNzU1ZS0zZjE3LTQ1ZTctODFmYi01MTRhODA3NDRhMzQiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGV2Y2xvdWQ6ZGVmYXVsdCJ9.W0Y1lR6LBi1ZjylBx35YeqYuq2u2AqH-ayK4FD-LDfC26E89DanOwSv9XLrRMnnDBZFS_ZCRBumbnDeMiS5vw2x7-QnrDqj1M5u9FB2fIJsz2LyUkOhATZZmTzj_Uhewvvg09aDKh16ttj6ZX27m-KVk_6MP-VfiuvZnCVxDbqRkRjK-zsor4ty1CEATPyKK-tvugAwS0RFNHUL94DamfZAq_t1PSnH5ZtEHq0uEMZxY2c7j6u9d_aD9lphm_ZpCy_C8OCHfanM21CQPDHlcJTW_aiOO6bAp8qdc1egPO5uIamHl6Zm0N3RkcsRp4DGOHrltTV9y_cGZHXSWmRHfxw
+    debugging: true
+  gitlab:
+    host: http://gitlab.192.168.99.105.nip.io
+    webHookProxy: http://localhost:3000
+    token: wXDeETv2Kmt-JhiApJ9H
+  mail:
+    from: 370774330@qq.com
+
+
+  jenkins:
+    host: http://192.168.68.79:8082
+    username: root
+    token: 11d7b9e4c1acac481904c25813e2ec1f0a
+  docker:
+    registry: 192.168.68.79:18082
+
+
+
+  registry:
+    host: 192.168.68.79:18082
+    username: admin
+    password: admin123
+    nexus-server: 192.168.68.79:18081
+    repo-name: sec-repo
+    clean-up-task: 83bb8249-71e8-4367-83b3-50c6f7518860

+ 6 - 6
web/src/main/resources/application-dev.yml

@@ -1,11 +1,11 @@
 spring:
 
-  datasource:
-    # url: jdbc:mysql://192.168.68.76:13306/seecoder_ii?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
-    url: jdbc:mysql://localhost:3306/seecoder?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
-#    url: jdbc:mysql://218.94.159.108:13306/seec_ii?useUnicode=true&characterEncoding=UTF-8&useSSL=false
-    username: root
-    password: xizhiheng921
+#  datasource:
+#    # url: jdbc:mysql://192.168.68.76:13306/seecoder_ii?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
+#    url: jdbc:mysql://localhost:3306/seecoder?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
+##    url: jdbc:mysql://218.94.159.108:13306/seec_ii?useUnicode=true&characterEncoding=UTF-8&useSSL=false
+#    username: root
+#    password: 123456
   jpa:
     hibernate:
       ddl-auto: update

+ 28 - 100
web/src/main/resources/application-wph.yml

@@ -1,123 +1,51 @@
-mybatis:
-  #标注mybatis配置文件的位置
-  config-location: classpath:mybatis-config.xml
-
 spring:
   datasource:
-    url: jdbc:mysql://localhost:3306/seecoder?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&allowMultiQueries=true
+    url: jdbc:mysql://192.168.99.105:30006/devcloud?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
     username: root
-    password: 123456
-  jpa:
-    hibernate:
-      ddl-auto: update
-      naming:
-        implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
-        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
-    database-platform: org.hibernate.dialect.MySQL57InnoDBDialect
-  security:
-    user:
-      name: root
-      password: NJU67software
+    password: root
+
   mail:
-    host: smtp.163.com
-    username: seecoder@163.com
-    password: seec67NJU
+    host: smtp.qq.com
+    username: 370774330@qq.com
+    password: qkoecnzivukvbibi
+    port: 465
+    protocol: smtp
     properties:
       mail:
         smtp:
-          auth: true
-          socketFactory:
-            class: javax.net.ssl.SSLSocketFactory
-            port: 465
-          starttls:
+          ssl:
             enable: true
-            required: true
-  resources:
-    chain:
-      strategy:
-        content:
-          enabled: true
-          paths:
-            - /moocoder-frontend/**
-            - /js/**
-            - /css/**
-  servlet:
-    multipart:
-      max-file-size: 16MB
-      max-request-size: 16MB
-  redis:
-    host: http://192.168.68.76
-    port: 6379
-    password: NJU67redis
-    timeout: 1000
-    database: 0
-    lettuce:
-      pool:
-        min-idle: 5
-        max-active: 8
-        max-idle: 10
-        max-wait: 1ms
-
-      shutdown-timeout: 100ms
-
-
+          socketFactory.class: javax.net.ssl.SSLSocketFactory
+        debug: true
 server:
   port: 8080
-  tomcat:
-    remoteip:
-      remote-ip-header: X-Forwarded-For
-      protocol-header: X-Forwarded-Proto
-  servlet:
-    session:
-      persistent: true
-      store-dir: ${java.io.tmpdir}/moocoder-session
-      timeout: 1d
-  use-forward-headers: true
-management:
-  endpoints:
-    web:
-      base-path: /api/actuator
-  endpoint:
-    shutdown:
-      enabled: true
+
+mybatis:
+  configuration:
+    map-underscore-to-camel-case: true
 
 seecoder:
-  secret: 1qaz2wsx
-  auth:
-    signingKey: SEEC-1919810-OIDC-114514
-    iss: p.seecoder.cn
-    aud: seec-seec
-    portal: http://p.seecoder.cn/api/interaction
-    redirectUri: http://seec.seecoder.cn/api/auth/receive
+  k8s:
+    api-server: https://192.168.99.105:8443
+    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IjBmSXc4RlRjNUpBYVpGYWc4QzRDY1MzUWJad1JMdFhKcDI1cngzWGtiRlkifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXZjbG91ZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLXNicXY4Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5NmIxNzU1ZS0zZjE3LTQ1ZTctODFmYi01MTRhODA3NDRhMzQiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGV2Y2xvdWQ6ZGVmYXVsdCJ9.W0Y1lR6LBi1ZjylBx35YeqYuq2u2AqH-ayK4FD-LDfC26E89DanOwSv9XLrRMnnDBZFS_ZCRBumbnDeMiS5vw2x7-QnrDqj1M5u9FB2fIJsz2LyUkOhATZZmTzj_Uhewvvg09aDKh16ttj6ZX27m-KVk_6MP-VfiuvZnCVxDbqRkRjK-zsor4ty1CEATPyKK-tvugAwS0RFNHUL94DamfZAq_t1PSnH5ZtEHq0uEMZxY2c7j6u9d_aD9lphm_ZpCy_C8OCHfanM21CQPDHlcJTW_aiOO6bAp8qdc1egPO5uIamHl6Zm0N3RkcsRp4DGOHrltTV9y_cGZHXSWmRHfxw
+    debugging: true
   gitlab:
-    host: http://gitlab.seec.seecoder.cn
-    git-proxy: http://gitlab.seec.seecoder.cn
+    host: http://gitlab.192.168.99.105.nip.io
     webHookProxy: http://localhost:3000
-    username: root
-    token: x7Hwx-zq8LFkHznGku9a
+    token: wXDeETv2Kmt-JhiApJ9H
+  mail:
+    from: 370774330@qq.com
+
+
   jenkins:
     host: http://192.168.68.79:8082
     username: root
     token: 11d7b9e4c1acac481904c25813e2ec1f0a
-    baseImage: 172.19.51.9:18082/build-base
   docker:
     registry: 192.168.68.79:18082
-  mail:
-    from: seecoder@163.com
-  storage:
-    type: local
-    properties:
-      location: ./problem
-  k8s:
-    api-server: https://192.168.68.76:6443
-    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi1zdzlwOCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImY3MzQ2NzVlLWZmYmItMTFlOS1iYTdjLTIyNmUyZmY2ZTdlYyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphZG1pbiJ9.GvBP7iNM_WNJN7dPm8ZPszDJeykifhHwBYcF58c4W73iAet1ZcZPrmiurqn4mSHcavUHNR8FqPaafnS3OD4eq5dR-i7frdIBt7yiME7AIGA4pDKWNJ3NykhyfG75omCvtQ2Gp-UvFIdf_4peAilxbwDR2NtWpky4sJQOTukZIYzN9fCeYV8My85XDBl7ze20QBsEKfPxTGZkqmytX-4VmjKkQH9ob7YB90NR2ug5VK8CA16_2ZPjf8Fx4QsdTbkyZKdFo7zIrIID0fa2lpOc2TpCKWeYCl335-ULllpxds6kiVwkRXYP-5zkIOfpI_0SshK7kbBjd238OLM3KFDf6A
-    ingress-host: seecii.cn
-    public-namespace: dev
-    public-mysql-url: jdbc:mysql://seecii-mysql.dev.svc.cluster.local:3306
-    public-mysql-pod: seecii-mysql-579dc7c857-8m2fd
-    public-mysql-username: root
-    public-mysql-password: NJU67software
-    debugging: true
+
+
+
   registry:
     host: 192.168.68.79:18082
     username: admin

+ 1 - 1
web/src/main/resources/application.yml

@@ -1,3 +1,3 @@
 spring:
   profiles:
-    active: wph
+    active: cyz

+ 1 - 1
web/src/test/java/seecoder/devcloud/web/ArchitectWebApplicationTests.java → web/src/test/java/seecoder/devcloud/web/DevcloudWebApplicationTests.java

@@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
 @SpringBootTest
-class devcloudWebApplicationTests {
+class DevcloudWebApplicationTests {
 
 
 }

+ 2 - 2
web/src/test/java/seecoder/devcloud/web/dataMapper/TreeNodeMapperTest.java → web/src/test/java/seecoder/devcloud/web/dao/tree/TreeNodeMapperTest.java

@@ -1,12 +1,12 @@
-package seecoder.devcloud.web.dataMapper;
+package seecoder.devcloud.web.dao.tree;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
-import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
+import seecoder.devcloud.web.dao.tree.TreeNodeMapper;
 import seecoder.devcloud.web.enums.NodeStateEnum;
 import seecoder.devcloud.web.enums.NodeTypeEnum;
 import seecoder.devcloud.web.po.TreeNodePO;

+ 31 - 0
web/src/test/java/seecoder/devcloud/web/dao/user/GroupMapperTest.java

@@ -0,0 +1,31 @@
+package seecoder.devcloud.web.dao.user;
+
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/4
+ * @description:
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@ComponentScan({"seecoder.devcloud.api","seecoder.devcloud.common","seecoder.devcloud.web"})
+@MapperScan(basePackages = {"seecoder.devcloud.web.dao"})
+class GroupMapperTest {
+
+    @Autowired
+    private GroupMapper groupMapper;
+
+    @Test
+    void findWithMembersById() {
+        System.out.println(groupMapper.findWithMembersById(47));
+    }
+}

+ 45 - 0
web/src/test/java/seecoder/devcloud/web/dao/user/GroupMemberMapperTest.java

@@ -0,0 +1,45 @@
+package seecoder.devcloud.web.dao.user;
+
+import org.junit.Assert;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/4
+ * @description: 与gitlab交互,没有办法配事务,只能手动测试。mvn test的时候请排除这个类
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@ComponentScan({"seecoder.devcloud.api","seecoder.devcloud.common","seecoder.devcloud.web"})
+@MapperScan(basePackages = {"seecoder.devcloud.web.dao"})
+class GroupMemberMapperTest {
+
+
+    private final GroupMemberMapper groupMemberMapper;
+
+    @Autowired
+    GroupMemberMapperTest(GroupMemberMapper groupMemberMapper) {
+        this.groupMemberMapper = groupMemberMapper;
+    }
+
+
+    @Test
+    void selectUsersByGroupId() {
+        try{
+            System.out.println(groupMemberMapper.selectUsersByGroupId(47));
+        } catch (Exception e){
+            Assert.fail(e.getMessage());
+        }
+
+    }
+
+    @Test
+    void selectGroupsByUserId() {
+    }
+}

+ 111 - 0
web/src/test/java/seecoder/devcloud/web/service/user/GroupServiceTest.java

@@ -0,0 +1,111 @@
+package seecoder.devcloud.web.service.user;
+
+import org.gitlab4j.api.GitLabApi;
+import org.gitlab4j.api.GitLabApiException;
+import org.gitlab4j.api.models.Group;
+import org.junit.Assert;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.junit4.SpringRunner;
+import seecoder.devcloud.api.ApplicationProperties;
+import seecoder.devcloud.common.exceptions.EntityNotFoundException;
+import seecoder.devcloud.common.exceptions.ServiceException;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/4
+ * @description: 与gitlab交互,没有办法配事务,只能手动测试。mvn test的时候请排除这个类
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@ComponentScan({"seecoder.devcloud.api","seecoder.devcloud.common","seecoder.devcloud.web"})
+@MapperScan(basePackages = {"seecoder.devcloud.web.dao"})
+//@Transactional
+class GroupServiceTest {
+
+    @Autowired
+    GroupService groupService;
+
+    @Autowired
+    UserService userService;
+
+    @Autowired
+    ApplicationProperties properties;
+
+    /**
+     * 注意 GitlabApi 和 GitLabApi 区别
+     * GitlabApi 是我们对 GitLabApi 的一个小封装
+     * 这里作辅助测试用
+     */
+    GitLabApi gitLabApi;
+
+    @BeforeEach
+    void setUp() {
+        gitLabApi = new GitLabApi(properties.getGitlab().getHost(), properties.getGitlab().getToken());
+    }
+
+    @Test
+    void handler(){
+        try {
+            //gitLabApi.getGroupApi().deleteGroup(46);
+            System.out.println(gitLabApi.getGroupApi().getGroups());
+        } catch (GitLabApiException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @Test
+    void createGroup() {
+        int userId = 36;
+        try {
+            System.out.println(groupService.createGroup("testgroup1",userId));
+        } catch (Exception e) {
+            e.printStackTrace();
+            try {
+                List<Group> groups = gitLabApi.getGroupApi().getGroups();
+                Group group = groups.get(groups.size()-1);
+                gitLabApi.getGroupApi().deleteGroup(group.getId());
+            } catch (GitLabApiException gitLabApiException) {
+                System.out.println(gitLabApiException.getMessage());
+            }finally {
+                Assert.fail(e.getMessage());
+            }
+        }
+    }
+
+    @Test
+    void addMember() {
+        int groupId = 51;
+        int userId = 36;
+        int invitedUserId = 37;
+        try {
+            groupService.addMember(userId, invitedUserId, groupId);
+        } catch (EntityNotFoundException e) {
+            e.printStackTrace();
+            Assert.fail(e.getMessage());
+        }
+
+    }
+
+
+    @Test
+    void quitGroup() {
+        try {
+            groupService.quitGroup(47,37);
+        } catch (ServiceException serviceException) {
+            serviceException.printStackTrace();
+            Assert.fail(serviceException.getMessage());
+        }
+    }
+}

+ 49 - 0
web/src/test/java/seecoder/devcloud/web/service/user/MailServiceTest.java

@@ -0,0 +1,49 @@
+package seecoder.devcloud.web.service.user;
+
+import org.junit.Assert;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.test.context.junit4.SpringRunner;
+import seecoder.devcloud.common.exceptions.ServiceException;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import java.util.concurrent.CompletableFuture;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/3
+ * @description:
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@ComponentScan({"seecoder.devcloud.api","seecoder.devcloud.common","seecoder.devcloud.web"})
+@MapperScan(basePackages = {"seecoder.devcloud.web.dao"})
+class MailServiceTest {
+
+    @Autowired
+    MailService mailService;
+
+    @Autowired
+    JavaMailSender javaMailSender;
+    @Test
+    void send() {
+        try {
+            CompletableFuture<Void> result = mailService.send( "你的课程作业","按时","2717718789@qq.com");
+            CompletableFuture.allOf(result).join();
+        } catch (Exception e) {
+            e.printStackTrace();
+            Assert.fail(e.getMessage());
+        }
+    }
+}

+ 113 - 0
web/src/test/java/seecoder/devcloud/web/service/user/UserServiceTest.java

@@ -0,0 +1,113 @@
+package seecoder.devcloud.web.service.user;
+
+import org.gitlab4j.api.GitLabApi;
+import org.gitlab4j.api.models.AbstractUser;
+import org.junit.Assert;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.junit4.SpringRunner;
+import seecoder.devcloud.api.ApplicationProperties;
+import seecoder.devcloud.common.exceptions.ServiceException;
+import seecoder.devcloud.web.vo.user.ChangePasswordVO;
+import seecoder.devcloud.web.vo.user.RegisterVO;
+import seecoder.devcloud.web.vo.user.UserVO;
+
+import java.util.stream.Collectors;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/2
+ * @description: 与gitlab交互,没有办法配事务,只能手动测试。mvn test的时候请排除这个类
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@ComponentScan({"seecoder.devcloud.api","seecoder.devcloud.common","seecoder.devcloud.web"})
+@MapperScan(basePackages = {"seecoder.devcloud.web.dao"})
+class UserServiceTest {
+
+    @Autowired
+    UserService userService;
+
+    @Autowired
+    ApplicationProperties properties;
+
+    /**
+     * 注意 GitlabApi 和 GitLabApi 区别
+     * GitlabApi 是我们对 GitLabApi 的一个小封装
+     * 这里作辅助测试用
+     */
+    GitLabApi gitLabApi;
+
+    @BeforeEach
+    void setUp() {
+        gitLabApi = new GitLabApi(properties.getGitlab().getHost(), properties.getGitlab().getToken());
+    }
+
+    @Test
+    void register() {
+        RegisterVO registerVO = RegisterVO.builder()
+                .username("gfygfytest")
+                .password("gfygfytest")
+                .email("308213885@qq.com")
+                .nickname("kennyday")
+                .build();
+        try {
+            System.out.println(userService.register(registerVO));
+        } catch (ServiceException e){
+            e.printStackTrace();
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    @Test
+    void delete() {
+        try {
+            userService.delete(36);
+            //gitLabApi.getUserApi().deleteUser(35);
+            System.out.println(gitLabApi.getUserApi().getActiveUsers().stream().map(AbstractUser::getId).collect(Collectors.toList()));
+        } catch (Exception e) {
+            e.printStackTrace();
+            Assert.fail(e.getMessage());
+        }
+
+    }
+
+    @Test
+    void getUserById() {
+        try {
+            UserVO u = userService.getUserById(25);
+            Assert.assertNotNull(u);
+            System.out.println(u);
+        } catch (Exception e) {
+            e.printStackTrace();
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    @Test
+    void changeUserInfo() {
+        try {
+            userService.changeUserInfo(31,"gogoing");
+        } catch (ServiceException e) {
+            e.printStackTrace();
+            Assert.fail(e.getMessage());
+        }
+    }
+
+
+    @Test
+    void changeUserPassword() {
+        try {
+
+            userService.changePassword(new ChangePasswordVO(25,"1234qwer","1234qwer1","1234qwer1"));
+        } catch (ServiceException e) {
+            e.printStackTrace();
+            Assert.fail(e.getMessage());
+        }
+    }
+}