xiatian 4 年之前
当前提交
9d1fefd338

+ 51 - 0
.gitignore

@@ -0,0 +1,51 @@
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+*.class
+*.log
+
+
+### Eclipse & STS ###
+.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
+node_modules
+package-lock.json
+yarn.lock
+
+
+### Package Files ###
+*.zip
+*.war
+*.ear
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+target/
+
+.flattened-pom.xml
+.DS_Store
+files/
+

二进制
course.xlsx


+ 212 - 0
pom.xml

@@ -0,0 +1,212 @@
+<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>gk-college-export</groupId>
+	<artifactId>gk-college-export</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
+	</properties>
+	<dependencies>
+		<dependency>
+			<groupId>io.swagger</groupId>
+			<artifactId>swagger-annotations</artifactId>
+			<version>1.5.24</version>
+		</dependency>
+		<dependency>
+			<groupId>io.swagger</groupId>
+			<artifactId>swagger-models</artifactId>
+			<version>1.5.24</version>
+		</dependency>
+		<dependency>
+			<groupId>io.springfox</groupId>
+			<artifactId>springfox-swagger2</artifactId>
+			<version>2.9.2</version>
+		</dependency>
+		<dependency>
+			<groupId>com.github.xiaoymin</groupId>
+			<artifactId>swagger-bootstrap-ui</artifactId>
+			<version>1.9.6</version>
+		</dependency>
+		<dependency>
+			<groupId>io.springfox</groupId>
+			<artifactId>springfox-swagger-ui</artifactId>
+			<version>2.9.2</version>
+		</dependency>
+		<dependency>
+			<groupId>com.oracle</groupId>
+			<artifactId>ojdbc6</artifactId>
+			<version>11.2.0.4.0-atlassian-hosted</version>
+		</dependency>
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-java</artifactId>
+			<version>8.0.17</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-1.2-api</artifactId>
+			<version>2.3</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi</artifactId>
+			<version>3.17</version>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>fastjson</artifactId>
+			<version>1.2.76</version>
+		</dependency>
+		<dependency>
+			<groupId>org.jsoup</groupId>
+			<artifactId>jsoup</artifactId>
+			<version>1.13.1</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+			<version>3.12.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi-ooxml</artifactId>
+			<version>3.17</version>
+		</dependency>
+		<dependency>
+			<groupId>com.squareup.okhttp3</groupId>
+			<artifactId>okhttp</artifactId>
+			<version>3.11.0</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+			<version>23.0</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.5</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+			<version>2.8.5</version>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<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>
+					<testFailureIgnore>true</testFailureIgnore>
+					<skipTests>true</skipTests>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+					<compilerArgument>-proc:none</compilerArgument>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<configuration>
+					<attach>true</attach>
+				</configuration>
+				<executions>
+					<execution>
+						<phase>compile</phase>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<version>2.6</version>
+				<configuration>
+					<archive>
+						<manifest>
+							<addClasspath>true</addClasspath>
+							<classpathPrefix>lib/</classpathPrefix>
+							<mainClass>cn.com.qmth.export.Export</mainClass>
+						</manifest>
+					</archive>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-dependency-plugin</artifactId>
+				<version>2.10</version>
+				<executions>
+					<execution>
+						<id>copy-dependencies</id>
+						<phase>package</phase>
+						<goals>
+							<goal>copy-dependencies</goal>
+						</goals>
+						<configuration>
+							<outputDirectory>${project.build.directory}/lib</outputDirectory>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>io.github.swagger2markup</groupId>
+				<artifactId>swagger2markup-maven-plugin</artifactId>
+				<version>1.2.0</version>
+				<configuration>
+					<!--此处端口一定要是当前项目启动所用的端口 -->
+					<swaggerInput>http://192.168.1.91:8090/v2/api-docs</swaggerInput>
+					<outputDir>src/docs/asciidoc/generated</outputDir>
+					<config>
+						<!-- 除了ASCIIDOC之外,还有MARKDOWN和CONFLUENCE_MARKUP可选 -->
+						<swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage>
+					</config>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+	<repositories>
+		<repository>
+			<id>nexus</id>
+			<name>Nexus</name>
+			<url>http://nexus-host:8081/repository/maven-public/</url>
+		</repository>
+	</repositories>
+
+	<pluginRepositories>
+		<pluginRepository>
+			<id>nexus</id>
+			<name>Nexus</name>
+			<url>http://nexus-host:8081/repository/maven-public/</url>
+		</pluginRepository>
+	</pluginRepositories>
+
+	<distributionManagement>
+		<repository>
+			<id>releases</id>
+			<url>http://nexus-host:8081/repository/maven-releases/</url>
+		</repository>
+		<snapshotRepository>
+			<id>snapshots</id>
+			<url>http://nexus-host:8081/repository/maven-snapshots/</url>
+		</snapshotRepository>
+	</distributionManagement>
+</project>

+ 46 - 0
src/main/java/cn/com/qmth/export/Answer.java

@@ -0,0 +1,46 @@
+package cn.com.qmth.export;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Answer {
+	private Long id;
+	private String body;
+	private Double score;
+
+	public String getBody() {
+		return body;
+	}
+
+	public void setBody(String body) {
+		this.body = body;
+	}
+
+	public Double getScore() {
+		return score;
+	}
+
+	public void setScore(Double score) {
+		this.score = score;
+	}
+
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+	public static void main(String[] args) {
+		String s="<p><img scr=\"sds\" /><a href=\"@@PLUGINFILE@@/boy-quiz03\" \"dfsd\">boy-quiz03.mp3</a></p><p>(如没有自动播放,请手动点击播放按钮)</p><p>你听到的单词是:</p>";
+		Pattern p = Pattern.compile("<a[^<]+href=\"[^<\"]+(?!\\.mp3)\"[^<]*>([^<]*)</a>");
+		Matcher m = p.matcher(s);
+		while(m.find()) {
+			System.out.println("+++++++++++++++++++");
+			System.out.println(m.group());
+			System.out.println(m.group(1));
+		}
+	}
+	
+}

+ 101 - 0
src/main/java/cn/com/qmth/export/Basket.java

@@ -0,0 +1,101 @@
+package cn.com.qmth.export;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Basket {
+	private static final Logger LOG = LoggerFactory.getLogger(Basket.class);
+	/**
+	 * 数据阻塞队列
+	 */
+	private BlockingQueue<Object> queue;
+	
+	/**
+	 * 多线程计数器,子线程都结束后主线程才继续执行
+	 */
+	private CountDownLatch endGate;
+	
+	/**
+	 * 消费者数量
+	 */
+	private int consumerCount;
+	
+	
+	/**
+	 * 判断线程执行是否有出错,生产者、消费者出错都需要修改此值为true
+	 */
+	private boolean isExcuteError = false;
+	
+	
+	public Basket(int consumerCount) {
+		this.consumerCount=consumerCount;
+		queue = new ArrayBlockingQueue<Object>(consumerCount*2);
+		endGate = new CountDownLatch(consumerCount);
+	}
+
+	/**
+	 * 生产数据,不采用put方法防止消费线程全部异常后生产线程阻塞
+	 * @param value
+	 * @throws InterruptedException
+	 */
+	protected void offer(final Object value) throws InterruptedException {
+		if(isExcuteError) {
+			LOG.error("**********************offer isExcuteError threadId:"+Thread.currentThread().getId());
+			throw new StatusException("1000001","线程异常");
+		}else {
+			boolean ret=queue.offer(value, 1, TimeUnit.MINUTES);
+			if(!ret) {
+				LOG.info("**********************offer time out threadId:"+Thread.currentThread().getId()+value);
+				this.offer(value);
+			}
+		}
+	}
+	/**
+	 * 消费数据,不采用take方法防止生产线程全部异常后消费线程阻塞
+	 * @return
+	 * @throws InterruptedException
+	 */
+	protected Object consume() throws InterruptedException {
+		if(isExcuteError) {
+			LOG.error("**********************poll isExcuteError  threadId:"+Thread.currentThread().getId());
+			return new EndObject();
+		}else {
+			Object ob=queue.poll(1, TimeUnit.MINUTES);
+			if(ob==null) {
+				LOG.info("**********************poll time out  threadId:"+Thread.currentThread().getId());
+				return this.consume();
+			}else {
+				return ob;
+			}
+		}
+	}
+	
+	protected void await() throws InterruptedException {
+		endGate.await();
+	}
+	protected void countDown() {
+		endGate.countDown();
+	}
+
+	protected boolean isExcuteError() {
+		return isExcuteError;
+	}
+
+	protected void setExcuteError(boolean isExcuteError) {
+		this.isExcuteError = isExcuteError;
+	}
+
+	protected int getConsumerCount() {
+		return consumerCount;
+	}
+
+	protected void setConsumerCount(int consumerCount) {
+		this.consumerCount = consumerCount;
+	}
+
+}

+ 72 - 0
src/main/java/cn/com/qmth/export/Consumer.java

@@ -0,0 +1,72 @@
+package cn.com.qmth.export;
+
+import java.util.Map;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+
+
+public abstract class Consumer<T>  extends Thread{
+	private static Logger LOG = LogManager.getLogger(MyProducer.class);
+	private Basket basket;
+	
+	private String traceId;
+	
+	private Map<String, Object> result;
+	
+	public Consumer() {
+	}
+	@Override
+	public void run() {
+		ThreadContext.put("TRACE_ID", traceId);
+		LOG.info("*******************Consumer:"+Thread.currentThread().getId()+" start");
+		try {
+			while (true) {
+				//先判断是否有异常结束
+				if(basket.isExcuteError()) {
+					break;
+				}
+				//取消费数据
+				Object o= basket.consume();
+				//判断消费数据是否是结束
+				if(o instanceof EndObject) {
+					break;
+				}
+				@SuppressWarnings("unchecked")
+				T t=(T)o;
+				LOG.info("*******************Consumer:"+Thread.currentThread().getId()+" consume");
+				//消费数据实现
+				consume(t);
+			}
+		} catch (Exception e) {
+			basket.setExcuteError(true);
+			LOG.error("消费线程处理出错",e);
+		}finally {
+			basket.countDown();
+			LOG.info("*******************Consumer:"+Thread.currentThread().getId()+" stop");
+			ThreadContext.clearAll();
+		}
+	}
+	public abstract void consume(T t);
+	public abstract void initResult();
+	public Basket getBasket() {
+		return basket;
+	}
+	public void setBasket(Basket basket) {
+		this.basket = basket;
+	}
+	public String getTraceId() {
+		return traceId;
+	}
+	public void setTraceId(String traceId) {
+		this.traceId = traceId;
+	}
+	public Map<String, Object> getResult() {
+		return result;
+	}
+	public void setResult(Map<String, Object> result) {
+		this.result = result;
+	}
+	
+}

+ 34 - 0
src/main/java/cn/com/qmth/export/Course.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.export;
+
+public class Course {
+	private String name;
+	private String code;
+	private String idnumber;
+	private Long id;
+	public String getName() {
+		return name;
+	}
+	public void setName(String name) {
+		this.name = name;
+	}
+	public String getCode() {
+		return code;
+	}
+	public void setCode(String code) {
+		this.code = code;
+	}
+	public Long getId() {
+		return id;
+	}
+	public void setId(Long id) {
+		this.id = id;
+	}
+	public String getIdnumber() {
+		return idnumber;
+	}
+	public void setIdnumber(String idnumber) {
+		this.idnumber = idnumber;
+	}
+	
+	
+}

+ 10 - 0
src/main/java/cn/com/qmth/export/EndObject.java

@@ -0,0 +1,10 @@
+package cn.com.qmth.export;
+
+/**
+ * 消费结束标识对象
+ * @author xiatian
+ *
+ */
+public class EndObject {
+
+}

+ 128 - 0
src/main/java/cn/com/qmth/export/ExportByCourseCode.java

@@ -0,0 +1,128 @@
+package cn.com.qmth.export;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import com.google.common.base.Joiner;
+
+public class ExportByCourseCode {
+	private static Logger logger = LogManager.getLogger(ExportByCourseCode.class);
+	private static String notFoundDir = "D:\\not_find\\";
+	private static AtomicInteger count=new AtomicInteger(0);	
+	private static AtomicInteger fdcount=new AtomicInteger(0);
+	private static AtomicInteger notfdcount=new AtomicInteger(0);
+	
+	private  static List<String> qdb = new ArrayList<>();
+	private  static List<String> qzip = new ArrayList<>();
+	private  static List<String> adb = new ArrayList<>();
+	private  static List<String> azip = new ArrayList<>();
+	private  static List<String> http = new ArrayList<>();
+	private  static List<String> filepath = new ArrayList<>();
+	private  static Set<String> noques = new HashSet<>();
+	
+	
+	private  static Set<Long> invalidAnswerCode = new HashSet<>();
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	public static void main(String[] args) {
+		logger.debug("导出开始");
+		try {
+			File excelFolder = new File(notFoundDir);
+			if (excelFolder.exists()) {
+				FileUtil.clearDirectory(notFoundDir);
+			} else {
+				excelFolder.mkdir();
+			}
+			MyProducer pro=new MyProducer();
+			pro.startDispose(MyConsumer.class, 20, null);
+			for(Consumer co:pro.getConsumers()) {
+				qdb.addAll((List<String>)co.getResult().get("qdb"));
+				qzip.addAll((List<String>)co.getResult().get("qzip"));
+				adb.addAll((List<String>)co.getResult().get("adb"));
+				azip.addAll((List<String>)co.getResult().get("azip"));
+				http.addAll((List<String>)co.getResult().get("http"));
+				filepath.addAll((List<String>)co.getResult().get("file"));
+				noques.addAll((Set<String>)co.getResult().get("noques"));
+				invalidAnswerCode.addAll((Set<Long>)co.getResult().get("invalidAnswerCode"));
+			}
+			logger.debug("===============================================小题数据库没有文件:" + qdb.size());
+			if (qdb.size() > 0) {
+				File file=new File(notFoundDir+"\\题干在数据库中找不到文件.txt");
+				file.createNewFile();
+				FileUtils.writeStringToFile(file, "questionid idnumber source", "utf-8", true);
+				FileUtils.writeLines(file, qdb,true);
+			}
+			logger.debug("===============================================小题zip没有文件:" + qzip.size());
+			if (qzip.size() > 0) {
+				File file=new File(notFoundDir+"\\题干在zip中找不到文件.txt");
+				file.createNewFile();
+				FileUtils.writeStringToFile(file, "questionid idnumber source", "utf-8", true);
+				FileUtils.writeLines(file, qzip,true);
+			}
+			logger.debug("===============================================答案数据库没有文件" + adb.size());
+			if (adb.size() > 0) {
+				File file=new File(notFoundDir+"\\答案在数据库中找不到文件.txt");
+				file.createNewFile();
+				FileUtils.writeStringToFile(file, "answerid idnumber source", "utf-8", true);
+				FileUtils.writeLines(file, adb,true);
+			}
+			logger.debug("===============================================答案zip没有文件:" + azip.size());
+			if (azip.size() > 0) {
+				File file=new File(notFoundDir+"\\答案在zip中找不到文件.txt");
+				file.createNewFile();
+				FileUtils.writeStringToFile(file, "answerid idnumber source", "utf-8", true);
+				FileUtils.writeLines(file, azip,true);
+			}
+			logger.debug("===============================================答案zip没有文件:" + azip.size());
+			if (azip.size() > 0) {
+				File file=new File(notFoundDir+"\\答案在zip中找不到文件.txt");
+				file.createNewFile();
+				FileUtils.writeStringToFile(file, "answerid idnumber source", "utf-8", true);
+				FileUtils.writeLines(file, azip,true);
+			}
+			logger.debug("===============================================http连接数:" + http.size());
+			if (http.size() > 0) {
+				File file=new File(notFoundDir+"\\http连接.txt");
+				file.createNewFile();
+				FileUtils.writeLines(file, http,true);
+			}
+			logger.debug("===============================================file连接数:" + filepath.size());
+			if (filepath.size() > 0) {
+				File file=new File(notFoundDir+"\\file连接.txt");
+				file.createNewFile();
+				FileUtils.writeLines(file, filepath,true);
+			}
+			logger.debug("===============================================内容空白校验:" + invalidAnswerCode.size());
+			if (invalidAnswerCode.size() > 0) {
+				logger.debug(Joiner.on(",").join(invalidAnswerCode));
+			}
+			logger.debug("===============================================没有小题:" + noques.size());
+			if (noques.size() > 0) {
+				logger.debug(Joiner.on(",").join(noques));
+			}
+			logger.debug("===============================================找到:" + fdcount+" 未找到:"+notfdcount);
+		} catch (Exception e) {
+			logger.error(e.getCause(), e);
+		}
+		logger.debug("导出结束");
+	}
+
+	public static void  addDisposeCount() {
+		count.addAndGet(1);
+		logger.debug("处理了"+count);
+	}
+	
+	public static void addFd() {
+		fdcount.addAndGet(1);
+	}
+	public static void addNotFd() {
+		notfdcount.addAndGet(1);
+	}
+}

+ 780 - 0
src/main/java/cn/com/qmth/export/FileUtil.java

@@ -0,0 +1,780 @@
+package cn.com.qmth.export;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import sun.misc.BASE64Decoder;
+
+/**
+ * @author chenken
+ * @date 2017年7月17日 上午9:36:32
+ * @company QMTH
+ */
+public class FileUtil {
+	public static void clearDirectory(String path) {
+		if (!path.endsWith(File.separator)) {
+			path = path + File.separator;
+		}
+		File dirFile = new File(path);
+		if (!dirFile.exists() || !dirFile.isDirectory()) {
+			return;
+		}
+		File[] files = dirFile.listFiles();
+		if (files != null) {
+			for (int i = 0; i < files.length; i++) {
+				if (files[i].isFile()) {
+					deleteFile(files[i].getAbsolutePath());
+				} else {
+					deleteDirectory(files[i].getAbsolutePath());
+				}
+			}
+		}
+	}
+	
+	public static void writeFile(String dir,String name,String content) throws IOException {
+		BufferedWriter out=null;
+		try {
+			File writename = new File(dir+name);
+			writename.createNewFile();
+			out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(writename), "UTF-8"));  
+			out.write(content);
+			out.flush();
+		} finally {
+			if(out!=null) {
+				out.close();
+			}
+		}
+	}
+	
+	public static void base64ToFile(File file,String base64Str){
+		if (base64Str.contains("data:image")) {//base64图片
+			base64Str = base64Str.substring(base64Str.indexOf(",") + 1);
+            BASE64Decoder decoder = new BASE64Decoder();
+			try {
+	            byte[] bytes = decoder.decodeBuffer(base64Str);
+				FileUtils.writeByteArrayToFile(file, bytes);
+			} catch (IOException e) {
+				throw new RuntimeException(e);
+			}
+        }else {
+        	throw new RuntimeException("not base64 string");
+        }
+	}
+	
+	public static String fileToBase64Src(File imgFile){
+		String fn=imgFile.getName();
+		String suff=fn.substring(fn.lastIndexOf(".")+1).toLowerCase();
+		return "data:image/"+suff+";base64,"+fileToBase64(imgFile);
+	}
+    public static String fileToBase64(File imgFile){
+        InputStream is = null;
+        byte[] base64Byte;
+        try {
+			base64Byte = new byte[0];
+			byte[] imgByte;
+			is = new FileInputStream(imgFile);
+			imgByte = IOUtils.toByteArray(is);
+			base64Byte = Base64.encodeBase64(imgByte);
+		}  catch (IOException e) {
+			throw new RuntimeException(e);
+		} finally {
+			if(is!=null)
+				try {
+					is.close();
+				} catch (IOException e) {
+				}
+		}
+        return new String(base64Byte);
+    }
+
+    /**
+     * 将网络文件保存到本地
+     *
+     * @param fileUrl
+     *            网络文件URL
+     * @param localFilePath
+     *            例如D:/123.txt
+     * @return
+     */
+    public static boolean saveUrlAs(String fileUrl, String localFilePath) {
+        URL url;
+        try {
+            url = new URL(fileUrl);
+        } catch (MalformedURLException e) {
+        	throw new RuntimeException("文件链接错误:"+fileUrl,e);
+        }
+
+        HttpURLConnection connection;
+        try {
+            connection = (HttpURLConnection) url.openConnection();
+        } catch (IOException e) {
+        	throw new RuntimeException("下载出错:"+e.getMessage(),e);
+        }
+
+        try (DataInputStream dataInputStream = new DataInputStream(connection.getInputStream());
+                FileOutputStream fileOutputStream = new FileOutputStream(localFilePath);
+                DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);) {
+
+            byte[] buffer = new byte[4096];
+            int count;
+            while ((count = dataInputStream.read(buffer)) > 0) {
+                dataOutputStream.write(buffer, 0, count);
+            }
+            fileOutputStream.flush();
+            dataOutputStream.flush();
+            return true;
+        } catch (Exception e) {
+        	throw new RuntimeException("下载出错:"+e.getMessage(),e);
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+
+    /**
+     * 获得文件MIME类型
+     *
+     * @param filename
+     * @return
+     */
+    public static String getContentType(String filename) {
+        String type = null;
+        Path path = Paths.get(filename);
+        try {
+            type = Files.probeContentType(path);
+        } catch (IOException e) {
+        	throw new RuntimeException("出错:"+e.getMessage(),e);
+        }
+        return type;
+    }
+
+    /**
+     * 将存放在sourceFilePath目录下的源文件,打包成fileName名称的zip文件,并存放到zipFilePath路径下
+     *
+     * @param sourceFilePath
+     *            :待压缩的文件夹路径
+     * @param zipFilePath
+     *            :压缩后zip文件的存放路径
+     * @param fileName
+     *            :zip文件的名称
+     * @return
+     */
+    public static void fileToZip(String sourceFilePath, String zipFilePath, String fileName) {
+
+        File sourceFile = new File(sourceFilePath);
+        if (!sourceFile.exists()) {
+        	throw new RuntimeException("待压缩的文件目录:" + sourceFilePath + "不存在.");
+        }
+
+        File zipFile = new File(zipFilePath + File.separator + fileName + ".zip");
+        if (zipFile.exists()) {
+        	throw new RuntimeException(zipFilePath + "目录下存在名字为:" + fileName + ".zip" + "打包文件.");
+        }
+
+        File[] sourceFiles = sourceFile.listFiles();
+        if (null == sourceFiles || sourceFiles.length < 1) {
+        	throw new RuntimeException("待压缩的文件目录:" + sourceFilePath + "里面不存在文件,无需压缩.");
+        }
+
+        try (FileOutputStream fos = new FileOutputStream(zipFile);
+                BufferedOutputStream bos = new BufferedOutputStream(fos);
+                ZipOutputStream zos = new ZipOutputStream(bos);) {
+
+            byte[] bytes = new byte[1024 * 10];
+            for (int i = 0; i < sourceFiles.length; i++) {
+                File file = sourceFiles[i];
+                if (!file.isFile()) {
+                    continue;
+                }
+
+                try (FileInputStream fis = new FileInputStream(file);
+                        BufferedInputStream bis = new BufferedInputStream(fis, 1024 * 10);) {
+                    // 创建ZIP实体,并添加进压缩包
+                    String fileEncode = System.getProperty("file.encoding");
+                    String name = new String(file.getName().getBytes(fileEncode), "UTF-8");
+
+                    ZipEntry zipEntry = new ZipEntry(name);
+                    zos.putNextEntry(zipEntry);
+
+                    // 读取待压缩的文件并写进压缩包里
+                    int read;
+                    while ((read = bis.read(bytes, 0, 1024 * 10)) != -1) {
+                        zos.write(bytes, 0, read);
+                    }
+
+                    zos.flush();
+                } catch (Exception e) {
+                	throw new RuntimeException("出错:"+e.getMessage(),e);
+                }
+            }
+        } catch (Exception e) {
+        	throw new RuntimeException("出错:"+e.getMessage(),e);
+        }
+    }
+
+    public static void createDirectory(String downloadDirectory) {
+        File directory = new File(downloadDirectory);
+        if (!directory.exists()) {
+            directory.mkdirs();
+        } else {
+            FileUtils.deleteQuietly(directory);
+            directory.mkdirs();
+        }
+    }
+
+    public static File createZip(String sourceFilePath, String targetFilePath) throws IOException {
+        OutputStream fos = null;
+        ZipOutputStream zos = null;
+        try {
+            File zipfile = new File(targetFilePath);
+            zipfile.deleteOnExit();
+            fos = new FileOutputStream(zipfile);
+            zos = new ZipOutputStream(fos);
+            // zos.setEncoding("utf-8"); // Solve linxu's mess
+            writeZip(new File(sourceFilePath), null, zos);
+            return zipfile;
+        } finally {
+            if (zos != null) {
+                zos.close();
+            }
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+
+    private static void writeZip(File file, String parentPath, ZipOutputStream zos) throws IOException {
+        if (file.exists()) {
+            ZipEntry ze = null;
+            if (file.isDirectory()) {// Processing folder
+                if (parentPath == null) {
+                    parentPath = "";
+                } else {
+                    parentPath += file.getName() + File.separator;
+                }
+                File[] files = file.listFiles();
+                if (files != null) {
+                    for (File f : files) {
+                        writeZip(f, parentPath, zos);
+                    }
+                } else { // An empty directory creates the current directory
+                    try {
+                        ze = new ZipEntry(parentPath);
+                        // ze.setUnixMode(755);// Solve Linux mess file Settings
+                        // 644 directory Settings 755
+                        zos.putNextEntry(ze);
+
+                        zos.flush();
+                    } finally {
+                        if (zos != null) {
+                            zos.closeEntry();
+                        }
+                    }
+                }
+            } else {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(file);
+                    ze = new ZipEntry(parentPath + file.getName());
+                    // ze.setUnixMode(644);// Solve Linux mess file Settings 644
+                    // directory Settings 755
+                    zos.putNextEntry(ze);
+                    byte[] content = new byte[1024];
+                    int len;
+                    while ((len = fis.read(content)) != -1) {
+                        zos.write(content, 0, len);
+                        zos.flush();
+                    }
+
+                } finally {
+                    if (fis != null) {
+                        fis.close();
+                    }
+                    if (zos != null) {
+                        zos.closeEntry();
+                    }
+                }
+            }
+        }
+    }
+    /**
+     * 文件压缩
+     *
+     * @param target  目录或文件
+     * @param zipFile 压缩后的ZIP文件
+     */
+    public static boolean doZip(File target, File zipFile) {
+        if (target == null || !target.exists()) {
+        	throw new RuntimeException("目录或文件不能为空!");
+        }
+
+        if (zipFile == null) {
+        	throw new RuntimeException("待压缩的文件不能为空!");
+        }
+
+        try (
+                OutputStream outStream = new FileOutputStream(zipFile);
+                ZipOutputStream zipOutStream = new ZipOutputStream(outStream, Charset.forName("UTF-8"));
+        ) {
+            if (!zipFile.exists()) {
+                boolean ok = zipFile.createNewFile();
+                if (!ok) {
+                	throw new RuntimeException("压缩的文件创建失败!");
+                }
+            }
+
+            if (target.isDirectory()) {
+                File[] files = target.listFiles();
+                if (files.length == 0) {
+                	throw new RuntimeException("文件夹内未找到任何文件!");
+                }
+
+                for (File file : files) {
+                    doZip(zipOutStream, file, null);
+                }
+            } else {
+                doZip(zipOutStream, target, null);
+            }
+        } catch (IOException e) {
+        	throw new RuntimeException(e);
+        }
+
+        return true;
+    }
+
+    private static void doZip(ZipOutputStream zipOutStream, File target, String parentDir) throws IOException {
+        //log.info("Zip:" + parentDir);
+        if (parentDir == null) {
+            parentDir = "";
+        }
+
+        if (!"".equals(parentDir) && !parentDir.endsWith(File.separator)) {
+            parentDir += File.separator;
+        }
+
+        if (target.isDirectory()) {
+            File[] files = target.listFiles();
+            if (files.length > 0) {
+                for (File file : files) {
+                    doZip(zipOutStream, file, parentDir + target.getName());
+                }
+            } else {
+                zipOutStream.putNextEntry(new ZipEntry(parentDir + target.getName()));
+                zipOutStream.closeEntry();
+            }
+        } else {
+            try (InputStream is = new FileInputStream(target);) {
+                zipOutStream.putNextEntry(new ZipEntry(parentDir + target.getName()));
+                int len;
+                byte[] bytes = new byte[1024];
+                while ((len = is.read(bytes)) > 0) {
+                    zipOutStream.write(bytes, 0, len);
+                }
+            } catch (IOException e) {
+            	throw new RuntimeException(e);
+            }
+            zipOutStream.closeEntry();
+        }
+    }
+    
+    /**
+     * 解压文件
+     *
+     * @param targetDir 解压目录
+     * @param zipFile   待解压的ZIP文件
+     */
+    public static List<File> unZip(File targetDir, File zipFile) {
+        if (targetDir == null) {
+        	throw new RuntimeException("解压目录不能为空!");
+        }
+
+        if (zipFile == null) {
+        	throw new RuntimeException("待解压的文件不能为空!");
+        }
+
+        if (!zipFile.exists()) {
+        	throw new RuntimeException("待解压的文件不存在!" + zipFile.getAbsolutePath());
+        }
+
+        String zipName = zipFile.getName().toLowerCase();
+        if (zipFile.isDirectory() || zipName.indexOf(".zip") < 0) {
+        	throw new RuntimeException("待解压的文件格式错误!");
+        }
+
+        if (!targetDir.exists()) {
+            targetDir.mkdir();
+        }
+
+        List<File> result = new LinkedList<>();
+
+        try (ZipFile zip = new ZipFile(zipFile, Charset.forName("UTF-8"));) {
+
+            @SuppressWarnings("rawtypes")
+			Enumeration entries = zip.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry) entries.nextElement();
+
+                //Linux中需要替换掉路径的反斜杠
+                String entryName = (File.separator + entry.getName()).replaceAll("\\\\", "/");
+
+                String filePath = targetDir.getAbsolutePath() + entryName;
+                File target = new File(filePath);
+                if (entry.isDirectory()) {
+                    target.mkdirs();
+                } else {
+                    File dir = target.getParentFile();
+                    if (!dir.exists()) {
+                        dir.mkdirs();
+                    }
+
+                    try (OutputStream os = new FileOutputStream(target);
+                         InputStream is = zip.getInputStream(entry);) {
+                        IOUtils.copy(is, os);
+                        os.flush();
+                    } catch (IOException e) {
+                    	throw new RuntimeException(e);
+                    }
+                    result.add(target);
+                }
+            }
+
+        } catch (IOException e) {
+        	throw new RuntimeException(e);
+        }
+
+        return result;
+    }
+
+    public static void unZipFiles(String zipFileName, String targetDirName) throws IOException {
+        if (!targetDirName.endsWith(File.separator)) {
+            targetDirName = targetDirName + File.separator;
+        }
+        ZipFile zipFile = null;
+        try {
+            // Create the ZipFile object from the ZIP file
+            zipFile = new ZipFile(zipFileName);
+            ZipEntry entry = null;
+            String entryName = null;
+            String descFileDir = null;
+            byte[] buf = new byte[4096];
+            int readByte = 0;
+            // Gets all entry in the ZIP file
+            @SuppressWarnings("rawtypes")
+            Enumeration enums = zipFile.entries();
+            // Go through all entry
+            while (enums.hasMoreElements()) {
+                entry = (ZipEntry) enums.nextElement();
+                // Get the name entry
+                entryName = entry.getName();
+                descFileDir = targetDirName + entryName;
+                if (entry.isDirectory()) {
+                    // If entry is a directory, create the directory
+                    // entry.setUnixMode(755);// Solve Linux mess file Settings
+                    // 644 directory Settings 755
+                    new File(descFileDir).mkdirs();
+                    continue;
+                } else {
+                    // If entry is a file, the parent directory is created
+                    // entry.setUnixMode(644);//Solve Linux mess file Settings
+                    // 644 directory Settings 755
+                    new File(descFileDir).getParentFile().mkdirs();
+                }
+                File file = new File(descFileDir);
+                // Open the file output stream
+                OutputStream os = null;
+                // Open the entry input stream from the ZipFile object
+                InputStream is = null;
+                try {
+                    os = new FileOutputStream(file);
+                    is = zipFile.getInputStream(entry);
+                    while ((readByte = is.read(buf)) != -1) {
+                        os.write(buf, 0, readByte);
+                    }
+                } finally {
+                    if (os != null)
+                        os.close();
+                    if (is != null)
+                        is.close();
+                }
+            }
+        } finally {
+            if (zipFile != null)
+                zipFile.close();
+        }
+    }
+
+    public static String getDocxBasePath() {
+        String path = FileUtil.class.getResource("/").getPath() + "templates/docx";
+        return path;
+    }
+    
+    public static void deleteFolder(String path) {
+
+		File file = new File(path);
+		if (file.exists()) {
+			if (file.isFile()) {
+				deleteFile(path);
+			} else {
+				deleteDirectory(path);
+			}
+		}
+	}
+
+	public static void deleteFile(String path) {
+		File file = new File(path);
+		if (file.isFile() && file.exists()) {
+			file.delete();
+		}
+	}
+
+	public static void deleteDirectory(String path) {
+		if (!path.endsWith(File.separator)) {
+			path = path + File.separator;
+		}
+		File dirFile = new File(path);
+		if (!dirFile.exists() || !dirFile.isDirectory()) {
+			return;
+		}
+		File[] files = dirFile.listFiles();
+		if (files != null) {
+			for (int i = 0; i < files.length; i++) {
+				if (files[i].isFile()) {
+					deleteFile(files[i].getAbsolutePath());
+				} else {
+					deleteDirectory(files[i].getAbsolutePath());
+				}
+			}
+		}
+
+		dirFile.delete();
+	}
+	public static File cutFile(String sourcePath, String targetPath, int n) {
+        File file = new File(sourcePath);
+        File newFile = new File(targetPath);
+
+        try (
+                FileInputStream fis = new FileInputStream(file);
+                InputStream is = new BufferedInputStream(fis);
+                OutputStream os = new FileOutputStream(newFile);
+        ) {
+
+            //从n个字节开始读,注意中文是两个字节
+            fis.skip(n);
+
+            //指定文件位置读取的文件流,存入新文件
+            byte buffer[] = new byte[4 * 1024];
+            int len;
+            while ((len = is.read(buffer)) != -1) {
+                os.write(buffer, 0, len);
+            }
+
+            os.flush();
+            return newFile;
+        } catch (IOException e) {
+        	throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 读取文件前面部分N个字节
+     *
+     * @param path       文件路径
+     * @param headerSize 头信息字节数(必须2的倍数)
+     * @param signSize   签名信息字节数
+     * @return
+     */
+    public static String[] readFileHeader(String path, int headerSize, int signSize) {
+        int n = headerSize / 2;
+        String[] codes = new String[n + 1];
+
+        File file = new File(path);
+        try (
+                FileInputStream fis = new FileInputStream(file);
+                DataInputStream ois = new DataInputStream(fis);
+        ) {
+            //分n次读取文件(n * 2)个字节
+            for (int i = 0; i < n; i++) {
+                codes[i] = String.valueOf(ois.readShort());
+            }
+
+            if (signSize > 0) {
+                StringBuilder ss = new StringBuilder();
+                for (int i = 0; i < signSize; i++) {
+                    ss.append((char) ois.readByte());
+                }
+                codes[2] = ss.toString();
+            }
+        } catch (IOException e) {
+        	throw new RuntimeException(e);
+        }
+
+        return codes;
+    }
+
+    /**
+     * 读取文件内容
+     *
+     * @param file
+     * @return
+     */
+	public static String readFileContent(File file) {
+        StringBuilder content = new StringBuilder();
+        InputStreamReader streamReader = null;
+        BufferedReader bufferedReader = null;
+        try {
+            String encoding = "UTF-8";
+            if (file.exists() && file.isFile()) {
+                streamReader = new InputStreamReader(new FileInputStream(file), encoding);
+                bufferedReader = new BufferedReader(streamReader);
+                String line;
+                while ((line = bufferedReader.readLine()) != null) {
+                    content.append(line);
+                }
+            }
+        } catch (Exception e) {
+        	throw new RuntimeException(e);
+        } finally {
+            IOUtils.closeQuietly(streamReader);
+            IOUtils.closeQuietly(bufferedReader);
+        }
+        return content.toString();
+    }
+
+
+    /**
+     * 生成日期目录路径
+     */
+    public static String generateDateDir() {
+        return "/" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + "/";
+    }
+
+    public static String generateFileName() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+    public static String generateDateName() {
+        return new SimpleDateFormat("yyMMddHHmmss").format(new Date());
+    }
+
+    /**
+     * 获取文件后缀名(包含".")
+     */
+    public static String getFileSuffix(String fileName) {
+        if (fileName == null) {
+            return "";
+        }
+        int index = fileName.lastIndexOf(".");
+        if (index > -1) {
+            return fileName.substring(index).toLowerCase();
+        }
+        return "";
+    }
+
+    /**
+     * 获取无后缀的文件名
+     *
+     * @param fileName 示例:../xxx/abc.xx
+     * @return 示例:../xxx/abc
+     */
+    public static String getFilePathName(String fileName) {
+        if (fileName != null && fileName.length() > 0) {
+            int index = fileName.lastIndexOf(".");
+            if (index != -1) {
+                return fileName.substring(0, index);
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 创建文件目录
+     */
+    public static boolean makeDirs(String path) {
+        if (path == null || "".equals(path)) {
+            return false;
+        }
+        File folder = new File(path);
+        if (!folder.exists()) {
+            return folder.mkdirs();
+        }
+        return true;
+    }
+
+    /**
+     * 保存字符串到文件中
+     */
+    public static void saveAsFile(String path, String content) {
+        saveAsFile(path, content, null);
+    }
+
+    public static void saveAsFile(String path, String content, String encoding) {
+        if (path == null || content == null) {
+            return;
+        }
+
+        if (encoding == null) {
+            encoding = "UTF-8";
+        }
+
+        File file = new File(path);
+        if (!file.exists()) {
+            if (makeDirs(file.getParent())) {
+                boolean ok;
+				try {
+					ok = file.createNewFile();
+				} catch (IOException e) {
+					throw new RuntimeException("文件创建失败!");
+				}
+                if (!ok) {
+                    throw new RuntimeException("文件创建失败!");
+                }
+            }
+        }
+
+        try (
+                FileOutputStream fos = new FileOutputStream(file);
+                OutputStreamWriter write = new OutputStreamWriter(fos, encoding);
+                BufferedWriter bw = new BufferedWriter(write);
+        ) {
+            bw.write(content);
+            bw.flush();
+        } catch (IOException e) {
+        	throw new RuntimeException("文件创建失败!",e);
+        }
+    }
+
+}

+ 43 - 0
src/main/java/cn/com/qmth/export/KdDetail.java

@@ -0,0 +1,43 @@
+package cn.com.qmth.export;
+
+import java.util.List;
+
+public class KdDetail {
+	private Integer number;
+	private String name;
+	private Integer questionCount;
+	private Double totalScore;
+	private List<KdQuestion> questions;
+	public Integer getNumber() {
+		return number;
+	}
+	public void setNumber(Integer number) {
+		this.number = number;
+	}
+	public String getName() {
+		return name;
+	}
+	public void setName(String name) {
+		this.name = name;
+	}
+	public Integer getQuestionCount() {
+		return questionCount;
+	}
+	public void setQuestionCount(Integer questionCount) {
+		this.questionCount = questionCount;
+	}
+	public Double getTotalScore() {
+		return totalScore;
+	}
+	public void setTotalScore(Double totalScore) {
+		this.totalScore = totalScore;
+	}
+	public List<KdQuestion> getQuestions() {
+		return questions;
+	}
+	public void setQuestions(List<KdQuestion> questions) {
+		this.questions = questions;
+	}
+	
+	
+}

+ 49 - 0
src/main/java/cn/com/qmth/export/KdPaper.java

@@ -0,0 +1,49 @@
+package cn.com.qmth.export;
+
+import java.util.List;
+
+public class KdPaper {
+	private String courseName;
+	private String courseCode;
+	private String name;
+	private Double totalScore;
+	private Integer detailCount;
+	private List<KdDetail> details;
+	public String getCourseCode() {
+		return courseCode;
+	}
+	public void setCourseCode(String courseCode) {
+		this.courseCode = courseCode;
+	}
+	public String getName() {
+		return name;
+	}
+	public void setName(String name) {
+		this.name = name;
+	}
+	public Double getTotalScore() {
+		return totalScore;
+	}
+	public void setTotalScore(Double totalScore) {
+		this.totalScore = totalScore;
+	}
+	public Integer getDetailCount() {
+		return detailCount;
+	}
+	public void setDetailCount(Integer detailCount) {
+		this.detailCount = detailCount;
+	}
+	public List<KdDetail> getDetails() {
+		return details;
+	}
+	public void setDetails(List<KdDetail> details) {
+		this.details = details;
+	}
+	public String getCourseName() {
+		return courseName;
+	}
+	public void setCourseName(String courseName) {
+		this.courseName = courseName;
+	}
+	
+}

+ 34 - 0
src/main/java/cn/com/qmth/export/KdQuesOption.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.export;
+
+public class KdQuesOption {
+	private Long answerId;
+	private Boolean select;
+	private Integer number;
+	private String body;
+	public Integer getNumber() {
+		return number;
+	}
+	public void setNumber(Integer number) {
+		this.number = number;
+	}
+	public String getBody() {
+		return body;
+	}
+	public void setBody(String body) {
+		this.body = body;
+	}
+	public Boolean getSelect() {
+		return select;
+	}
+	public void setSelect(Boolean select) {
+		this.select = select;
+	}
+	public Long getAnswerId() {
+		return answerId;
+	}
+	public void setAnswerId(Long answerId) {
+		this.answerId = answerId;
+	}
+	
+	
+}

+ 71 - 0
src/main/java/cn/com/qmth/export/KdQuestion.java

@@ -0,0 +1,71 @@
+package cn.com.qmth.export;
+
+import java.util.List;
+
+public class KdQuestion {
+	private Long id;
+	private Integer number;
+	private Integer structType;
+	private Boolean objective;
+	private String body;
+	private String answer;
+	private String qtype;
+	private Boolean haveAudio;
+	private List<KdQuesOption> options;
+	public Integer getNumber() {
+		return number;
+	}
+	public void setNumber(Integer number) {
+		this.number = number;
+	}
+	public Integer getStructType() {
+		return structType;
+	}
+	public void setStructType(Integer structType) {
+		this.structType = structType;
+	}
+	public Boolean getObjective() {
+		return objective;
+	}
+	public void setObjective(Boolean objective) {
+		this.objective = objective;
+	}
+	public String getBody() {
+		return body;
+	}
+	public void setBody(String body) {
+		this.body = body;
+	}
+	public String getAnswer() {
+		return answer;
+	}
+	public void setAnswer(String answer) {
+		this.answer = answer;
+	}
+	public List<KdQuesOption> getOptions() {
+		return options;
+	}
+	public void setOptions(List<KdQuesOption> options) {
+		this.options = options;
+	}
+	public Long getId() {
+		return id;
+	}
+	public void setId(Long id) {
+		this.id = id;
+	}
+	public String getQtype() {
+		return qtype;
+	}
+	public void setQtype(String qtype) {
+		this.qtype = qtype;
+	}
+	public Boolean getHaveAudio() {
+		return haveAudio;
+	}
+	public void setHaveAudio(Boolean haveAudio) {
+		this.haveAudio = haveAudio;
+	}
+	
+	
+}

+ 712 - 0
src/main/java/cn/com/qmth/export/MyConsumer.java

@@ -0,0 +1,712 @@
+package cn.com.qmth.export;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+
+import com.alibaba.fastjson.JSONObject;
+
+public class MyConsumer extends Consumer<String> {
+
+	private String excelDir = "D:\\kd_export\\";
+
+	private Pattern mp3Pat = Pattern.compile("<a[^<]+href=\"([^<\"]+\\.mp3)\"[^<]*>[^<]*</a>");
+	private Pattern imgPat = Pattern.compile("<img[^<]+src=\"([^<\"]+)\"[^<]*/>");
+
+	private Pattern notmp3Pat = Pattern.compile("<a[^<]+href=\"[^<\"]+(?!\\.mp3)\"[^<]*>([^<]*)</a>");
+
+	private List<String> qdb = new ArrayList<>();
+	private List<String> qzip = new ArrayList<>();
+	private List<String> adb = new ArrayList<>();
+	private List<String> azip = new ArrayList<>();
+	private List<String> http = new ArrayList<>();
+	private List<String> file = new ArrayList<>();
+
+	private Set<String> noques = new HashSet<>();
+
+	private Set<Long> invalidAnswerCode = new HashSet<>();
+
+	@Override
+	public void consume(String code) {
+		Connection connect = null;
+
+		try {
+			Class.forName("com.mysql.cj.jdbc.Driver");
+
+			String url = "jdbc:mysql://localhost:3306/moodle_question?serverTimezone=GMT%2B8";
+
+			String user = "root";
+
+			String password = "123456";
+			connect = DriverManager.getConnection(url, user, password);
+			exportPaper(connect, code);
+			ExportByCourseCode.addDisposeCount();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (connect != null) {
+				try {
+					connect.close();
+				} catch (SQLException e) {
+				}
+			}
+		}
+	}
+
+	private Course getCourse(Connection connect, String code) throws SQLException, IOException {
+		Course c = new Course();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = "select * from mdl_course where idnumber like '" + code + "_%';";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+			int count = 0;
+			while (resultSet.next()) {
+				count++;
+				if (count > 1) {
+					return null;
+				}
+				c.setId(resultSet.getLong("id"));
+				c.setName(resultSet.getString("shortname"));
+				c.setIdnumber(resultSet.getString("idnumber"));
+				c.setCode(code);
+			}
+			return c;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	private List<Long> getContextIds(Connection connect, Long courseId) throws SQLException, IOException {
+		List<Long> ids = new ArrayList<>();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = "select * from mdl_context where instanceid =" + courseId
+					+ " and contextlevel=50 order by id ";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				ids.add(resultSet.getLong("id"));
+			}
+			return ids;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	private List<Long> getQuestionCategorie(Connection connect, List<Long> cids) throws SQLException, IOException {
+		List<Long> ids = new ArrayList<>();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = "select * from mdl_question_categories where contextid in (" + getInStr(cids)
+					+ ") order by id ";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				ids.add(resultSet.getLong("id"));
+			}
+			return ids;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	private List<KdQuestion> getQuestion(Connection connect, List<Long> qcids) throws SQLException, IOException {
+		List<KdQuestion> qs = new ArrayList<>();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = "SELECT f.* FROM mdl_question f WHERE category IN (" + getInStr(qcids)
+					+ ") AND f.qtype IN ('multichoice','truefalse') ";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				KdQuestion q = new KdQuestion();
+				q.setId(resultSet.getLong("id"));
+				q.setBody(resultSet.getString("questiontext"));
+				q.setQtype(resultSet.getString("qtype"));
+				q.setObjective(true);
+				qs.add(q);
+			}
+			return qs;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	private List<Answer> getAnswer(Connection connect, KdQuestion q) throws SQLException, IOException {
+		List<Answer> as = new ArrayList<>();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = "select * from mdl_question_answers where question =" + q.getId();
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				Answer a = new Answer();
+				a.setId(resultSet.getLong("id"));
+				a.setBody(resultSet.getString("answer"));
+				a.setScore(resultSet.getDouble("fraction"));
+				as.add(a);
+			}
+			return as;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	private void exportPaper(Connection connect, String code) throws Exception {
+		Course c = getCourse(connect, code);
+		if (c == null) {
+			noques.add(code);
+			return;
+		}
+		List<Long> ctids = getContextIds(connect, c.getId());
+		if (ctids.size() == 0) {
+			noques.add(code);
+			return;
+		}
+		List<Long> qcids = getQuestionCategorie(connect, ctids);
+		if (qcids.size() == 0) {
+			noques.add(code);
+			return;
+		}
+		List<KdQuestion> qs = getQuestion(connect, qcids);
+		if (qs.size() == 0) {
+			noques.add(code);
+			return;
+		}
+		for (KdQuestion q : qs) {
+			List<Answer> as = getAnswer(connect, q);
+			disposeAnswer(q, as);
+			removeATag(q);
+		}
+
+		qs = removeInvalidQuestion(qs, code);
+		if (qs.size() == 0) {
+			noques.add(code);
+			return;
+		}
+
+		KdPaper paper = new KdPaper();
+		paper.setTotalScore((double) qs.size());
+		paper.setName(c.getName());
+		paper.setCourseCode(c.getCode());
+		File courseCode = new File(excelDir + paper.getCourseCode() + "\\");
+		courseCode.mkdirs();
+		File att = new File(excelDir + paper.getCourseCode() + "\\att\\");
+		att.mkdirs();
+		for (KdQuestion q : qs) {
+			disposeMedia(connect, c.getIdnumber(), q, att);
+		}
+
+		List<KdQuestion> single = new ArrayList<>();
+		List<KdQuestion> muti = new ArrayList<>();
+		List<KdQuestion> boo = new ArrayList<>();
+		for (KdQuestion q : qs) {
+			if (q.getStructType() == 1) {
+				single.add(q);
+			} else if (q.getStructType() == 2) {
+				muti.add(q);
+			} else if (q.getStructType() == 3) {
+				boo.add(q);
+			}
+		}
+
+		int detailIndx = 0;
+		List<KdDetail> des = new ArrayList<>();
+		if (single.size() > 0) {
+			detailIndx++;
+			KdDetail d = new KdDetail();
+			d.setName("单选题");
+			d.setNumber(detailIndx);
+			d.setQuestionCount(single.size());
+			d.setTotalScore((double) single.size());
+			d.setQuestions(single);
+			des.add(d);
+		}
+		if (muti.size() > 0) {
+			detailIndx++;
+			KdDetail d = new KdDetail();
+			d.setName("多选题");
+			d.setNumber(detailIndx);
+			d.setQuestionCount(muti.size());
+			d.setTotalScore((double) muti.size());
+			d.setQuestions(muti);
+			des.add(d);
+		}
+		if (boo.size() > 0) {
+			detailIndx++;
+			KdDetail d = new KdDetail();
+			d.setName("判断题");
+			d.setNumber(detailIndx);
+			d.setQuestionCount(boo.size());
+			d.setTotalScore((double) boo.size());
+			d.setQuestions(boo);
+			des.add(d);
+		}
+		paper.setDetails(des);
+		paper.setDetailCount(detailIndx);
+
+		FileUtil.writeFile(courseCode.getAbsolutePath(), "\\paper.json", JSONObject.toJSONString(paper));
+	}
+
+	private List<KdQuestion> removeInvalidQuestion(List<KdQuestion> qs, String courseCode) {
+		List<KdQuestion> ret = new ArrayList<>();
+		for (KdQuestion q : qs) {
+			if (StringUtils.isBlank(q.getAnswer())) {
+				invalidAnswerCode.add(q.getId());
+				continue;
+			}
+			if (isEmpty(q.getBody())) {
+				invalidAnswerCode.add(q.getId());
+				continue;
+			}
+			if (q.getStructType() == 1 || q.getStructType() == 2) {
+				if (!checkQuestionAndRemoveInvalidOption(q)) {
+					invalidAnswerCode.add(q.getId());
+					continue;
+				}
+			}
+			ret.add(q);
+		}
+		return ret;
+	}
+
+	private boolean checkQuestionAndRemoveInvalidOption(KdQuestion q) {
+		List<KdQuesOption> ret = new ArrayList<>();
+		for (KdQuesOption op : q.getOptions()) {
+			if (isEmpty(op.getBody())) {
+				if (op.getSelect()) {
+					return false;
+				}
+			} else {
+				ret.add(op);
+			}
+		}
+		if (ret.size() < 2) {
+			return false;
+		} else {
+			q.setOptions(ret);
+		}
+		return true;
+	}
+
+	private void disposeAnswer(KdQuestion q, List<Answer> as) {
+		if (CollectionUtils.isEmpty(as)) {
+			return;
+		}
+		if ("truefalse".equals(q.getQtype())) {
+			q.setStructType(3);
+			for (Answer a : as) {
+				if (a.getScore() > 0) {
+					q.setAnswer("对".equals(a.getBody().trim()) ? "true" : "false");
+					return;
+				}
+			}
+		} else {
+			int index = 0;
+			List<KdQuesOption> ops = new ArrayList<>();
+			StringBuilder sb = new StringBuilder();
+			for (Answer a : as) {
+				index++;
+				KdQuesOption op = new KdQuesOption();
+				op.setAnswerId(a.getId());
+				op.setNumber(index);
+				op.setBody(a.getBody());
+				if (a.getScore() > 0) {
+					op.setSelect(true);
+					sb.append(index).append(",");
+				} else {
+					op.setSelect(false);
+				}
+				ops.add(op);
+			}
+			q.setOptions(ops);
+			if (sb.length() > 0) {
+				sb.deleteCharAt(sb.length() - 1);
+			}
+			if (sb.indexOf(",") > 0) {
+				q.setStructType(2);
+			} else {
+				q.setStructType(1);
+			}
+			q.setAnswer(sb.toString());
+		}
+	}
+
+	private String getInStr(List<Long> cids) {
+		StringBuilder sb = new StringBuilder();
+		for (Long id : cids) {
+			sb.append(id).append(",");
+		}
+		sb.deleteCharAt(sb.length() - 1);
+		return sb.toString();
+	}
+
+	private Map<String, String> findAtag(String body) {
+		Map<String, String> set = new HashMap<>();
+		if (StringUtils.isBlank(body)) {
+			return set;
+		}
+		Matcher m = notmp3Pat.matcher(body);
+		while (m.find()) {
+			String f = m.group(1);
+			set.put(m.group(), f);
+		}
+		return set;
+	}
+
+	private void removeATag(KdQuestion q) throws Exception {
+		String body = q.getBody();
+
+		Map<String, String> as = findAtag(body);
+		if (as.size() > 0) {
+			for (String k : as.keySet()) {
+				String val = as.get(k);
+				body = body.replaceAll(k, val);
+			}
+			q.setBody(body);
+		}
+
+		if (CollectionUtils.isNotEmpty(q.getOptions())) {
+			for (KdQuesOption o : q.getOptions()) {
+				String obody = o.getBody();
+
+				Map<String, String> oas = findAtag(obody);
+				if (oas.size() > 0) {
+					for (String k : oas.keySet()) {
+						String val = oas.get(k);
+						obody = obody.replaceAll(k, val);
+					}
+					o.setBody(obody);
+				}
+			}
+		}
+	}
+
+	private void disposeMedia(Connection connect, String courseidnumber, KdQuestion q, File att) throws Exception {
+		String body = q.getBody();
+
+		Map<String, String> imgs = findAllImg(body, q.getId(), null, courseidnumber);
+		if (imgs.size() > 0) {
+			for (String k : imgs.keySet()) {
+				String img = imgs.get(k);
+				String fileName = getFileName(img);
+				File file = getQuestionFile(connect, q.getId(), courseidnumber, fileName);
+				if (file == null) {
+					ExportByCourseCode.addNotFd();
+					body = body.replaceAll(k, "[未找到图片文件]");
+				} else {
+					ExportByCourseCode.addFd();
+					body = body.replaceAll(img, FileUtil.fileToBase64Src(file));
+				}
+			}
+			q.setBody(body);
+		}
+		Map<String, String> audios = findAllAudio(body, q.getId(), null, courseidnumber);
+		if (audios.size() > 0) {
+			q.setHaveAudio(true);
+			for (String k : audios.keySet()) {
+				String val = audios.get(k);
+				String fileName = getFileName(val);
+				File file = getQuestionFile(connect, q.getId(), courseidnumber, fileName);
+				if (file == null) {
+					ExportByCourseCode.addNotFd();
+					body = body.replaceAll(k, "[未找到音频文件]");
+				} else {
+					ExportByCourseCode.addFd();
+					File newAudio = new File(att.getAbsoluteFile() + "\\" + file.getName());
+					newAudio.createNewFile();
+					FileUtils.copyFile(file, newAudio);
+					body = body.replaceAll(k, "<a id=\"" + file.getName() + "\" name=\"" + file.getName() + "\"></a>");
+				}
+			}
+			q.setBody(body);
+		}
+
+		if (CollectionUtils.isNotEmpty(q.getOptions())) {
+			for (KdQuesOption o : q.getOptions()) {
+				String obody = o.getBody();
+
+				Map<String, String> oimgs = findAllImg(obody, null, o.getAnswerId(), courseidnumber);
+				if (oimgs.size() > 0) {
+					for (String k : oimgs.keySet()) {
+						String img = oimgs.get(k);
+						String fileName = getFileName(img);
+						File file = getAnswerFile(connect, o.getAnswerId(), courseidnumber, fileName);
+						if (file == null) {
+							ExportByCourseCode.addNotFd();
+							obody = obody.replaceAll(k, "[未找到图片文件]");
+						} else {
+							ExportByCourseCode.addFd();
+							obody = obody.replaceAll(img, FileUtil.fileToBase64Src(file));
+						}
+					}
+					o.setBody(obody);
+				}
+
+				Map<String, String> oaudios = findAllAudio(obody, null, o.getAnswerId(), courseidnumber);
+				if (oaudios.size() > 0) {
+					q.setHaveAudio(true);
+					for (String k : oaudios.keySet()) {
+						String val = oaudios.get(k);
+						String fileName = getFileName(val);
+						File file = getAnswerFile(connect, o.getAnswerId(), courseidnumber, fileName);
+						if (file == null) {
+							ExportByCourseCode.addNotFd();
+							obody = obody.replaceAll(k, "[未找到音频文件]");
+						} else {
+							ExportByCourseCode.addFd();
+							File newAudio = new File(att.getAbsoluteFile() + "\\" + file.getName());
+							newAudio.createNewFile();
+							FileUtils.copyFile(file, newAudio);
+							obody = obody.replaceAll(k,
+									"<a id=\"" + file.getName() + "\" name=\"" + file.getName() + "\"></a>");
+						}
+					}
+					o.setBody(obody);
+				}
+			}
+		}
+	}
+
+	private Map<String, String> findAllImg(String body, Long qid, Long aid, String courseidnumber) {
+		Map<String, String> set = new HashMap<>();
+		Matcher m = imgPat.matcher(body);
+		while (m.find()) {
+			String f = m.group(1);
+			if (f.startsWith("file:") || f.startsWith("FILE:")) {
+				if (qid != null)
+					file.add("questionid:" + qid + " " + courseidnumber + " " + f);
+				if (aid != null)
+					file.add("answerId:" + aid + " " + courseidnumber + " " + f);
+				continue;
+			}
+			if (f.startsWith("http:") || f.startsWith("HTTP:")) {
+				if (qid != null)
+					http.add("questionid:" + qid + " " + courseidnumber + " " + f);
+				if (aid != null)
+					http.add("answerId:" + aid + " " + courseidnumber + " " + f);
+				continue;
+			}
+			if (!f.startsWith("data:image")) {
+				set.put(m.group(), f);
+			}
+		}
+		return set;
+	}
+
+	private Map<String, String> findAllAudio(String body, Long qid, Long aid, String courseidnumber) {
+		Map<String, String> set = new HashMap<>();
+		Matcher m = mp3Pat.matcher(body);
+		while (m.find()) {
+			String f = m.group(1);
+			if (f.startsWith("file:") || f.startsWith("FILE:")) {
+				if (qid != null)
+					file.add("questionid:" + qid + " " + courseidnumber + " " + f);
+				if (aid != null)
+					file.add("answerId:" + aid + " " + courseidnumber + " " + f);
+				continue;
+			}
+			if (f.startsWith("http:") || f.startsWith("HTTP:")) {
+				if (qid != null)
+					http.add("questionid:" + qid + " " + courseidnumber + " " + f);
+				if (aid != null)
+					http.add("answerId:" + aid + " " + courseidnumber + " " + f);
+				continue;
+			}
+			set.put(m.group(), f);
+		}
+		return set;
+	}
+
+	private boolean hasImg(String body) {
+		Matcher m = imgPat.matcher(body);
+		while (m.find()) {
+			return true;
+		}
+		return false;
+	}
+
+	private boolean hasAudio(String body) {
+		Matcher m = mp3Pat.matcher(body);
+		while (m.find()) {
+			return true;
+		}
+		return false;
+	}
+
+	private boolean isEmpty(String html) {
+		if (StringUtils.isBlank(html)) {
+			return true;
+		}
+		if (hasImg(html) || hasAudio(html)) {
+			return false;
+		}
+		Document doc = Jsoup.parse(html);
+		Element b = doc.body();
+		if (StringUtils.isBlank(b.wholeText())) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	private String getFileName(String s) throws UnsupportedEncodingException {
+		int end = s.length();
+		int f = s.indexOf("?");
+		if (f != -1) {
+			end = f;
+		}
+		return URLDecoder.decode(s.substring(s.lastIndexOf("/") + 1, end), "utf-8");
+	}
+
+	private File getQuestionFile(Connection connect, Long quesId, String courseidnumber, String fileName)
+			throws Exception {
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String hash = null;
+			String sql = " select * from mdl_files where itemid = " + quesId + " and source='" + fileName
+					+ "' and component = 'question' and filearea = 'questiontext' ";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				hash = resultSet.getString("contenthash");
+				break;
+			}
+			if (hash == null) {
+				qdb.add(quesId + " " + courseidnumber + " " + fileName);
+				return null;
+			}
+			String suff = fileName.substring(fileName.lastIndexOf("."));
+			File directory = new File("");
+			File file = new File(directory.getAbsolutePath() + "\\files\\" + courseidnumber + "\\" + hash + suff);
+			if (!file.exists()) {
+				qzip.add(quesId + " " + courseidnumber + " " + fileName);
+				return null;
+			}
+			return file;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	private File getAnswerFile(Connection connect, Long answerId, String courseidnumber, String fileName)
+			throws Exception {
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String hash = null;
+			String sql = " select * from mdl_files where itemid = " + answerId + " and source='" + fileName
+					+ "' and component = 'question' and filearea = 'answer' ";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				hash = resultSet.getString("contenthash");
+				break;
+			}
+			if (hash == null) {
+				adb.add(answerId + " " + courseidnumber + " " + fileName);
+				return null;
+			}
+			String suff = fileName.substring(fileName.lastIndexOf("."));
+			File directory = new File("");
+			File file = new File(directory.getAbsolutePath() + "\\files\\" + courseidnumber + "\\" + hash + suff);
+			if (!file.exists()) {
+				azip.add(answerId + " " + courseidnumber + " " + fileName);
+				return null;
+			}
+			return file;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	@Override
+	public void initResult() {
+		setResult(new HashMap<>());
+		getResult().put("qdb", qdb);
+		getResult().put("qzip", qzip);
+		getResult().put("adb", adb);
+		getResult().put("azip", azip);
+		getResult().put("invalidAnswerCode", invalidAnswerCode);
+		getResult().put("file", file);
+		getResult().put("http", http);
+		getResult().put("noques", noques);
+
+	}
+}

+ 64 - 0
src/main/java/cn/com/qmth/export/MyProducer.java

@@ -0,0 +1,64 @@
+package cn.com.qmth.export;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+
+public class MyProducer extends Producer {
+	private static Logger logger = LogManager.getLogger(MyProducer.class);
+	private static String excelDir = "D:\\kd_export\\";
+
+	@Override
+	protected void produce(Map<String, Object> param) throws Exception {
+		logger.info("***************************任务生产开始");
+		File excelFolder = new File(excelDir);
+		if (excelFolder.exists()) {
+			FileUtil.clearDirectory(excelDir);
+		} else {
+			excelFolder.mkdir();
+		}
+		Set<String> codes = readCourseCode();
+		if (codes == null || codes.size() == 0) {
+			logger.debug("无数据导出");
+			return;
+		} else {
+			logger.debug(codes.size() + "个课程");
+		}
+		for (String code : codes) {
+			offer(code);
+		}
+		logger.info("***************************任务生产结束");
+	}
+	
+	private  Set<String> readCourseCode() throws InvalidFormatException, IOException {
+		File directory = new File("");
+		Set<String> list = new HashSet<String>();
+		XSSFWorkbook wb = null;
+		try {
+			wb = new XSSFWorkbook(directory.getAbsolutePath() + "\\course.xlsx");
+			XSSFSheet sheet = wb.getSheetAt(0);
+			int rows = sheet.getLastRowNum();
+			for (int i = 1; i <= rows; i++) {
+				XSSFRow row = sheet.getRow(i);
+				String tem = row.getCell(0).getStringCellValue().trim();
+				tem = tem.substring(0, tem.length() - 2);
+				list.add(tem);
+			}
+		} finally {
+			if (wb != null) {
+				wb.close();
+			}
+		}
+		return list;
+	}
+}

+ 152 - 0
src/main/java/cn/com/qmth/export/Producer.java

@@ -0,0 +1,152 @@
+package cn.com.qmth.export;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+
+public abstract class Producer {
+
+	private static Logger LOG = LogManager.getLogger(MyProducer.class);
+
+	private Basket basket;
+	
+	private List<Consumer<?>> consumers;
+	
+	/**
+	 * 业务参数
+	 */
+	private Map<String, Object> param;
+	
+	/**
+	 * 消费线程class
+	 */
+	private Class<? extends Consumer<?>> consumer;
+	/**
+	 * 	处理开始方法
+	 * @param consumer 消费线程class
+	 * @param consumerCount 消费线程数
+	 * @param param 生产者业务参数
+	 * @throws InstantiationException
+	 * @throws IllegalAccessException
+	 */
+	public void startDispose(Class<? extends Consumer<?>> consumer, int consumerCount,Map<String, Object> param)
+			throws InstantiationException, IllegalAccessException {
+		this.consumers=new ArrayList<Consumer<?>>();
+		Basket basket = new Basket(consumerCount);
+		this.basket = basket;
+		this.consumer = consumer;
+		this.param=param;
+		//启动消费者
+		startConsumer();
+		//开始处理
+		dispose();
+	}
+
+	private void dispose() {
+		try {
+			LOG.info("*******************Producer:开始处理");
+			// 生产数据
+			produce(param);
+			LOG.info("*******************Producer:生产结束");
+			// 发送生产结束信息
+			endConsumer();
+			LOG.info("*******************Producer:成功发送生产结束信息");
+			// 等待子线程结束
+			LOG.info("*******************Producer:等待消费线程结束");
+			await();
+			LOG.info("*******************Producer:消费线程已结束");
+			// 判断子线程是否正常结束
+			if (basket.isExcuteError()) {
+				throw new StatusException("1000001", "处理失败,线程异常");
+			}
+			LOG.info("*******************Producer:结束处理");
+		} catch (StatusException e) {
+			// 获取异常时发送异常结束信息
+			endConsumerAsError();
+			throw e;
+		} catch (Exception e) {
+			// 获取异常时发送异常结束信息
+			endConsumerAsError();
+			throw new StatusException("1000002", "处理失败", e);
+		}
+	}
+
+	/**
+	 * 启动消费者
+	 * 
+	 * @param consumer
+	 * @throws InstantiationException
+	 * @throws IllegalAccessException
+	 */
+	private void startConsumer() throws InstantiationException, IllegalAccessException {
+		int count = basket.getConsumerCount();
+		for (int i = 0; i < count; i++) {
+			Consumer<?> co = (Consumer<?>) consumer.newInstance();
+			co.initResult();
+			co.setBasket(basket);
+			co.setTraceId(ThreadContext.get("TRACE_ID"));
+			consumers.add(co);
+			co.start();
+		}
+
+	}
+
+	/**
+	 * 出异常后修改标识
+	 * 
+	 */
+	private void endConsumerAsError() {
+		basket.setExcuteError(true);
+	}
+
+	/**
+	 * 正常结束消费者
+	 * 
+	 * @throws InterruptedException
+	 */
+	private void endConsumer() throws InterruptedException {
+		int count = basket.getConsumerCount();
+		EndObject eo = new EndObject();
+		for (int i = 0; i < count; i++) {
+			basket.offer(eo);
+		}
+
+	}
+
+	/**
+	 * 生产数据
+	 * 
+	 * @param ob
+	 * @throws InterruptedException
+	 */
+	protected void offer(Object ob) throws InterruptedException {
+		synchronized (basket) {
+			basket.offer(ob);
+		}
+	}
+
+	/**
+	 * 等待所有消费者结束
+	 * 
+	 * @throws InterruptedException
+	 */
+	private void await() throws InterruptedException {
+		basket.await();
+	}
+
+	protected abstract void produce(Map<String, Object> param) throws Exception;
+
+	public List<Consumer<?>> getConsumers() {
+		return consumers;
+	}
+
+	public void setConsumers(List<Consumer<?>> consumers) {
+		this.consumers = consumers;
+	}
+
+	
+}

+ 73 - 0
src/main/java/cn/com/qmth/export/StatusException.java

@@ -0,0 +1,73 @@
+package cn.com.qmth.export;
+
+/**
+ * 状态异常类<br>
+ *
+ * @author WANG
+ */
+public class StatusException extends RuntimeException {
+	private static final long serialVersionUID = 5003047488500388819L;
+
+	private static final String DEF_CODE="500";
+	/**
+	 * 追踪ID
+	 */
+	private String traceId;
+
+	/**
+	 * 状态码
+	 */
+	private String code;
+
+	/**
+	 * 状态描述
+	 */
+	private String desc;
+
+	/**
+	 * 构造函数
+	 */
+	public StatusException(String code, String desc) {
+		super("[code: " + code + "; desc: " + desc + "]");
+		this.code = code;
+		this.desc = desc;
+	}
+
+	/**
+	 * 构造函数
+	 */
+	public StatusException(String code, String desc, Throwable cause) {
+		super("[code: " + code + "; desc: " + desc + "]", cause);
+		this.code = code;
+		this.desc = desc;
+	}
+	
+	public StatusException( String desc) {
+		super("[code: " + DEF_CODE + "; desc: " + desc + "]");
+		this.code = DEF_CODE;
+		this.desc = desc;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public String getDesc() {
+		return desc;
+	}
+
+	public String getTraceId() {
+		return traceId;
+	}
+
+	public void setTraceId(String traceId) {
+		this.traceId = traceId;
+	}
+
+
+	@Override
+	public String toString() {
+		return desc;
+	}
+
+}

+ 32 - 0
src/main/resources/log4j2.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN" monitorInterval="30">
+	<Appenders>
+		<!-- 控制台 日志 -->
+		<Console name="Console" target="SYSTEM_OUT">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m | %l%n" />
+		</Console>
+		<!-- debug 日志 -->
+		<RollingFile name="DEBUG_APPERDER" fileName="./logs/debug/debug.log" filePattern="./logs/debug/debug-%d{yyyy.MM.dd.HH}-%i.log">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m | %l%n" />
+			<Policies>
+				<SizeBasedTriggeringPolicy size="100 MB" />
+			</Policies>
+			<DefaultRolloverStrategy max="10000">
+			</DefaultRolloverStrategy>
+		</RollingFile>
+	</Appenders>
+
+	<Loggers>
+		<Logger name="cn.com.qmth" level="debug" additivity="false">
+			<AppenderRef ref="DEBUG_APPERDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+
+		<Root level="INFO">
+			<AppenderRef ref="Console" />
+			<AppenderRef ref="DEBUG_APPERDER" />
+		</Root>
+	</Loggers>
+
+</Configuration>