haha 2 gadi atpakaļ
revīzija
f96d03d4d9
100 mainītis faili ar 2284 papildinājumiem un 0 dzēšanām
  1. 37 0
      .gitignore
  2. 13 0
      Dockerfile
  3. 100 0
      pom.xml
  4. 1 0
      problem/bb
  5. 17 0
      src/main/java/com/seecoder/AppApplication.java
  6. 56 0
      src/main/java/com/seecoder/client/alibaba/AliService.java
  7. 18 0
      src/main/java/com/seecoder/client/k8s/AbstractApi.java
  8. 7 0
      src/main/java/com/seecoder/client/k8s/ConfigMapApi.java
  9. 46 0
      src/main/java/com/seecoder/client/k8s/K8sConstants.java
  10. 68 0
      src/main/java/com/seecoder/client/k8s/exception/K8sApiException.java
  11. 177 0
      src/main/java/com/seecoder/client/k8s/impl/ConfigMapApiImpl.java
  12. 48 0
      src/main/java/com/seecoder/client/k8s/model/ConfigMap.java
  13. 86 0
      src/main/java/com/seecoder/client/k8s/model/K8sAbstractObject.java
  14. 42 0
      src/main/java/com/seecoder/client/k8s/model/K8sObject.java
  15. 24 0
      src/main/java/com/seecoder/client/k8s/model/K8sObjectRequest.java
  16. 6 0
      src/main/java/com/seecoder/client/k8s/model/K8sObjectTransformer.java
  17. 170 0
      src/main/java/com/seecoder/client/k8s/model/LabelSelector.java
  18. 105 0
      src/main/java/com/seecoder/config/K8sApiConfiguration.java
  19. 22 0
      src/main/java/com/seecoder/config/property/AliProperties.java
  20. 50 0
      src/main/java/com/seecoder/config/property/ApplicationProperties.java
  21. 11 0
      src/main/java/com/seecoder/constant/ConfigConstant.java
  22. 76 0
      src/main/java/com/seecoder/controller/ConfigController.java
  23. 48 0
      src/main/java/com/seecoder/controller/CourseController.java
  24. 60 0
      src/main/java/com/seecoder/controller/FileController.java
  25. 28 0
      src/main/java/com/seecoder/controller/MessageController.java
  26. 12 0
      src/main/java/com/seecoder/dao/ConfigHistoryDAO.java
  27. 7 0
      src/main/java/com/seecoder/dao/CourseDAO.java
  28. 7 0
      src/main/java/com/seecoder/dao/StudentCourseDAO.java
  29. 24 0
      src/main/java/com/seecoder/entity/bo/ConfigBO.java
  30. 42 0
      src/main/java/com/seecoder/entity/po/ConfigHistory.java
  31. 91 0
      src/main/java/com/seecoder/entity/po/Course.java
  32. 14 0
      src/main/java/com/seecoder/entity/req/SendByMailReq.java
  33. 12 0
      src/main/java/com/seecoder/entity/req/SendByPhoneReq.java
  34. 11 0
      src/main/java/com/seecoder/enums/ConfigModifiedTypeEnums.java
  35. 6 0
      src/main/java/com/seecoder/enums/CourseStatusTypeEnums.java
  36. 6 0
      src/main/java/com/seecoder/enums/CourseTypeEnums.java
  37. 20 0
      src/main/java/com/seecoder/service/ConfigService.java
  38. 14 0
      src/main/java/com/seecoder/service/CourseService.java
  39. 10 0
      src/main/java/com/seecoder/service/MessageService.java
  40. 132 0
      src/main/java/com/seecoder/service/impl/ConfigServiceImpl.java
  41. 49 0
      src/main/java/com/seecoder/service/impl/CourseServiceImpl.java
  42. 46 0
      src/main/java/com/seecoder/service/impl/MessageServiceImpl.java
  43. 12 0
      src/main/java/com/seecoder/storage/StorageProvider.java
  44. 36 0
      src/main/java/com/seecoder/storage/StorageProviderConfiguration.java
  45. 43 0
      src/main/java/com/seecoder/storage/impl/LocalStorageProvider.java
  46. 79 0
      src/main/java/com/seecoder/storage/impl/OssStorageProvider.java
  47. 42 0
      src/main/java/com/seecoder/util/LoggerUtil.java
  48. 67 0
      src/main/resources/application.yml
  49. 119 0
      target/classes/META-INF/spring-configuration-metadata.json
  50. 67 0
      target/classes/application.yml
  51. BIN
      target/classes/com/seecoder/AppApplication.class
  52. BIN
      target/classes/com/seecoder/client/alibaba/AliService.class
  53. BIN
      target/classes/com/seecoder/client/k8s/AbstractApi.class
  54. BIN
      target/classes/com/seecoder/client/k8s/ConfigMapApi.class
  55. BIN
      target/classes/com/seecoder/client/k8s/K8sConstants.class
  56. BIN
      target/classes/com/seecoder/client/k8s/exception/K8sApiException.class
  57. BIN
      target/classes/com/seecoder/client/k8s/impl/ConfigMapApiImpl.class
  58. BIN
      target/classes/com/seecoder/client/k8s/model/ConfigMap$ConfigMapBuilder.class
  59. BIN
      target/classes/com/seecoder/client/k8s/model/ConfigMap.class
  60. BIN
      target/classes/com/seecoder/client/k8s/model/K8sAbstractObject.class
  61. BIN
      target/classes/com/seecoder/client/k8s/model/K8sObject.class
  62. BIN
      target/classes/com/seecoder/client/k8s/model/K8sObjectRequest$K8sObjectRequestBuilder.class
  63. BIN
      target/classes/com/seecoder/client/k8s/model/K8sObjectRequest.class
  64. BIN
      target/classes/com/seecoder/client/k8s/model/K8sObjectTransformer.class
  65. BIN
      target/classes/com/seecoder/client/k8s/model/LabelSelector$1.class
  66. BIN
      target/classes/com/seecoder/client/k8s/model/LabelSelector$MatchExpressionItem.class
  67. BIN
      target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$1.class
  68. BIN
      target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$2.class
  69. BIN
      target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$3.class
  70. BIN
      target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$4.class
  71. BIN
      target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$5.class
  72. BIN
      target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator.class
  73. BIN
      target/classes/com/seecoder/client/k8s/model/LabelSelector.class
  74. BIN
      target/classes/com/seecoder/config/K8sApiConfiguration.class
  75. BIN
      target/classes/com/seecoder/config/property/AliProperties$Profile.class
  76. BIN
      target/classes/com/seecoder/config/property/AliProperties.class
  77. BIN
      target/classes/com/seecoder/config/property/ApplicationProperties$K8s.class
  78. BIN
      target/classes/com/seecoder/config/property/ApplicationProperties$LocalStorage.class
  79. BIN
      target/classes/com/seecoder/config/property/ApplicationProperties$OssStorage.class
  80. BIN
      target/classes/com/seecoder/config/property/ApplicationProperties.class
  81. BIN
      target/classes/com/seecoder/constant/ConfigConstant.class
  82. BIN
      target/classes/com/seecoder/controller/ConfigController.class
  83. BIN
      target/classes/com/seecoder/controller/CourseController.class
  84. BIN
      target/classes/com/seecoder/controller/FileController.class
  85. BIN
      target/classes/com/seecoder/controller/MessageController.class
  86. BIN
      target/classes/com/seecoder/dao/ConfigHistoryDAO.class
  87. BIN
      target/classes/com/seecoder/dao/CourseDAO.class
  88. BIN
      target/classes/com/seecoder/dao/StudentCourseDAO.class
  89. BIN
      target/classes/com/seecoder/entity/bo/ConfigBO.class
  90. BIN
      target/classes/com/seecoder/entity/po/ConfigHistory$ConfigHistoryBuilder.class
  91. BIN
      target/classes/com/seecoder/entity/po/ConfigHistory.class
  92. BIN
      target/classes/com/seecoder/entity/po/Course$CourseBuilder.class
  93. BIN
      target/classes/com/seecoder/entity/po/Course.class
  94. BIN
      target/classes/com/seecoder/entity/req/SendByMailReq.class
  95. BIN
      target/classes/com/seecoder/entity/req/SendByPhoneReq.class
  96. BIN
      target/classes/com/seecoder/enums/ConfigModifiedTypeEnums.class
  97. BIN
      target/classes/com/seecoder/enums/CourseStatusTypeEnums.class
  98. BIN
      target/classes/com/seecoder/enums/CourseTypeEnums.class
  99. BIN
      target/classes/com/seecoder/service/ConfigService.class
  100. BIN
      target/classes/com/seecoder/service/CourseService.class

+ 37 - 0
.gitignore

@@ -0,0 +1,37 @@
+!.mvn/wrapper/maven-wrapper.jar
+
+### IntelliJ IDEA ###
+.idea/
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
+/.idea/

+ 13 - 0
Dockerfile

@@ -0,0 +1,13 @@
+FROM maven:3.6.1 AS compile_stage
+ENV PROJECT_NAME app
+ENV WORK_PATH /opt/$PROJECT_NAME
+COPY settings.xml /root/.m2/settings.xml
+COPY . $WORK_PATH
+#RUN cd $WORK_PATH && mvn clean install -DskipTests -DfinalName=$PROJECT_NAME
+
+FROM java:8-jre-alpine
+ENV PROJECT_NAME app
+ENV WORK_PATH /opt/$PROJECT_NAME
+WORKDIR /app
+COPY --from=compile_stage $WORK_PATH/target/${PROJECT_NAME}.jar .
+CMD ["sh", "-c", "java -Xdebug -Xrunjdwp:transport=dt_socket,address=8888,server=y,suspend=n -jar -server /app/${PROJECT_NAME}.jar"]

+ 100 - 0
pom.xml

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.seecoder</groupId>
+    <artifactId>seec-unified</artifactId>
+    <version>0.0.1</version>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.3</version>
+        <relativePath />
+    </parent>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-mail</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!--持久化-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.28</version>
+        </dependency>
+
+        <!--阿里云依赖-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.5.20</version>
+        </dependency>
+
+        <!--内部依赖-->
+        <dependency>
+            <groupId>com.seecoder</groupId>
+            <artifactId>common-core</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <!--统一-->
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/io.kubernetes/client-java -->
+        <dependency>
+            <groupId>io.kubernetes</groupId>
+            <artifactId>client-java</artifactId>
+            <version>10.0.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/io.fabric8/kubernetes-client -->
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>kubernetes-client</artifactId>
+            <version>5.0.0</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 1 - 0
problem/bb

@@ -0,0 +1 @@
+123121

+ 17 - 0
src/main/java/com/seecoder/AppApplication.java

@@ -0,0 +1,17 @@
+package com.seecoder;
+
+
+import com.seecoder.config.property.AliProperties;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication
+@EnableScheduling
+@EnableConfigurationProperties({AliProperties.class})
+public class AppApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(AppApplication.class, args);
+    }
+}

+ 56 - 0
src/main/java/com/seecoder/client/alibaba/AliService.java

@@ -0,0 +1,56 @@
+package com.seecoder.client.alibaba;
+
+import com.aliyuncs.CommonRequest;
+import com.aliyuncs.CommonResponse;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.http.MethodType;
+import com.aliyuncs.profile.DefaultProfile;
+import com.seecoder.config.property.AliProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AliService {
+    private static final Logger log = LoggerFactory.getLogger(AliService.class);
+
+    private final AliProperties.Profile aliProfile;
+    private final IAcsClient client;
+
+    public AliService(AliProperties aliProperties) {
+        this.aliProfile = aliProperties.getProfile();
+        DefaultProfile profile = DefaultProfile.getProfile(
+                aliProfile.getRegionId(),
+                aliProfile.getAccessKeyId(),
+                aliProfile.getAccessKeySecret());
+        client = new DefaultAcsClient(profile);
+    }
+
+    @Async
+    public void sendMessage(String phone, String text) {
+        sendSms(phone, text);
+    }
+
+    private void sendSms(String phone, String code) {
+        CommonRequest request = new CommonRequest();
+        request.setSysMethod(MethodType.POST);
+        request.setSysDomain(aliProfile.getSysDomain());
+        request.setSysVersion(aliProfile.getSysVersion());
+        request.setSysAction("SendSms");
+        request.putQueryParameter("RegionId", aliProfile.getRegionId());
+        request.putQueryParameter("PhoneNumbers", phone);
+        request.putQueryParameter("SignName", aliProfile.getSignName());
+        request.putQueryParameter("TemplateCode", aliProfile.getTemplateCode());
+        request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");
+        try {
+            CommonResponse response = client.getCommonResponse(request);
+            log.info(response.getData());
+        } catch (ClientException e) {
+            log.error("Send sms error", e);
+        }
+    }
+
+}

+ 18 - 0
src/main/java/com/seecoder/client/k8s/AbstractApi.java

@@ -0,0 +1,18 @@
+package com.seecoder.client.k8s;
+
+
+import com.seecoder.client.k8s.model.K8sAbstractObject;
+import com.seecoder.client.k8s.model.K8sObjectRequest;
+
+import java.util.List;
+
+public interface AbstractApi<T extends K8sAbstractObject> {
+
+    T create(T t);
+
+    T update(T t);
+
+    void delete(T t);
+
+    List<T> getByCondition(K8sObjectRequest request);
+}

+ 7 - 0
src/main/java/com/seecoder/client/k8s/ConfigMapApi.java

@@ -0,0 +1,7 @@
+package com.seecoder.client.k8s;
+
+
+import com.seecoder.client.k8s.model.ConfigMap;
+
+public interface ConfigMapApi extends AbstractApi<ConfigMap> {
+}

+ 46 - 0
src/main/java/com/seecoder/client/k8s/K8sConstants.java

@@ -0,0 +1,46 @@
+package com.seecoder.client.k8s;
+
+public class K8sConstants {
+
+    public static final String NAME_PATTERN = "^[0-9a-zA-Z-]{8,}$";
+
+    //标签域名前缀
+    private static final String LABEL_PREFIX = "demo.seec.nju.cn";
+    //课程前缀
+    public static final String TYPE_LABEL = LABEL_PREFIX + "/type";
+    //描述
+    public static final String DESCRIPTION_LABEL = LABEL_PREFIX + "/description";
+    //应用
+    public static final String APPLICATION_LABEL = LABEL_PREFIX + "/app";
+
+    public static final String DEPLOY_ID_LABEL = LABEL_PREFIX + "/deployid";
+    // 用户namespace的label
+    public static final String USERSPACE_LABEL = LABEL_PREFIX +"/userspace";
+    // pod的label
+    public static final String SELECTOR_LABEL = LABEL_PREFIX + "/app";
+    // 非对外service的label
+    public static final String INTERNAL_LABEL = LABEL_PREFIX + "/internal";
+    // 对外service的label
+    public static final String EXTERNAL_LABEL = LABEL_PREFIX + "/external";
+    // 持久卷访问模式
+    public static final String STORAGE_ACCESS_MODE = "ReadWriteMany";
+    // 持久卷存储类型
+    public static final String STORAGE_CLASS = "glusterfs";
+
+    public static final String RESOURCE_STORAGE = "storage";
+
+    public static final String STARTS_AT_LABEL = LABEL_PREFIX + "/start";
+
+    public static final String ENDS_AT_LABEL = LABEL_PREFIX + "/end";
+
+    public static final String PRETTY_FORMAT = "true";
+
+    //cascade清除策略
+    //Deployment必须使用Foreground,否则只会删除Replica Set 不会删除底下的Pod
+    //https://github.com/kubernetes/kubeadm/issues/149#issuecomment-284766613
+    public static final String FOREGROUND_PROPAGATION_POLICY = "Foreground";
+
+    public static final String METADATA_NAME_SELECTOR = "metadata.name=";
+
+    public static final String DB_SECRET_NAME = "db-secret";
+}

+ 68 - 0
src/main/java/com/seecoder/client/k8s/exception/K8sApiException.java

@@ -0,0 +1,68 @@
+package com.seecoder.client.k8s.exception;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class K8sApiException extends RuntimeException {
+
+    private final int code;
+
+    private final String description;
+
+    public static final Map<Integer, String> codeMap = new HashMap<>();
+
+    public static final int NAMESPACE_NOT_EXIST = 400;
+
+    public static final int DEPLOYMENT_NOT_EXIST = 401;
+
+    public static final int NOT_FOUND = 404;
+
+    public static final int RESOURCE_QUOTA_NOT_EXIST = 402;
+
+    public static final int ALREADY_EXIST = 409;
+    //LimitRange的参数配置:default值不大于max的值,default request的值不小于min的值
+    public static final int INVALID_VALUE = 422;
+
+    public static final int BAD_REQUEST = 200;
+
+    public static final int SYSTEM_ERROR = 500;
+
+    public static final int COMMAND_FAIL = 300;
+
+    public static final int NOT_SUPPORT_METHOD = 100;
+
+
+    public static final K8sApiException K8s_SYSTEM_ERROR_EXCEPTION = new K8sApiException(SYSTEM_ERROR);
+
+    public static final K8sApiException K8s_NAMESPACE_NOT_EXIST = new K8sApiException(NAMESPACE_NOT_EXIST);
+
+    public static final K8sApiException K8s_NOT_SUPPORT_METHOD = new K8sApiException(NOT_SUPPORT_METHOD);
+
+    //系统故障
+    static {
+        codeMap.put(NOT_FOUND, "资源不存在");
+        codeMap.put(RESOURCE_QUOTA_NOT_EXIST, "资源限额不存在");
+        codeMap.put(ALREADY_EXIST, "资源已存在");
+        codeMap.put(INVALID_VALUE, "参数设置无效");
+        codeMap.put(BAD_REQUEST, "请求参数非法");
+        codeMap.put(SYSTEM_ERROR, "系统异常");
+        codeMap.put(COMMAND_FAIL, "指令执行失败");
+        codeMap.put(NOT_SUPPORT_METHOD, "不支持的方法");
+    }
+    public K8sApiException(int code, String description){
+        this.code = code;
+        this.description = description;
+    }
+
+    public K8sApiException(int code) {
+        this.code = code;
+        this.description = codeMap.getOrDefault(code, "操作执行失败");
+    }
+
+
+}

+ 177 - 0
src/main/java/com/seecoder/client/k8s/impl/ConfigMapApiImpl.java

@@ -0,0 +1,177 @@
+package com.seecoder.client.k8s.impl;
+
+import com.google.gson.JsonSyntaxException;
+import com.seecoder.client.k8s.ConfigMapApi;
+import com.seecoder.client.k8s.exception.K8sApiException;
+import com.seecoder.client.k8s.model.ConfigMap;
+import com.seecoder.client.k8s.model.K8sObjectRequest;
+import com.seecoder.client.k8s.model.LabelSelector;
+import com.seecoder.util.LoggerUtil;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.V1ConfigMap;
+import io.kubernetes.client.openapi.models.V1ConfigMapList;
+import io.kubernetes.client.openapi.models.V1DeleteOptions;
+import io.kubernetes.client.openapi.models.V1DeleteOptionsBuilder;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.seecoder.client.k8s.K8sConstants.FOREGROUND_PROPAGATION_POLICY;
+import static com.seecoder.client.k8s.K8sConstants.PRETTY_FORMAT;
+import static com.seecoder.client.k8s.exception.K8sApiException.ALREADY_EXIST;
+import static com.seecoder.client.k8s.exception.K8sApiException.NOT_FOUND;
+
+
+@Service
+public class ConfigMapApiImpl implements ConfigMapApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(ConfigMapApiImpl.class);
+
+    private CoreV1Api coreV1Api;
+
+    @Autowired
+    public ConfigMapApiImpl(CoreV1Api coreV1Api) {
+        this.coreV1Api = coreV1Api;
+    }
+
+    @Override
+    public ConfigMap create(ConfigMap configMap) {
+        try {
+            V1ConfigMap v1ConfigMap = configMap.toK8sObject();
+            V1ConfigMap result = coreV1Api.createNamespacedConfigMap(configMap.getNamespace(), v1ConfigMap, PRETTY_FORMAT, null, null);
+            return result == null ? null : new ConfigMap(result);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "configMap创建失败, configMap={}, response={}", configMap, e.getResponseBody());
+
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "namespace不存在");
+            } else if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "configMap已经存在");
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "configMap创建异常, configMap={}", configMap);
+            throw e;
+        }
+    }
+
+    @Override
+    public ConfigMap update(ConfigMap configMap) {
+        try {
+            V1ConfigMap v1ConfigMap = getV1ConfigMap(configMap.getNamespace(), configMap.getName());
+            configMap.merge(v1ConfigMap);
+            V1ConfigMap result = coreV1Api.replaceNamespacedConfigMap(configMap.getName(), configMap.getNamespace(), v1ConfigMap, PRETTY_FORMAT, null, null);
+            return result == null ? null : new ConfigMap(result);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "configMap更新失败, configMap={}, response={}", configMap, e.getResponseBody());
+
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "namespace不存在");
+            } else if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "configMap已经存在");
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "configMap更新异常, configMap={}", configMap);
+            throw e;
+        }
+    }
+
+    @Override
+    public void delete(ConfigMap configMap) {
+        try {
+            V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                    .withApiVersion(ConfigMap.API_VERSION)
+                    .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                    .build();
+            coreV1Api.deleteNamespacedConfigMap(
+                    configMap.getName(),
+                    configMap.getNamespace(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    FOREGROUND_PROPAGATION_POLICY,
+                    v1DeleteOptions);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "删除ConfigMap失败,namespace={}, name={}, response={}", configMap.getNamespace(), configMap.getName(), e.getResponseBody());
+
+            if (e.getCode() == K8sApiException.NOT_FOUND) {
+                return;
+            } else {
+                return;
+            }
+        } catch (JsonSyntaxException e) {
+            //do nothing
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "删除ConfigMap异常,namespace={}, name={}", configMap.getNamespace(), configMap.getName());
+            return;
+        }
+    }
+
+    @Override
+    public List<ConfigMap> getByCondition(K8sObjectRequest request) {
+        try {
+            if (request.getName() != null) {
+                V1ConfigMap obj = coreV1Api.readNamespacedConfigMap(
+                        request.getName(),
+                        request.getNamespace(),
+                        PRETTY_FORMAT,
+                        null,
+                        null);
+                return obj == null ? Collections.emptyList() : Collections.singletonList(new ConfigMap(obj));
+            }
+            LabelSelector selector = null;
+            if (request.getSelector() != null) {
+                selector = request.getSelector();
+            }
+            if (request.getLabels() != null) {
+                if (selector == null) {
+                    selector = new LabelSelector();
+                }
+                selector.addAll(request.getLabels());
+            }
+            logger.info(String.valueOf(selector.toJsonString()));
+            V1ConfigMapList objList = coreV1Api.listNamespacedConfigMap(
+                    request.getNamespace(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    selector == null ? null : selector.toJsonString(),
+                    null,
+                    null,
+                    null,
+                    null);
+            return objList.getItems().stream().map(ConfigMap::new).collect(Collectors.toList());
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "查找configMap失败,namespace = {}, response={}",
+                    request.getNamespace(), e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                return Collections.emptyList();
+            } else {
+                return Collections.emptyList();
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "查找configMap失败,namespace = {}, name={}",
+                    request.getNamespace());
+            return Collections.emptyList();
+        }
+    }
+
+    private V1ConfigMap getV1ConfigMap(String namespace, String name) throws ApiException {
+        return coreV1Api.readNamespacedConfigMap(
+                name,
+                namespace,
+                PRETTY_FORMAT,
+                false,
+                false);
+    }
+}

+ 48 - 0
src/main/java/com/seecoder/client/k8s/model/ConfigMap.java

@@ -0,0 +1,48 @@
+package com.seecoder.client.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 java.util.HashMap;
+import java.util.Map;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ConfigMap extends K8sAbstractObject<V1ConfigMap> {
+
+    public static final String API_VERSION = "v1";
+
+    public static final String KIND = "ConfigMap";
+
+    private Map<String, String> data = new HashMap<>();
+
+
+    public ConfigMap(V1ConfigMap v1ConfigMap) {
+        super(v1ConfigMap);
+        this.data = v1ConfigMap.getData();
+    }
+
+    public void merge(V1ConfigMap v1ConfigMap) {
+        v1ConfigMap.getMetadata().setName(getName());
+        v1ConfigMap.getMetadata().setNamespace(this.getNamespace());
+        v1ConfigMap.getMetadata().setLabels(this.getLabels());
+        v1ConfigMap.getMetadata().setAnnotations(this.getAnnotations());
+        v1ConfigMap.setData(this.getData());
+    }
+
+    @Override
+    public V1ConfigMap toK8sObject() {
+        return new V1ConfigMapBuilder()
+                .withApiVersion(getApiVersion() == null ? API_VERSION : getApiVersion())
+                .withKind(getKind() == null ? KIND : getKind())
+                .withMetadata(toV1ObjectMeta())
+                .withData(data)
+                .build();
+    }
+}

+ 86 - 0
src/main/java/com/seecoder/client/k8s/model/K8sAbstractObject.java

@@ -0,0 +1,86 @@
+package com.seecoder.client.k8s.model;
+
+import io.kubernetes.client.common.KubernetesObject;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@NoArgsConstructor
+@Data
+@ToString
+public abstract class K8sAbstractObject<T extends KubernetesObject> implements K8sObject, K8sObjectTransformer<T> {
+
+    private String apiVersion;
+
+    private String kind;
+
+    private String name;
+
+    private String namespace;
+
+    private Map<String, String> labels = new HashMap<>();
+
+    private Map<String, String> annotations = new HashMap<>();
+
+    public K8sAbstractObject(T t) {
+        this.apiVersion = t.getApiVersion();
+        this.kind = t.getKind();
+        this.name = t.getMetadata().getName();
+        this.namespace = t.getMetadata().getNamespace();
+        this.annotations = t.getMetadata().getAnnotations();
+        this.labels = t.getMetadata().getLabels();
+    }
+
+    @Override
+    public Map<String, String> getAnnotations() {
+        return annotations;
+    }
+
+    @Override
+    public String getAnnotation(String key) {
+        return annotations.get(key);
+    }
+
+    @Override
+    public void setAnnotation(String key, String value) {
+        annotations.put(key, value);
+    }
+
+    @Override
+    public Map<String, String> getLabels() {
+        return labels;
+    }
+
+    @Override
+    public String getLabel(String key) {
+        return labels.get(key);
+    }
+
+    @Override
+    public void setLabel(String key, String value) {
+        labels.put(key, value);
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getNamespace() {
+        return namespace;
+    }
+
+    @Override
+    public String getApiVersion() {
+        return apiVersion;
+    }
+
+    @Override
+    public String getKind() {
+        return kind;
+    }
+}

+ 42 - 0
src/main/java/com/seecoder/client/k8s/model/K8sObject.java

@@ -0,0 +1,42 @@
+package com.seecoder.client.k8s.model;
+
+import io.kubernetes.client.openapi.models.V1ObjectMeta;
+import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
+
+import java.util.Map;
+
+public interface K8sObject {
+
+    String DEFAULT_NAMESPACE = "default";
+
+    String getApiVersion();
+
+    String getKind();
+
+    String getName();
+
+    Map<String, String> getLabels();
+
+    String getLabel(String key);
+
+    void setLabel(String key, String value);
+
+    Map<String, String> getAnnotations();
+
+    String getAnnotation(String key);
+
+    void setAnnotation(String key, String value);
+
+    default String getNamespace() {
+      return DEFAULT_NAMESPACE;
+    }
+
+    default V1ObjectMeta toV1ObjectMeta() {
+        return new V1ObjectMetaBuilder()
+                .withName(getName())
+                .withNamespace(getNamespace())
+                .withAnnotations(getAnnotations())
+                .withLabels(getLabels())
+                .build();
+    }
+}

+ 24 - 0
src/main/java/com/seecoder/client/k8s/model/K8sObjectRequest.java

@@ -0,0 +1,24 @@
+package com.seecoder.client.k8s.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+@AllArgsConstructor
+@Builder
+public class K8sObjectRequest {
+
+    private String name;
+
+    private String namespace;
+
+    private Map<String, String> labels = new HashMap<>();
+
+    private Map<String, String> annotations = new HashMap<>();
+
+    private LabelSelector selector;
+}

+ 6 - 0
src/main/java/com/seecoder/client/k8s/model/K8sObjectTransformer.java

@@ -0,0 +1,6 @@
+package com.seecoder.client.k8s.model;
+
+public interface K8sObjectTransformer<T> {
+
+    T toK8sObject();
+}

+ 170 - 0
src/main/java/com/seecoder/client/k8s/model/LabelSelector.java

@@ -0,0 +1,170 @@
+package com.seecoder.client.k8s.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+
+/**
+ * author: rale
+ * createdAt: 1/7/19
+ *
+ * 标签选择器
+ * 支持以下几种语法
+ * key IN value1,value2 ... 筛选出键为key且值在value中的资源 key equals value 等价于key in value
+ * key NOT_IN value1, value2 ... 筛选出键为key且值不在value中,或者不存在键为key的键值对的资源 key not equals value 等价于 key not in value
+ * Key EXISTS 筛选出包含键key的资源
+ * Key NOT_EXISTS 筛选出不包含键key的资源
+ *
+ * 本选择器不会删除同名的key
+ */
+@NoArgsConstructor
+public class LabelSelector {
+
+    public enum Operator{
+
+        IN {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                Set<String> values = matchExpressionItem.getValues();
+                if (values == null || values.size() == 0) return "";
+                if (values.size() == 1) return matchExpressionItem.getKey() + "=" + values.iterator().next();
+                StringBuilder resultValue = new StringBuilder("(");
+                for (String value : matchExpressionItem.getValues()) {
+                    if (resultValue.length() > 1) {
+                        resultValue.append(",");
+                    }
+                    resultValue.append(value);
+                }
+                resultValue.append(")");
+                return matchExpressionItem.getKey() + " in " + resultValue;
+            }
+        },
+
+        NOT_IN {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                Set<String> values = matchExpressionItem.getValues();
+                if (values == null || values.size() == 0) return "";
+                if (values.size() == 1) return matchExpressionItem.getKey() + "!=" + values.iterator().next();
+                StringBuilder resultValue = new StringBuilder("(");
+                for (String value : matchExpressionItem.getValues()) {
+                    if (resultValue.length() > 1) {
+                        resultValue.append(",");
+                    }
+                    resultValue.append(value);
+                }
+                resultValue.append(")");
+                return matchExpressionItem.getKey() + " notin " + resultValue;
+            }
+        },
+
+        EXISTS {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                return matchExpressionItem.getKey();
+            }
+        },
+
+        NOT_EXISTS {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                return "!" + matchExpressionItem.getKey();
+            }
+        },
+
+        EQUALS {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                Set<String> values = matchExpressionItem.getValues();
+                if (values == null || values.size() == 0) return "";
+                return matchExpressionItem.getKey() + "=" + values.iterator().next();
+            }
+        }
+        ;
+
+        public abstract String toJsonString(MatchExpressionItem matchExpressionItem);
+    }
+
+
+    private List<MatchExpressionItem> matchExpressionItems;
+
+
+    public void addMatchExpression(String key, Operator operator, Set<String> values) {
+        addMatchExpressionItem(new MatchExpressionItem(key, operator, values));
+    }
+
+
+    public void addMatchExpression(String key, Operator operator, String... values) {
+        Set<String> valueSet = new HashSet<>(Arrays.asList(values));
+        addMatchExpressionItem(new MatchExpressionItem(key, operator, valueSet));
+    }
+
+    public void addAll(Map<String, String> labels) {
+        if (labels == null) return;
+        labels.entrySet().forEach(entry -> {
+            addMatchExpressionItem(new MatchExpressionItem(entry.getKey(), Operator.EQUALS, Collections.singleton(entry.getValue())));
+        });
+    }
+
+
+    private void addMatchExpressionItem(MatchExpressionItem matchExpressionItem) {
+        if (matchExpressionItem == null) {
+            return;
+        }
+        if (matchExpressionItems == null) {
+            matchExpressionItems = new LinkedList<>();
+        }
+        matchExpressionItems.add(matchExpressionItem);
+    }
+
+    @Data
+    @EqualsAndHashCode
+    @NoArgsConstructor
+    @AllArgsConstructor
+    private static class MatchExpressionItem{
+
+        private String key;
+
+        private Operator operator;
+
+        private Set<String> values;
+
+        private String toJsonString() {
+            return operator == null ? "" : operator.toJsonString(this);
+        }
+
+    }
+
+    public String toJsonString() {
+        StringBuilder sb = new StringBuilder();
+
+        if (matchExpressionItems != null && !matchExpressionItems.isEmpty()) {
+            for (MatchExpressionItem matchExpressionItem : matchExpressionItems) {
+                if (sb.length() > 0) {
+                    sb.append(",");
+                }
+                sb.append(matchExpressionItem.toJsonString());
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String toJsonString(Map<String, String> labels) {
+        StringBuilder result = new StringBuilder();
+        for (Map.Entry<String, String> label : labels.entrySet()) {
+            if (result.length() > 0) {
+                result.append(",");
+            }
+            result.append(toJsonString(label.getKey(), label.getValue()));
+        }
+        return result.toString();
+    }
+
+    public static String toJsonString(String key, String value) {
+        return key + "=" + value;
+    }
+
+}

+ 105 - 0
src/main/java/com/seecoder/config/K8sApiConfiguration.java

@@ -0,0 +1,105 @@
+package com.seecoder.config;
+
+import com.seecoder.config.property.ApplicationProperties;
+import io.fabric8.kubernetes.client.ConfigBuilder;
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
+import io.kubernetes.client.Exec;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.apis.AppsV1Api;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+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.Configuration;
+
+@Configuration
+public class K8sApiConfiguration {
+    private final ApiClient apiClient;
+
+    private final CoreV1Api coreV1Api;
+
+    private final AppsV1Api appsV1Api;
+
+    private final ExtensionsV1beta1Api extensionsV1beta1Api;
+
+    private final Exec exec;
+
+    /**
+     * http读取的超时时间设置为7分钟
+     */
+    private final ApiClient watchApiClient;
+
+    private final CoreV1Api watchCoreV1Api;
+
+    private final AppsV1Api watchAppsV1Api;
+
+    private DefaultKubernetesClient fabricK8sClient;
+
+    @Autowired
+    public K8sApiConfiguration(ApplicationProperties applicationProperties){
+        ApplicationProperties.K8s k8s = applicationProperties.getK8s();
+        this.apiClient = Config.fromToken(k8s.getApiServer(), k8s.getToken(), k8s.isValidateSSL());
+        this.apiClient.setDebugging(k8s.isDebug());
+        io.kubernetes.client.openapi.Configuration.setDefaultApiClient(this.apiClient);
+        this.coreV1Api = new CoreV1Api();
+        this.appsV1Api = new AppsV1Api();
+        this.extensionsV1beta1Api = new ExtensionsV1beta1Api();
+        this.exec = new Exec();
+
+
+        this.watchApiClient = Config.fromToken(k8s.getApiServer(), k8s.getToken(), k8s.isValidateSSL());
+        this.watchCoreV1Api = new CoreV1Api(watchApiClient);
+        this.watchAppsV1Api = new AppsV1Api(watchApiClient);
+
+        // fabric client
+        io.fabric8.kubernetes.client.Config config = new ConfigBuilder().withMasterUrl(k8s.getApiServer()).withOauthToken(k8s.getToken()).withTrustCerts(k8s.isValidateSSL()).build();
+        this.fabricK8sClient = new DefaultKubernetesClient(config);
+    }
+
+    @Bean
+    public DefaultKubernetesClient fabricK8sClient() {
+        return this.fabricK8sClient;
+    }
+
+    @Bean
+    public ApiClient apiClient() {
+        return apiClient;
+    }
+
+    @Bean
+    public CoreV1Api coreV1Api() {
+        return coreV1Api;
+    }
+
+    @Bean
+    public AppsV1Api appsV1Api() {
+        return appsV1Api;
+    }
+
+    @Bean
+    public ExtensionsV1beta1Api extensionsV1beta1Api() {
+        return extensionsV1beta1Api;
+    }
+
+    @Bean
+    public Exec exec() {
+        return exec;
+    }
+
+
+    @Bean
+    public ApiClient watchApiClient() {
+        return watchApiClient;
+    }
+
+    @Bean
+    public CoreV1Api watchCoreV1Api() {
+        return watchCoreV1Api;
+    }
+
+    @Bean
+    public AppsV1Api watchAppsV1Api() {
+        return watchAppsV1Api;
+    }
+}

+ 22 - 0
src/main/java/com/seecoder/config/property/AliProperties.java

@@ -0,0 +1,22 @@
+package com.seecoder.config.property;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties("aliyun")
+public class AliProperties {
+    private Profile profile;
+
+    @Data
+    public static class Profile {
+        private String accessKeyId;
+        private String accessKeySecret;
+        private String regionId;
+        private String sysDomain;
+        private String sysVersion;
+        private String signName;
+        private String templateCode;
+    }
+
+}

+ 50 - 0
src/main/java/com/seecoder/config/property/ApplicationProperties.java

@@ -0,0 +1,50 @@
+package com.seecoder.config.property;
+
+import lombok.Data;
+import org.checkerframework.checker.units.qual.K;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+@Configuration
+@ConfigurationProperties("unified")
+@Validated
+public class ApplicationProperties {
+
+    private LocalStorage localStorage = new LocalStorage();
+
+    private OssStorage ossStorage = new OssStorage();
+
+    private K8s k8s = new K8s();
+
+    private String deployNamespace;
+    @Data
+    public static class K8s {
+
+        private String apiServer;
+
+        private String token;
+
+        private boolean validateSSL = false;
+
+        private boolean debug = true;
+
+        private String imageRegistry;
+
+        private String secretName;
+    }
+
+    @Data
+    public static class LocalStorage {
+        private Map<String, String> properties = new HashMap<>();
+    }
+
+    @Data
+    public static class OssStorage {
+        private Map<String, String> properties = new HashMap<>();
+    }
+}

+ 11 - 0
src/main/java/com/seecoder/constant/ConfigConstant.java

@@ -0,0 +1,11 @@
+package com.seecoder.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ConfigConstant {
+    public static final Map<String,String> DEFAULT_CONFIGMAP_LABELS = new HashMap<>();
+    static {
+        DEFAULT_CONFIGMAP_LABELS.put("spring.cloud.kubernetes.config","true");
+    }
+}

+ 76 - 0
src/main/java/com/seecoder/controller/ConfigController.java

@@ -0,0 +1,76 @@
+package com.seecoder.controller;
+
+import com.seecoder.client.k8s.ConfigMapApi;
+import com.seecoder.client.k8s.model.ConfigMap;
+import com.seecoder.client.k8s.model.K8sObjectRequest;
+import com.seecoder.dao.ConfigHistoryDAO;
+import com.seecoder.entity.bo.ConfigBO;
+import com.seecoder.entity.po.ConfigHistory;
+import com.seecoder.service.ConfigService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.seecoder.constant.ConfigConstant.DEFAULT_CONFIGMAP_LABELS;
+
+@RestController
+@RequestMapping("/config")
+@RequiredArgsConstructor
+public class ConfigController {
+    @Autowired
+    ConfigMapApi configMapApi;
+    @Autowired
+    ConfigService configService;
+    @Autowired
+    ConfigHistoryDAO configHistoryDAO;
+
+    @PostMapping("/get")
+    public List<ConfigMap> get(@RequestBody K8sObjectRequest request) {
+        Map<String, String> requestLabels = request.getLabels();
+        if (requestLabels == null) {
+            request.setLabels(new HashMap<>(DEFAULT_CONFIGMAP_LABELS));
+        }
+        return configMapApi.getByCondition(request);
+    }
+
+    @GetMapping("/test")
+    public void test() {
+        ConfigHistory history = configHistoryDAO.findById(1L).orElse(null);
+        System.out.println(history);
+    }
+
+    @GetMapping("/get")
+    public List<ConfigBO> getByName(@RequestParam @Nullable String serviceName) {
+        return configService.getConfigByName(serviceName);
+    }
+
+    @PostMapping("/create")
+    public ConfigBO create(@RequestBody ConfigBO configBO) {
+        return configService.createConfig(configBO);
+    }
+
+    @PostMapping("/update")
+    public ConfigBO update(@RequestBody ConfigBO configBO) {
+        return configService.updateConfig(configBO);
+    }
+
+    @GetMapping("/delete")
+    public void delete(@RequestParam String serviceName) {
+        configService.deleteConfig(serviceName);
+    }
+
+    @GetMapping("/history")
+    public List<ConfigHistory> getHistoryByName(@RequestParam @Nullable String serviceName) {
+        return configService.getHistoryByName(serviceName);
+    }
+
+    @GetMapping("/rollback")
+    public void rollback(@RequestParam String serviceName,@RequestParam Long historyId) {
+        configService.rollback(serviceName,historyId);
+    }
+}

+ 48 - 0
src/main/java/com/seecoder/controller/CourseController.java

@@ -0,0 +1,48 @@
+package com.seecoder.controller;
+
+
+import com.seecoder.commoncore.util.Response;
+import com.seecoder.entity.po.Course;
+import com.seecoder.service.CourseService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/course")
+@RequiredArgsConstructor
+public class CourseController {
+    final private CourseService courseService;
+
+    @PostMapping("/create")
+    Response<Course> create(Course course) {
+        return Response.ok(courseService.create(course));
+    }
+
+    @PostMapping("/delete")
+    Response<Void> delete(Long id) {
+        courseService.delete(id);
+        return Response.ok();
+    }
+
+    @PostMapping("/update")
+    Response<Course> update(Course course) {
+        return Response.ok(courseService.update(course));
+    }
+
+    @GetMapping("/getById")
+    Response<Course> getById(Long id) {
+        return Response.ok(courseService.getById(id));
+    }
+
+    @PostMapping("/getByParams")
+    Response<List<Course>> getByParams(Course course) {
+        return Response.ok(courseService.getByParams(course));
+    }
+
+
+}

+ 60 - 0
src/main/java/com/seecoder/controller/FileController.java

@@ -0,0 +1,60 @@
+package com.seecoder.controller;
+
+import com.seecoder.commoncore.util.Response;
+import com.seecoder.storage.impl.LocalStorageProvider;
+import com.seecoder.storage.impl.OssStorageProvider;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+
+@RestController
+@RequestMapping("/file")
+@RequiredArgsConstructor
+public class FileController {
+    private final OssStorageProvider ossStorageProvider;
+    private final LocalStorageProvider localStorageProvider;
+
+    @PostMapping("/oss")
+    public Response<Void> saveByOss(byte[] content, String name) {
+        try {
+            ossStorageProvider.save(content, name);
+        } catch (IOException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+        return Response.ok();
+    }
+
+    @GetMapping("/oss")
+    public byte[] fetchByOss(String name) {
+        try {
+            return ossStorageProvider.fetch(name);
+        } catch (IOException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    @PostMapping("/local")
+    public Response<Void> saveByLocal(byte[] content, String name) {
+        try {
+            localStorageProvider.save(content, name);
+        } catch (IOException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+        return Response.ok();
+    }
+
+    @GetMapping("/local")
+    public byte[] fetchByLocal(String name) {
+        try {
+            return localStorageProvider.fetch(name);
+        } catch (IOException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+
+}

+ 28 - 0
src/main/java/com/seecoder/controller/MessageController.java

@@ -0,0 +1,28 @@
+package com.seecoder.controller;
+
+import com.seecoder.commoncore.util.Response;
+import com.seecoder.entity.req.SendByMailReq;
+import com.seecoder.entity.req.SendByPhoneReq;
+import com.seecoder.service.MessageService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+
+@RestController
+@RequestMapping("/message")
+@RequiredArgsConstructor
+public class MessageController {
+    private final MessageService messageService;
+
+    @PostMapping("/phone")
+    public Response<Void> sendByPhone(@RequestBody SendByPhoneReq req) {
+        messageService.sendByPhone(req.getPhone(), req.getText());
+        return Response.ok();
+    }
+
+    @PostMapping("/mail")
+    public Response<Void> sendByMail(@RequestBody SendByMailReq req) {
+        messageService.sendMail(req.getReceivers(), req.getSubject(), req.getText(),req.getIsHtml());
+        return Response.ok();
+    }
+}

+ 12 - 0
src/main/java/com/seecoder/dao/ConfigHistoryDAO.java

@@ -0,0 +1,12 @@
+package com.seecoder.dao;
+
+import com.seecoder.entity.po.ConfigHistory;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface ConfigHistoryDAO extends JpaRepository<ConfigHistory, Long> {
+    public List<ConfigHistory> findByServiceNameOrderByModifyTimeDesc(String serviceName);
+
+    public ConfigHistory findFirstByServiceNameOrderByModifyTimeDesc(String serviceName);
+}

+ 7 - 0
src/main/java/com/seecoder/dao/CourseDAO.java

@@ -0,0 +1,7 @@
+package com.seecoder.dao;
+
+import com.seecoder.entity.po.Course;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface CourseDAO extends JpaRepository<Course,Long> {
+}

+ 7 - 0
src/main/java/com/seecoder/dao/StudentCourseDAO.java

@@ -0,0 +1,7 @@
+package com.seecoder.dao;
+
+import com.seecoder.entity.po.Course;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface StudentCourseDAO extends JpaRepository<Course,Long> {
+}

+ 24 - 0
src/main/java/com/seecoder/entity/bo/ConfigBO.java

@@ -0,0 +1,24 @@
+package com.seecoder.entity.bo;
+
+import com.seecoder.client.k8s.model.ConfigMap;
+import com.seecoder.config.property.ApplicationProperties;
+import com.seecoder.constant.ConfigConstant;
+import lombok.Data;
+import org.checkerframework.checker.units.qual.C;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Map;
+
+@Data
+public class ConfigBO {
+    String name;
+    Map<String, String> data;
+
+    public ConfigBO() {
+    }
+
+    public ConfigBO(ConfigMap configMap) {
+        this.name = configMap.getName();
+        this.data = configMap.getData();
+    }
+}

+ 42 - 0
src/main/java/com/seecoder/entity/po/ConfigHistory.java

@@ -0,0 +1,42 @@
+package com.seecoder.entity.po;
+
+import com.seecoder.enums.ConfigModifiedTypeEnums;
+import lombok.*;
+import org.hibernate.annotations.DynamicUpdate;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "config_history")
+@ToString
+@EqualsAndHashCode
+@DynamicUpdate
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class ConfigHistory {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Long id;
+
+    @Column(name = "service_name")
+    private String serviceName;
+
+    private String content;
+
+    @Column(name = "modified_at")
+    private LocalDateTime modifyTime;
+
+    @Column(name = "modified_by")
+    private Long modifyUserId;
+
+    @Column(name = "modified_type")
+    private ConfigModifiedTypeEnums modifyType;
+
+    @Column(name = "history_id")
+    private Long historyId;
+
+}

+ 91 - 0
src/main/java/com/seecoder/entity/po/Course.java

@@ -0,0 +1,91 @@
+package com.seecoder.entity.po;
+
+
+import com.seecoder.enums.CourseStatusTypeEnums;
+import com.seecoder.enums.CourseTypeEnums;
+import lombok.*;
+import org.hibernate.annotations.DynamicUpdate;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "course")
+@ToString
+@EqualsAndHashCode
+@DynamicUpdate
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class Course {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Long id;
+
+    /**
+     * 课程名
+     */
+    private String name;
+
+    /**
+     * 选课码
+     */
+    private String code;
+
+    /**
+     * 类型
+     */
+    private CourseTypeEnums type;
+
+    /**
+     * 状态
+     */
+    private CourseStatusTypeEnums status;
+
+    private LocalDateTime startTime;
+
+    private LocalDateTime endTime;
+
+    /**
+     * 价格,默认为0
+     */
+    private Double price;
+
+    @Column(name = "created_at")
+    private LocalDateTime createTime;
+    @Column(name = "modified_at")
+    private LocalDateTime modifiedTime;
+
+    public void copyNotNullProperties(Course course){
+        if(course.getId()!=null){
+            this.setId(course.getId());
+        }
+        if(course.getName()!=null){
+            this.setName(course.getName());
+        }
+        if(course.getPrice()!=null){
+            this.setPrice(course.getPrice());
+        }
+        if(course.getCreateTime()!=null){
+            this.setCreateTime(course.getCreateTime());
+        }
+        if(course.getModifiedTime()!=null){
+            this.setModifiedTime(course.getModifiedTime());
+        }
+        if(course.getType()!=null){
+            this.setType(course.getType());
+        }
+        if(course.getStatus()!=null){
+            this.setStatus(course.getStatus());
+        }
+        if(course.getStartTime()!=null){
+            this.setStartTime(course.getStartTime());
+        }
+        if(course.getEndTime()!=null){
+            this.setEndTime(course.getEndTime());
+        }
+
+    }
+}

+ 14 - 0
src/main/java/com/seecoder/entity/req/SendByMailReq.java

@@ -0,0 +1,14 @@
+package com.seecoder.entity.req;
+
+import lombok.Data;
+
+import java.util.Set;
+
+@Data
+public class SendByMailReq {
+    Set<String> receivers;
+    String subject;
+    String text;
+    Boolean isHtml;
+
+}

+ 12 - 0
src/main/java/com/seecoder/entity/req/SendByPhoneReq.java

@@ -0,0 +1,12 @@
+package com.seecoder.entity.req;
+
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class SendByPhoneReq {
+    String phone;
+    String text;
+    Map<String,String> params;
+}

+ 11 - 0
src/main/java/com/seecoder/enums/ConfigModifiedTypeEnums.java

@@ -0,0 +1,11 @@
+package com.seecoder.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum ConfigModifiedTypeEnums {
+    CREATE(),
+    UPDATE(),
+    DELETE(),
+    ROLLBACK()
+}

+ 6 - 0
src/main/java/com/seecoder/enums/CourseStatusTypeEnums.java

@@ -0,0 +1,6 @@
+package com.seecoder.enums;
+
+public enum CourseStatusTypeEnums {
+    OFF(),
+    ON()
+}

+ 6 - 0
src/main/java/com/seecoder/enums/CourseTypeEnums.java

@@ -0,0 +1,6 @@
+package com.seecoder.enums;
+
+public enum CourseTypeEnums {
+    PUBLIC(),
+    INTERNAL()
+}

+ 20 - 0
src/main/java/com/seecoder/service/ConfigService.java

@@ -0,0 +1,20 @@
+package com.seecoder.service;
+
+import com.seecoder.entity.bo.ConfigBO;
+import com.seecoder.entity.po.ConfigHistory;
+
+import java.util.List;
+
+public interface ConfigService {
+    ConfigBO createConfig(ConfigBO configBO);
+
+    ConfigBO updateConfig(ConfigBO configBO);
+
+    void deleteConfig(String serviceName);
+
+    List<ConfigBO> getConfigByName(String serviceName);
+
+    List<ConfigHistory> getHistoryByName(String serviceName);
+
+    void rollback(String serviceName,Long historyId);
+}

+ 14 - 0
src/main/java/com/seecoder/service/CourseService.java

@@ -0,0 +1,14 @@
+package com.seecoder.service;
+
+import com.seecoder.entity.po.Course;
+
+import java.util.List;
+
+public interface CourseService {
+    Course create(Course course);
+    void delete(Long id);
+    Course update(Course course);
+
+    List<Course> getByParams(Course course);
+    Course getById(Long id);
+}

+ 10 - 0
src/main/java/com/seecoder/service/MessageService.java

@@ -0,0 +1,10 @@
+package com.seecoder.service;
+
+import java.util.Set;
+
+public interface MessageService {
+    void sendByPhone(String phone,String text);
+
+    void sendMail(Set<String> receivers, String subject, String text,Boolean isHtml);
+
+}

+ 132 - 0
src/main/java/com/seecoder/service/impl/ConfigServiceImpl.java

@@ -0,0 +1,132 @@
+package com.seecoder.service.impl;
+
+import com.seecoder.client.k8s.ConfigMapApi;
+import com.seecoder.client.k8s.model.ConfigMap;
+import com.seecoder.client.k8s.model.K8sObjectRequest;
+import com.seecoder.config.property.ApplicationProperties;
+import com.seecoder.constant.ConfigConstant;
+import com.seecoder.dao.ConfigHistoryDAO;
+import com.seecoder.entity.bo.ConfigBO;
+import com.seecoder.entity.po.ConfigHistory;
+import com.seecoder.enums.ConfigModifiedTypeEnums;
+import com.seecoder.service.ConfigService;
+import lombok.RequiredArgsConstructor;
+import org.checkerframework.checker.units.qual.C;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.seecoder.constant.ConfigConstant.DEFAULT_CONFIGMAP_LABELS;
+
+@Service
+@RequiredArgsConstructor
+public class ConfigServiceImpl implements ConfigService {
+    private final ConfigMapApi configMapApi;
+    private final ApplicationProperties properties;
+    private final ConfigHistoryDAO configHistoryDAO;
+
+
+    @Override
+    public ConfigBO createConfig(ConfigBO configBO) {
+        ConfigMap configMap = buildConfigMap(configBO);
+        configMapApi.create(configMap);
+        saveConfigHistory(configBO.getName(), configBO.getData(), null, ConfigModifiedTypeEnums.CREATE,null);
+        return configBO;
+    }
+
+    @Override
+    public ConfigBO updateConfig(ConfigBO configBO) {
+        ConfigMap configMap = buildConfigMap(configBO);
+        configMapApi.update(configMap);
+        ConfigHistory configHistory = configHistoryDAO.findFirstByServiceNameOrderByModifyTimeDesc(configBO.getName());
+        saveConfigHistory(configBO.getName(), configBO.getData(), null, ConfigModifiedTypeEnums.UPDATE,configHistory.getId());
+        return configBO;
+    }
+
+    private ConfigMap buildConfigMap(ConfigBO configBO) {
+        ConfigMap configMap = new ConfigMap();
+        configMap.setName(configBO.getName());
+        configMap.setNamespace(properties.getDeployNamespace());
+        configMap.setLabels(ConfigConstant.DEFAULT_CONFIGMAP_LABELS);
+        configMap.setData(configBO.getData());
+        return configMap;
+    }
+
+    @Override
+    public void deleteConfig(String serviceName) {
+        ConfigMap configMap = new ConfigMap();
+        configMap.setName(serviceName);
+        configMap.setNamespace(properties.getDeployNamespace());
+        configMapApi.delete(configMap);
+        ConfigHistory configHistory = configHistoryDAO.findFirstByServiceNameOrderByModifyTimeDesc(serviceName);
+        saveConfigHistory(serviceName, null, null, ConfigModifiedTypeEnums.DELETE,configHistory.getId());
+    }
+
+    @Override
+    public List<ConfigBO> getConfigByName(String serviceName) {
+        K8sObjectRequest request = K8sObjectRequest.builder().name(serviceName).namespace(properties.getDeployNamespace()).build();
+        if (request.getLabels() == null) {
+            request.setLabels(new HashMap<>(DEFAULT_CONFIGMAP_LABELS));
+        } else {
+            request.getLabels().putAll(DEFAULT_CONFIGMAP_LABELS);
+        }
+        List<ConfigMap> configMapList = configMapApi.getByCondition(request);
+        List<ConfigBO> res = new ArrayList<>();
+        for (ConfigMap configMap : configMapList) {
+            ConfigBO bo = new ConfigBO();
+            bo.setName(configMap.getName());
+            bo.setData(configMap.getData());
+            res.add(bo);
+        }
+        return res;
+    }
+
+    @Override
+    public List<ConfigHistory> getHistoryByName(String serviceName) {
+        return configHistoryDAO.findByServiceNameOrderByModifyTimeDesc(serviceName);
+    }
+
+    @Override
+    public void rollback(String serviceName, Long historyId) {
+        ConfigHistory configHistory = configHistoryDAO.findById(historyId).orElseThrow(() -> new RuntimeException("找不到对应的历史记录"));
+        ConfigBO configBO = new ConfigBO();
+        configBO.setName(serviceName);
+        ObjectMapper mapper = new ObjectMapper();
+        Map<String, String> content;
+        try {
+            content = mapper.readValue(configHistory.getContent(), Map.class);
+            configBO.setData(content);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        ConfigMap configMap = buildConfigMap(configBO);
+        configMapApi.update(configMap);
+        saveConfigHistory(serviceName, content, null, ConfigModifiedTypeEnums.ROLLBACK,historyId);
+    }
+
+    private void saveConfigHistory(String serviceName, Map<String, String> content, Long userId, ConfigModifiedTypeEnums type,Long historyId) {
+        ConfigHistory configHistory = new ConfigHistory();
+        configHistory.setServiceName(serviceName);
+        configHistory.setModifyTime(LocalDateTime.now());
+        configHistory.setModifyUserId(userId);
+        configHistory.setModifyType(type);
+        configHistory.setHistoryId(historyId);
+        ObjectMapper mapper = new ObjectMapper();
+        String data = null;
+        if (content != null) {
+            try {
+                data = mapper.writeValueAsString(content);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        configHistory.setContent(data);
+        configHistoryDAO.save(configHistory);
+    }
+}

+ 49 - 0
src/main/java/com/seecoder/service/impl/CourseServiceImpl.java

@@ -0,0 +1,49 @@
+package com.seecoder.service.impl;
+
+import com.seecoder.dao.CourseDAO;
+import com.seecoder.entity.po.Course;
+import com.seecoder.service.CourseService;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Example;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+@AllArgsConstructor
+public class CourseServiceImpl implements CourseService {
+    private final CourseDAO courseDAO;
+
+    @Override
+    public Course create(Course course) {
+        return courseDAO.save(course);
+    }
+
+    @Override
+    public void delete(Long id) {
+        courseDAO.deleteById(id);
+    }
+
+    @Override
+    public Course update(Course course) {
+        if (course == null) {
+            return null;
+        }
+        Course res = courseDAO.findById(course.getId()).orElseThrow(() -> new RuntimeException("找不到课程"));
+        res.copyNotNullProperties(course);
+        return courseDAO.save(res);
+    }
+
+
+    @Override
+    public List<Course> getByParams(Course course) {
+        return courseDAO.findAll(Example.of(course));
+    }
+
+    @Override
+    public Course getById(Long id) {
+        return courseDAO.findById(id).orElse(null);
+    }
+
+
+}

+ 46 - 0
src/main/java/com/seecoder/service/impl/MessageServiceImpl.java

@@ -0,0 +1,46 @@
+package com.seecoder.service.impl;
+
+import com.seecoder.service.MessageService;
+import com.seecoder.client.alibaba.AliService;
+import lombok.AllArgsConstructor;
+import org.springframework.core.env.Environment;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Service;
+
+import javax.mail.internet.MimeMessage;
+import java.util.Date;
+import java.util.Objects;
+import java.util.Set;
+
+@Service
+@AllArgsConstructor
+public class MessageServiceImpl implements MessageService {
+    private final JavaMailSender javaMailSender;
+    private final Environment environment;
+    private final AliService aliService;
+
+    @Override
+    public void sendMail(Set<String> receivers, String subject, String text, Boolean isHtml) {
+        MimeMessage message = javaMailSender.createMimeMessage();
+        try{
+            MimeMessageHelper helper = new MimeMessageHelper(message,false);
+            helper.setSubject(subject);
+            helper.setFrom(Objects.requireNonNull(environment.getProperty("spring.mail.username")));
+            helper.setTo(receivers.toArray(new String[0]));
+            helper.setSentDate(new Date());
+            helper.setText(text, isHtml == null || isHtml);
+            // 发送邮件
+            javaMailSender.send(message);
+        }catch (Exception e){
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    @Override
+    public void sendByPhone(String phone, String text) {
+        aliService.sendMessage(phone,text);
+    }
+}

+ 12 - 0
src/main/java/com/seecoder/storage/StorageProvider.java

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

+ 36 - 0
src/main/java/com/seecoder/storage/StorageProviderConfiguration.java

@@ -0,0 +1,36 @@
+package com.seecoder.storage;
+
+import com.seecoder.config.property.ApplicationProperties;
+import com.seecoder.storage.impl.LocalStorageProvider;
+import com.seecoder.storage.impl.OssStorageProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.IOException;
+
+@Configuration
+public class StorageProviderConfiguration {
+	private final ApplicationProperties properties;
+
+	public StorageProviderConfiguration(ApplicationProperties properties) {
+		this.properties = properties;
+	}
+
+	@Bean
+	public OssStorageProvider ossStorageProvider() throws IOException {
+		ApplicationProperties.OssStorage storage = properties.getOssStorage();
+		OssStorageProvider storageProvider = new OssStorageProvider();
+		storageProvider.initialize(storage.getProperties());
+		return storageProvider;
+	}
+
+	@Bean
+	public LocalStorageProvider localStorageProvider() throws IOException {
+		ApplicationProperties.LocalStorage storage = properties.getLocalStorage();
+		LocalStorageProvider storageProvider = new LocalStorageProvider();
+		storageProvider.initialize(storage.getProperties());
+		return storageProvider;
+	}
+
+
+}

+ 43 - 0
src/main/java/com/seecoder/storage/impl/LocalStorageProvider.java

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

+ 79 - 0
src/main/java/com/seecoder/storage/impl/OssStorageProvider.java

@@ -0,0 +1,79 @@
+package com.seecoder.storage.impl;
+
+import com.aliyun.oss.ClientConfiguration;
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.ServiceException;
+import com.aliyun.oss.common.auth.CredentialsProvider;
+import com.aliyun.oss.common.auth.DefaultCredentialProvider;
+import com.aliyun.oss.model.GetObjectRequest;
+import com.aliyun.oss.model.OSSObject;
+import com.aliyun.oss.model.ObjectMetadata;
+import com.aliyun.oss.model.PutObjectRequest;
+import com.seecoder.storage.StorageProvider;
+import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.springframework.util.Assert;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Map;
+
+public class OssStorageProvider implements StorageProvider {
+	private OSSClient client;
+	private String bucket;
+
+	@Override
+	public void initialize(Map<String, String> properties) throws IOException {
+		String endpoint = properties.get("endpoint");
+		String accessKeyId = properties.get("access-key-id");
+		String accessKeySecret = properties.get("access-key-secret");
+		String useCname = properties.get("use-cname");
+		bucket = properties.get("bucket");
+		Assert.notNull(endpoint, "未提供阿里云OSS的Endpoint!");
+		Assert.notNull(accessKeyId, "未提供阿里云OSS的AccessKeyId!");
+		Assert.notNull(accessKeySecret, "未提供阿里云OSS的AccessKeySecret!");
+		Assert.notNull(bucket, "未提供阿里云OSS的Bucket!");
+		CredentialsProvider provider = new DefaultCredentialProvider(accessKeyId, accessKeySecret);
+		ClientConfiguration config = new ClientConfiguration();
+		config.setSupportCname("true".equals(useCname));
+		client = new OSSClient(endpoint, provider, config);
+		System.out.println("OssStorage 初始化成功!");
+		try {
+			Assert.isTrue(client.doesBucketExist(bucket), "Bucket [" + bucket + "] 不存在!");
+		} catch (ServiceException | ClientException e) {
+			throw new IOException(e);
+		}
+	}
+
+	@Override
+	public void save(byte[] content, String name) throws IOException {
+		ObjectMetadata metadata = new ObjectMetadata();
+		metadata.setContentLength(content.length);
+		PutObjectRequest request = new PutObjectRequest(bucket, name, new ByteArrayInputStream(content), metadata);
+		try {
+			client.putObject(request);
+		} catch (ServiceException | ClientException e) {
+			throw new IOException(e);
+		}
+	}
+
+	@Override
+	public byte[] fetch(String name) throws IOException {
+		GetObjectRequest request = new GetObjectRequest(bucket, name);
+		OSSObject object;
+		try {
+			object = client.getObject(request);
+		} catch (ServiceException e) {
+			if ("NoSuchKey".equals(e.getErrorCode())) {
+				throw new FileNotFoundException(name);
+			}
+			throw new IOException(e);
+		} catch (ClientException e) {
+			throw new IOException(e);
+		}
+		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+		outputStream.write(object.getObjectContent());
+		return outputStream.toByteArray();
+	}
+}

+ 42 - 0
src/main/java/com/seecoder/util/LoggerUtil.java

@@ -0,0 +1,42 @@
+package com.seecoder.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * author: rale
+ * createdAt: 12/22/18
+ * 日志工具
+ */
+public class LoggerUtil {
+
+    public static Logger getLogger(Class<?> className){
+        return LoggerFactory.getLogger(className);
+    }
+
+    public static void info(Logger logger, String info, Object... parameters){
+        logger.info(info, parameters);
+    }
+
+    public static void warn(Logger logger, String info, Object... parameters){
+        logger.warn(info, parameters);
+    }
+
+    public static void warn(Logger logger, Exception e, String info, Object... parameters) {
+        logger.warn("", e);
+        logger.warn(info, parameters);
+    }
+
+    public static void  error(Logger logger, String info, Object... parameters) {
+        logger.error(info, parameters);
+    }
+
+    public static void error(Logger logger, Exception e, String info, Object... parameters) {
+        logger.error("", e);
+        logger.error(info, parameters);
+    }
+
+    public static void debug(Logger logger, String info, Object... parameters) {
+        logger.debug(info, parameters);
+    }
+}

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

@@ -0,0 +1,67 @@
+server:
+  # 端口
+  port: 8090
+
+spring:
+  datasource:
+    url: jdbc:mysql://106.14.123.58:3306/micro?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
+    username: root
+    password: NJU67seecoder
+    driverClassName: com.mysql.cj.jdbc.Driver
+  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
+    #    show-sql: true
+    properties:
+      hibernate.enable_lazy_load_no_trans: true
+      #      hibernate.format_sql: true
+    open-in-view: true
+  mail:
+    host: smtp.163.com
+    username: 15915185699@163.com
+    password: SPNQLFBOCRIGFJZD
+    default-encoding: UTF-8
+    protocol: smtp
+    properties:
+      mail.smtp.auth: true
+      mail.smtp.starttls.enable: true
+      mail.smtp.starttls.required: true
+      mail.smtp.socketFactory.port: 465
+      mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
+      mail.smtp.socketFactory.fallback: false
+
+aliyun:
+  profile:
+    access-key-id: LTAI5t5ZwNZ6a4ptUXuiy7of
+    access-key-secret: zC4VEHInTiGDNWGakVvkUPrZSzS1P2
+    region-id: cn-hangzhou
+    sys-domain: dysmsapi.aliyuncs.com
+    sys-version: 2017-05-25
+    sign-name: SEECODER
+    template-code: SMS_253920073
+
+unified:
+  local-storage:
+    properties:
+      location: ./problem
+  oss-storage:
+    properties:
+      endpoint: https://oss-cn-hangzhou.aliyuncs.com
+      access-key-id: LTAI4Fi1qmL4iuhH8t7G7r2H
+      access-key-secret: 7tJWyQqa0cMkQGaPMMvtH6c0VLiOO2
+      bucket: seec-portal
+  k8s:
+    api-server: https://101.133.232.246:6443
+    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJzZWVjIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNlZWMtYWRtaW4tdG9rZW4tZ3hra2wiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoic2VlYy1hZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImMxYWFiNzcyLTI2Y2YtMTFlYi04ZmJjLTAwMTYzZTIzMzQzMyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpzZWVjOnNlZWMtYWRtaW4ifQ.Y6T5sz8FTNIzDhTacpAZvQ0iP2LZhqeMmagw3SiRiAvIVNHjPZK5QdLu1xFiz7UZRcCxh7FpUtBsKDSU9RBwQEJtt-8ZVDr-RAMwuj4c1tRimAmKDm6FiQ-Fl6eLx-3dyC6-bwgJS2rr3miFPRX-E-mqpBsBqwuughaqek6OG7dXf5tiulE7gpEXrGs2DZU8B8Fwmp_5SBz7Kpxg6AdKbCka2quNOofq_pymButwGJl8aZuZtmpeHHkydfHDrxzjiDb_41Cpx8byPnDvf-y7-zsn7eR1K55PrmQYl0Ye6mAUm9prSE8xpSgyWzW_jpfuH_aMod7E7D8syvB00GE-IA
+    validateSSL: false
+    debug: false
+    imageRegistry: nexus.seecoder.cn:8092
+    secretName: seecoder-pass
+  deploy-namespace: micro
+
+
+

+ 119 - 0
target/classes/META-INF/spring-configuration-metadata.json

@@ -0,0 +1,119 @@
+{
+  "groups": [
+    {
+      "name": "aliyun",
+      "type": "com.seecoder.config.property.AliProperties",
+      "sourceType": "com.seecoder.config.property.AliProperties"
+    },
+    {
+      "name": "aliyun.profile",
+      "type": "com.seecoder.config.property.AliProperties$Profile",
+      "sourceType": "com.seecoder.config.property.AliProperties"
+    },
+    {
+      "name": "unified",
+      "type": "com.seecoder.config.property.ApplicationProperties",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties"
+    },
+    {
+      "name": "unified.k8s",
+      "type": "com.seecoder.config.property.ApplicationProperties$K8s",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties"
+    },
+    {
+      "name": "unified.local-storage",
+      "type": "com.seecoder.config.property.ApplicationProperties$LocalStorage",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties"
+    },
+    {
+      "name": "unified.oss-storage",
+      "type": "com.seecoder.config.property.ApplicationProperties$OssStorage",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties"
+    }
+  ],
+  "properties": [
+    {
+      "name": "aliyun.profile.access-key-id",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.AliProperties$Profile"
+    },
+    {
+      "name": "aliyun.profile.access-key-secret",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.AliProperties$Profile"
+    },
+    {
+      "name": "aliyun.profile.region-id",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.AliProperties$Profile"
+    },
+    {
+      "name": "aliyun.profile.sign-name",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.AliProperties$Profile"
+    },
+    {
+      "name": "aliyun.profile.sys-domain",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.AliProperties$Profile"
+    },
+    {
+      "name": "aliyun.profile.sys-version",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.AliProperties$Profile"
+    },
+    {
+      "name": "aliyun.profile.template-code",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.AliProperties$Profile"
+    },
+    {
+      "name": "unified.deploy-namespace",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties"
+    },
+    {
+      "name": "unified.k8s.api-server",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties$K8s"
+    },
+    {
+      "name": "unified.k8s.debug",
+      "type": "java.lang.Boolean",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties$K8s",
+      "defaultValue": true
+    },
+    {
+      "name": "unified.k8s.image-registry",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties$K8s"
+    },
+    {
+      "name": "unified.k8s.secret-name",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties$K8s"
+    },
+    {
+      "name": "unified.k8s.token",
+      "type": "java.lang.String",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties$K8s"
+    },
+    {
+      "name": "unified.k8s.validate-s-s-l",
+      "type": "java.lang.Boolean",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties$K8s",
+      "defaultValue": false
+    },
+    {
+      "name": "unified.local-storage.properties",
+      "type": "java.util.Map<java.lang.String,java.lang.String>",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties$LocalStorage"
+    },
+    {
+      "name": "unified.oss-storage.properties",
+      "type": "java.util.Map<java.lang.String,java.lang.String>",
+      "sourceType": "com.seecoder.config.property.ApplicationProperties$OssStorage"
+    }
+  ],
+  "hints": []
+}

+ 67 - 0
target/classes/application.yml

@@ -0,0 +1,67 @@
+server:
+  # 端口
+  port: 8090
+
+spring:
+  datasource:
+    url: jdbc:mysql://106.14.123.58:3306/micro?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
+    username: root
+    password: NJU67seecoder
+    driverClassName: com.mysql.cj.jdbc.Driver
+  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
+    #    show-sql: true
+    properties:
+      hibernate.enable_lazy_load_no_trans: true
+      #      hibernate.format_sql: true
+    open-in-view: true
+  mail:
+    host: smtp.163.com
+    username: 15915185699@163.com
+    password: SPNQLFBOCRIGFJZD
+    default-encoding: UTF-8
+    protocol: smtp
+    properties:
+      mail.smtp.auth: true
+      mail.smtp.starttls.enable: true
+      mail.smtp.starttls.required: true
+      mail.smtp.socketFactory.port: 465
+      mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
+      mail.smtp.socketFactory.fallback: false
+
+aliyun:
+  profile:
+    access-key-id: LTAI5t5ZwNZ6a4ptUXuiy7of
+    access-key-secret: zC4VEHInTiGDNWGakVvkUPrZSzS1P2
+    region-id: cn-hangzhou
+    sys-domain: dysmsapi.aliyuncs.com
+    sys-version: 2017-05-25
+    sign-name: SEECODER
+    template-code: SMS_253920073
+
+unified:
+  local-storage:
+    properties:
+      location: ./problem
+  oss-storage:
+    properties:
+      endpoint: https://oss-cn-hangzhou.aliyuncs.com
+      access-key-id: LTAI4Fi1qmL4iuhH8t7G7r2H
+      access-key-secret: 7tJWyQqa0cMkQGaPMMvtH6c0VLiOO2
+      bucket: seec-portal
+  k8s:
+    api-server: https://101.133.232.246:6443
+    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJzZWVjIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNlZWMtYWRtaW4tdG9rZW4tZ3hra2wiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoic2VlYy1hZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImMxYWFiNzcyLTI2Y2YtMTFlYi04ZmJjLTAwMTYzZTIzMzQzMyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpzZWVjOnNlZWMtYWRtaW4ifQ.Y6T5sz8FTNIzDhTacpAZvQ0iP2LZhqeMmagw3SiRiAvIVNHjPZK5QdLu1xFiz7UZRcCxh7FpUtBsKDSU9RBwQEJtt-8ZVDr-RAMwuj4c1tRimAmKDm6FiQ-Fl6eLx-3dyC6-bwgJS2rr3miFPRX-E-mqpBsBqwuughaqek6OG7dXf5tiulE7gpEXrGs2DZU8B8Fwmp_5SBz7Kpxg6AdKbCka2quNOofq_pymButwGJl8aZuZtmpeHHkydfHDrxzjiDb_41Cpx8byPnDvf-y7-zsn7eR1K55PrmQYl0Ye6mAUm9prSE8xpSgyWzW_jpfuH_aMod7E7D8syvB00GE-IA
+    validateSSL: false
+    debug: false
+    imageRegistry: nexus.seecoder.cn:8092
+    secretName: seecoder-pass
+  deploy-namespace: micro
+
+
+

BIN
target/classes/com/seecoder/AppApplication.class


BIN
target/classes/com/seecoder/client/alibaba/AliService.class


BIN
target/classes/com/seecoder/client/k8s/AbstractApi.class


BIN
target/classes/com/seecoder/client/k8s/ConfigMapApi.class


BIN
target/classes/com/seecoder/client/k8s/K8sConstants.class


BIN
target/classes/com/seecoder/client/k8s/exception/K8sApiException.class


BIN
target/classes/com/seecoder/client/k8s/impl/ConfigMapApiImpl.class


BIN
target/classes/com/seecoder/client/k8s/model/ConfigMap$ConfigMapBuilder.class


BIN
target/classes/com/seecoder/client/k8s/model/ConfigMap.class


BIN
target/classes/com/seecoder/client/k8s/model/K8sAbstractObject.class


BIN
target/classes/com/seecoder/client/k8s/model/K8sObject.class


BIN
target/classes/com/seecoder/client/k8s/model/K8sObjectRequest$K8sObjectRequestBuilder.class


BIN
target/classes/com/seecoder/client/k8s/model/K8sObjectRequest.class


BIN
target/classes/com/seecoder/client/k8s/model/K8sObjectTransformer.class


BIN
target/classes/com/seecoder/client/k8s/model/LabelSelector$1.class


BIN
target/classes/com/seecoder/client/k8s/model/LabelSelector$MatchExpressionItem.class


BIN
target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$1.class


BIN
target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$2.class


BIN
target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$3.class


BIN
target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$4.class


BIN
target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator$5.class


BIN
target/classes/com/seecoder/client/k8s/model/LabelSelector$Operator.class


BIN
target/classes/com/seecoder/client/k8s/model/LabelSelector.class


BIN
target/classes/com/seecoder/config/K8sApiConfiguration.class


BIN
target/classes/com/seecoder/config/property/AliProperties$Profile.class


BIN
target/classes/com/seecoder/config/property/AliProperties.class


BIN
target/classes/com/seecoder/config/property/ApplicationProperties$K8s.class


BIN
target/classes/com/seecoder/config/property/ApplicationProperties$LocalStorage.class


BIN
target/classes/com/seecoder/config/property/ApplicationProperties$OssStorage.class


BIN
target/classes/com/seecoder/config/property/ApplicationProperties.class


BIN
target/classes/com/seecoder/constant/ConfigConstant.class


BIN
target/classes/com/seecoder/controller/ConfigController.class


BIN
target/classes/com/seecoder/controller/CourseController.class


BIN
target/classes/com/seecoder/controller/FileController.class


BIN
target/classes/com/seecoder/controller/MessageController.class


BIN
target/classes/com/seecoder/dao/ConfigHistoryDAO.class


BIN
target/classes/com/seecoder/dao/CourseDAO.class


BIN
target/classes/com/seecoder/dao/StudentCourseDAO.class


BIN
target/classes/com/seecoder/entity/bo/ConfigBO.class


BIN
target/classes/com/seecoder/entity/po/ConfigHistory$ConfigHistoryBuilder.class


BIN
target/classes/com/seecoder/entity/po/ConfigHistory.class


BIN
target/classes/com/seecoder/entity/po/Course$CourseBuilder.class


BIN
target/classes/com/seecoder/entity/po/Course.class


BIN
target/classes/com/seecoder/entity/req/SendByMailReq.class


BIN
target/classes/com/seecoder/entity/req/SendByPhoneReq.class


BIN
target/classes/com/seecoder/enums/ConfigModifiedTypeEnums.class


BIN
target/classes/com/seecoder/enums/CourseStatusTypeEnums.class


BIN
target/classes/com/seecoder/enums/CourseTypeEnums.class


BIN
target/classes/com/seecoder/service/ConfigService.class


BIN
target/classes/com/seecoder/service/CourseService.class


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels