deason 4 роки тому
батько
коміт
823ff8e808

+ 73 - 0
pom.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+         xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>cn.com.qmth.examcloud</groupId>
+    <artifactId>examcloud-oe-tool</artifactId>
+    <version>v1.0.1</version>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.1.18.RELEASE</version>
+    </parent>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</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>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>examcloud-oe-tool</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                    <skipTests>true</skipTests>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 11 - 0
shell/build.bat

@@ -0,0 +1,11 @@
+cd ..
+
+call mvn clean package spring-boot:repackage
+
+echo ...
+echo ***** ***** ***** ***** ***** *****
+echo java -jar target/examcloud-oe-tool.jar --examcloud.task.params=abc
+echo ***** ***** ***** ***** ***** *****
+echo ...
+
+pause

+ 15 - 0
src/main/java/cn/com/qmth/examcloud/tool/ExamCloudToolApplication.java

@@ -0,0 +1,15 @@
+package cn.com.qmth.examcloud.tool;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@EnableAsync
+@SpringBootApplication
+public class ExamCloudToolApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(ExamCloudToolApplication.class, args);
+    }
+
+}

+ 7 - 0
src/main/java/cn/com/qmth/examcloud/tool/task/Task.java

@@ -0,0 +1,7 @@
+package cn.com.qmth.examcloud.tool.task;
+
+public interface Task {
+
+    void start(String params);
+
+}

+ 32 - 0
src/main/java/cn/com/qmth/examcloud/tool/task/TaskConfig.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.tool.task;
+
+import cn.com.qmth.examcloud.tool.utils.SpringContextHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+
+@Configuration
+@EnableConfigurationProperties(TaskProperties.class)
+@ConditionalOnProperty(prefix = "examcloud.task", name = "active", havingValue = "true")
+public class TaskConfig {
+
+    private final static Logger log = LoggerFactory.getLogger(TaskConfig.class);
+
+    @Autowired
+    private TaskProperties taskProperties;
+
+    @PostConstruct
+    public void init() throws Exception {
+        log.info("task init... " + taskProperties.getImpl());
+
+        Class<Task> clazz = (Class<Task>) Class.forName(taskProperties.getImpl());
+        Task task = SpringContextHelper.getBean(clazz);
+        task.start(taskProperties.getParams());
+    }
+
+}

+ 50 - 0
src/main/java/cn/com/qmth/examcloud/tool/task/TaskProperties.java

@@ -0,0 +1,50 @@
+package cn.com.qmth.examcloud.tool.task;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.io.Serializable;
+
+@ConfigurationProperties(prefix = "examcloud.task", ignoreUnknownFields = false)
+public class TaskProperties implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /*
+     * 是否启用
+     */
+    private Boolean active;
+
+    /**
+     * 任务实现类
+     */
+    private String impl;
+
+    /**
+     * 任务自定义参数
+     */
+    private String params;
+
+    public Boolean getActive() {
+        return active;
+    }
+
+    public void setActive(Boolean active) {
+        this.active = active;
+    }
+
+    public String getImpl() {
+        return impl;
+    }
+
+    public void setImpl(String impl) {
+        this.impl = impl;
+    }
+
+    public String getParams() {
+        return params;
+    }
+
+    public void setParams(String params) {
+        this.params = params;
+    }
+}

+ 22 - 0
src/main/java/cn/com/qmth/examcloud/tool/task/demo/DemoTask.java

@@ -0,0 +1,22 @@
+package cn.com.qmth.examcloud.tool.task.demo;
+
+import cn.com.qmth.examcloud.tool.task.Task;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DemoTask implements Task {
+
+    private final static Logger log = LoggerFactory.getLogger(DemoTask.class);
+
+    @Override
+    public void start(String params) {
+        log.info("task start... " + params);
+
+        // to do ...
+
+        log.info("task end...");
+    }
+
+}

+ 103 - 0
src/main/java/cn/com/qmth/examcloud/tool/utils/FileUtils.java

@@ -0,0 +1,103 @@
+package cn.com.qmth.examcloud.tool.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FileUtils {
+
+    private static Logger log = LoggerFactory.getLogger(FileUtils.class);
+
+    private static final String encoding = "UTF-8";
+
+    /**
+     * 创建文件目录
+     */
+    public static boolean makeDirs(String dirPath) {
+        if (StringUtils.isEmpty(dirPath)) {
+            return false;
+        }
+
+        File folder = new File(dirPath);
+        if (!folder.exists()) {
+            return folder.mkdirs();
+        }
+
+        return true;
+    }
+
+    /**
+     * 写入内容到文件(覆盖)
+     */
+    public static void writeFile(String filePath, String content) {
+        File file = new File(filePath);
+        FileUtils.makeDirs(file.getParent());
+
+        try {
+            if (!file.exists()) {
+                file.createNewFile();
+            }
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return;
+        }
+
+        if (content == null) {
+            content = "";
+        }
+
+        try (FileOutputStream fos = new FileOutputStream(file);
+             OutputStreamWriter osw = new OutputStreamWriter(fos, encoding);
+             BufferedWriter bw = new BufferedWriter(osw);) {
+
+            bw.write(content);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 写入内容到文件(追加)
+     */
+    public static void appendWriteFile(String filePath, String content) {
+        if (content == null) {
+            content = "";
+        }
+
+        try {
+            File file = new File(filePath);
+            org.apache.commons.io.FileUtils.writeStringToFile(file, content, encoding, true);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 读取文件内容
+     */
+    public static List<String> readFile(File file) {
+        if (!file.exists() || !file.isFile()) {
+            return new ArrayList<>();
+        }
+
+        List<String> lines = new ArrayList<>();
+        try (FileInputStream fos = new FileInputStream(file);
+             InputStreamReader isr = new InputStreamReader(fos, encoding);
+             BufferedReader br = new BufferedReader(isr);) {
+
+            String line;
+            while ((line = br.readLine()) != null) {
+                lines.add(line);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return lines;
+    }
+
+}

+ 255 - 0
src/main/java/cn/com/qmth/examcloud/tool/utils/JsonMapper.java

@@ -0,0 +1,255 @@
+package cn.com.qmth.examcloud.tool.utils;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.util.JSONPObject;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 简单封装Jackson,实现JSON 与 Java Object互相转换的Mapper
+ * 封装不同的输出风格, 使用不同的builder函数创建实例
+ */
+@SuppressWarnings("unchecked")
+public class JsonMapper {
+
+    private static Logger log = LoggerFactory.getLogger(JsonMapper.class);
+
+    private ObjectMapper mapper;
+
+    /**
+     * 默认构造JsonMapper
+     */
+    public JsonMapper() {
+        this(null);
+    }
+
+    /**
+     * 创建只输出初始值被改变的属性到Json字符串的Mapper, 最节约的存储方式
+     */
+    public static JsonMapper nonDefaultMapper() {
+        return new JsonMapper(Include.NON_DEFAULT);
+    }
+
+    /**
+     * 创建只输出非Null且非Empty的属性到Json字符串的Mapper
+     */
+    public static JsonMapper nonEmptyMapper() {
+        return new JsonMapper(Include.NON_EMPTY);
+    }
+
+    /**
+     * 创建只输出非Null的属性到Json字符串的Mapper
+     */
+    public static JsonMapper nonNullMapper() {
+        return new JsonMapper(Include.NON_NULL);
+    }
+
+    /**
+     * 构造JsonMapper
+     */
+    public JsonMapper(Include include) {
+        mapper = new ObjectMapper();
+
+        //设置输出时包含属性的风格
+        if (include != null) {
+            mapper.setSerializationInclusion(include);
+        }
+
+        //设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性
+        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+
+        //设置默认日期格式
+        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
+    }
+
+    /**
+     * Object可以是POJO,也可以是Collection或数组
+     * 如果对象为Null, 返回"null"
+     * 如果集合为空集合, 返回"[]"
+     */
+    public String toJson(Object object) {
+        if (object == null) {
+            return null;
+        }
+
+        try {
+            return mapper.writeValueAsString(object);
+        } catch (IOException e) {
+            log.error("toJson error!" + e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化POJO或简单Collection如List<String>
+     * 如果JSON字符串为Null或"null"字符串, 返回Null
+     * 如果JSON字符串为"[]", 返回空集合
+     * 如需反序列化复杂Collection如List<MyBean>, 请使用parseJson(String, JavaType)
+     */
+    public <T> T parseJson(String jsonStr, Class<T> clazz) {
+        if (StringUtils.isEmpty(jsonStr)) {
+            return null;
+        }
+
+        try {
+            return mapper.readValue(jsonStr, clazz);
+        } catch (IOException e) {
+            log.error("parseJson error!" + e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化复杂Collection如List<Bean>, 先使用createCollectionType()或constructMapType()构造类型, 然后调用本函数
+     */
+    public <T> T parseJson(String jsonStr, JavaType javaType) {
+        if (StringUtils.isEmpty(jsonStr)) {
+            return null;
+        }
+
+        try {
+            return (T) mapper.readValue(jsonStr, javaType);
+        } catch (IOException e) {
+            log.error("parseJson error!" + e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化复杂的对象,如Page<Bean>
+     */
+    public <T> T parseJson(String jsonStr, TypeReference javaType) {
+        if (StringUtils.isEmpty(jsonStr)) {
+            return null;
+        }
+
+        try {
+            return (T) mapper.readValue(jsonStr, javaType);
+        } catch (IOException e) {
+            log.error("parseJson error!" + e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * Json to List
+     */
+    public <T> List<T> toList(String jsonStr, Class<T> bean) {
+        if (StringUtils.isEmpty(jsonStr)) {
+            return null;
+        }
+
+        try {
+            JavaType javaType = constructCollectionType(List.class, bean);
+            return mapper.readValue(jsonStr, javaType);
+        } catch (IOException e) {
+            log.error("parseJson error!" + e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * Json to HashMap
+     */
+    public <T> Map<String, T> toHashMap(String jsonStr, Class<T> bean) {
+        if (StringUtils.isEmpty(jsonStr)) {
+            return null;
+        }
+
+        try {
+            JavaType javaType = constructMapType(HashMap.class, String.class, bean);
+            return mapper.readValue(jsonStr, javaType);
+        } catch (IOException e) {
+            log.error("parseJson error!" + e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 构造Collection类型
+     */
+    public JavaType constructCollectionType(Class<? extends Collection> collectionClass, Class<?> elementClass) {
+        return mapper.getTypeFactory().constructCollectionType(collectionClass, elementClass);
+    }
+
+    /**
+     * 构造Map类型
+     */
+    public JavaType constructMapType(Class<? extends Map> mapClass, Class<?> keyClass, Class<?> valueClass) {
+        return mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass);
+    }
+
+    /**
+     * 当JSON里只含有Bean的部分属性時,更新一個已存在Bean,只覆盖該部分的属性
+     */
+    public void update(String jsonStr, Object object) {
+        if (jsonStr == null) {
+            return;
+        }
+
+        try {
+            mapper.readerForUpdating(object).readValue(jsonStr);
+        } catch (JsonProcessingException e) {
+            log.error("updateJson error!" + e.getMessage(), e);
+        } catch (IOException e) {
+            log.error("updateJson error!" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 輸出JSONP格式数据
+     */
+    public String toJsonP(String functionName, Object object) {
+        if (object == null) {
+            return null;
+        }
+
+        return toJson(new JSONPObject(functionName, object));
+    }
+
+    /**
+     * 設定是否使用Enum的toString函数來读写Enum
+     * 为False時使用Enum的name()函数來读写Enum, 默认为False
+     * 注意本函数一定要在Mapper創建後, 所有的读写動作之前調用
+     */
+    public void enableEnumUseToString() {
+        mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
+        mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
+    }
+
+    /**
+     * 取出Mapper做进一步的设置或使用其他序列化API
+     */
+    public ObjectMapper getMapper() {
+        return mapper;
+    }
+
+    /***
+     * 把Json字符串转换成Node对象
+     */
+    public JsonNode getNode(String jsonStr) {
+        if (jsonStr == null) {
+            return null;
+        }
+
+        try {
+            //mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
+            return mapper.readTree(jsonStr);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+}

+ 30 - 0
src/main/java/cn/com/qmth/examcloud/tool/utils/SpringContextHelper.java

@@ -0,0 +1,30 @@
+package cn.com.qmth.examcloud.tool.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringContextHelper implements ApplicationContextAware {
+
+    private static ApplicationContext context = null;
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException {
+        SpringContextHelper.context = context;
+    }
+
+    public static Object getBean(String beanName) {
+        return context.getBean(beanName);
+    }
+
+    public static <T> T getBean(String beanName, Class<T> clazz) {
+        return context.getBean(beanName, clazz);
+    }
+
+    public static <T> T getBean(Class<T> clazz) {
+        return context.getBean(clazz);
+    }
+
+}

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

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+cn.com.qmth.examcloud.tool.task.TaskConfig

+ 22 - 0
src/main/resources/application.properties

@@ -0,0 +1,22 @@
+server.port=8080
+server.servlet.context-path=/
+server.tomcat.uri-encoding=UTF-8
+spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
+spring.jackson.time-zone=GMT+8
+# datasource config
+dsurl.host=192.168.10.30
+dsurl.port=3306
+dsurl.database=exam_cloud_dev
+spring.datasource.username=exam_cloud_dev
+spring.datasource.password=exam_cloud_dev
+spring.datasource.url=jdbc:mysql://${dsurl.host}:${dsurl.port}/${dsurl.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2b8&rewriteBatchedStatements=true
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.jpa.hibernate.ddl-auto=validate
+# logging config
+logging.level.org.springframework=warn
+logging.level.org.apache=warn
+logging.level.cn.com.qmth=debug
+# task config
+examcloud.task.active=true
+examcloud.task.impl=cn.com.qmth.examcloud.tool.task.demo.DemoTask
+examcloud.task.params=xxx