xiatian 2 년 전
커밋
b3fe8420b0
29개의 변경된 파일3524개의 추가작업 그리고 0개의 파일을 삭제
  1. 20 0
      .gitignore
  2. 212 0
      pom.xml
  3. 106 0
      src/main/java/cn/com/qmth/export/Answer.java
  4. 22 0
      src/main/java/cn/com/qmth/export/AnswerOb.java
  5. 101 0
      src/main/java/cn/com/qmth/export/Basket.java
  6. 72 0
      src/main/java/cn/com/qmth/export/Consumer.java
  7. 52 0
      src/main/java/cn/com/qmth/export/Course.java
  8. 174 0
      src/main/java/cn/com/qmth/export/CreatePaperByExcel.java
  9. 10 0
      src/main/java/cn/com/qmth/export/EndObject.java
  10. 121 0
      src/main/java/cn/com/qmth/export/ExportByCourseCode.java
  11. 158 0
      src/main/java/cn/com/qmth/export/ExportProperty.java
  12. 780 0
      src/main/java/cn/com/qmth/export/FileUtil.java
  13. 32 0
      src/main/java/cn/com/qmth/export/IdBase.java
  14. 43 0
      src/main/java/cn/com/qmth/export/KdDetail.java
  15. 49 0
      src/main/java/cn/com/qmth/export/KdPaper.java
  16. 34 0
      src/main/java/cn/com/qmth/export/KdQuesOption.java
  17. 78 0
      src/main/java/cn/com/qmth/export/KdQuestion.java
  18. 38 0
      src/main/java/cn/com/qmth/export/Knowledge.java
  19. 10 0
      src/main/java/cn/com/qmth/export/MediaNotFoundException.java
  20. 844 0
      src/main/java/cn/com/qmth/export/MyConsumer.java
  21. 64 0
      src/main/java/cn/com/qmth/export/MyProducer.java
  22. 48 0
      src/main/java/cn/com/qmth/export/PicCompress.java
  23. 152 0
      src/main/java/cn/com/qmth/export/Producer.java
  24. 50 0
      src/main/java/cn/com/qmth/export/PropertyDto.java
  25. 26 0
      src/main/java/cn/com/qmth/export/Question.java
  26. 123 0
      src/main/java/cn/com/qmth/export/SetRootId.java
  27. 73 0
      src/main/java/cn/com/qmth/export/StatusException.java
  28. 32 0
      src/main/resources/log4j2.xml
  29. BIN
      subject.xlsx

+ 20 - 0
.gitignore

@@ -0,0 +1,20 @@
+*.class
+
+# Proguard folder generated by ide
+.project
+.classpath
+.settings
+target/
+.idea/
+*.iml
+
+# Log Files
+*.log
+*.class
+
+
+# Package Files #
+*.jar
+*.war
+*.ear
+logs/

+ 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>net.coobird</groupId>
+			<artifactId>thumbnailator</artifactId>
+			<version>0.4.8</version>
+		</dependency>
+		<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>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>

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

@@ -0,0 +1,106 @@
+package cn.com.qmth.export;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
+
+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><span style=\"color: #4e4e4e; font-family: -apple-system, system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; white-space: nowrap; background-color: #e4e4e4;\">为知笔记</span></p>";
+		System.out.println(disBody(s));
+	}
+
+	private static String disBody(String html) {
+		Document doc = Jsoup.parse(html);
+		Element b = doc.body();
+		if (b.childrenSize()== 1&&"p".equals(b.child(0).nodeName())) {
+			b.child(0).tagName("span");
+			b.child(0).removeAttr("style");
+		}
+		if (b.childrenSize()> 0) {
+			for (Element n : b.children()) {
+				if("p".equals(n.nodeName())) {
+					boolean[] find=new boolean[] {false};
+					findImg(n, find);
+					if(!find[0]&&StringUtils.isBlank(n.text())) {
+						n.tagName("span");
+					}
+				}
+				disNode(n);
+			}
+		}
+		return b.html();
+	}
+	
+	private static void findImg(Element e,boolean[] find) {
+		if(find[0]) {
+			return;
+		}else {
+			if("img".equals(e.nodeName())) {
+				find[0]=true;
+				return;
+			}else {
+				if(e.childrenSize()>0) {
+					for(Element ce:e.children()) {
+						findImg(ce, find);
+					}
+				}else {
+					return;
+				}
+			}
+		}
+	}
+	private static void disNode(Node n) {
+		if (n instanceof Element) {
+			if (!"img".equals(n.nodeName())) {
+				n.removeAttr("style");
+			}
+			if ("h1".equals(n.nodeName()) || "h2".equals(n.nodeName()) || "h3".equals(n.nodeName())
+					|| "h4".equals(n.nodeName()) || "h5".equals(n.nodeName())) {
+				((Element) n).tagName("h6");
+			}
+			if ("strong".equals(n.nodeName())||"a".equals(n.nodeName())||"u".equals(n.nodeName())||"em".equals(n.nodeName())) {
+				((Element) n).tagName("span");
+			}
+			if (n.childNodeSize() > 0) {
+				for (Node cn : n.childNodes()) {
+					disNode(cn);
+				}
+			}
+		}
+	}
+
+}

+ 22 - 0
src/main/java/cn/com/qmth/export/AnswerOb.java

@@ -0,0 +1,22 @@
+package cn.com.qmth.export;
+
+import java.util.List;
+
+public class AnswerOb {
+	private List<String> subArea;
+	private List<String> answer;
+	public List<String> getSubArea() {
+		return subArea;
+	}
+	public void setSubArea(List<String> subArea) {
+		this.subArea = subArea;
+	}
+	public List<String> getAnswer() {
+		return answer;
+	}
+	public void setAnswer(List<String> answer) {
+		this.answer = answer;
+	}
+	
+	
+}

+ 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;
+	}
+	
+}

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

@@ -0,0 +1,52 @@
+package cn.com.qmth.export;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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;
+	}
+
+	public static void main(String[] args) {
+		String s = "<span><span>已知<span lang=\"EN-US\">A=<img src=\"@@PLUGINFILE@@/4.png?time=1594114072778\" alt=\"\" width=\"75\" height=\"54\"></span></span><span>,<span lang=\"EN-US\">B=<img src=\"@@PLUGINFILE@@/5.png?time=1594114090754\" alt=\"\" width=\"93\" height=\"48\"></span></span><span>,下列运算可行的是(&nbsp; &nbsp; &nbsp; )。&nbsp;</span></span>";
+		String k="@@PLUGINFILE@@/4.png?time=1594114072778";
+		k=k.replaceAll("\\?", "\\\\?");
+		System.out.println(k);
+		System.out.println(s.replaceAll(k, "AAAAAA"));
+	}
+
+}

+ 174 - 0
src/main/java/cn/com/qmth/export/CreatePaperByExcel.java

@@ -0,0 +1,174 @@
+package cn.com.qmth.export;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import com.alibaba.fastjson.JSONObject;
+
+public class CreatePaperByExcel {
+	private static String paperSuff = "(211)";
+	private static int maxqc = 100;
+	private static String excelDir = "D:\\kd_excel_export\\";
+	public static void main(String[] args) {
+		try {
+			File excelFolder = new File(excelDir);
+			if (excelFolder.exists()) {
+				FileUtil.clearDirectory(excelDir);
+			} else {
+				excelFolder.mkdir();
+			}
+			Course c=new Course();
+			c.setName("汽车维修基本技能");
+			c.setCode("10613");
+			List<KdQuestion> qs=getQus(read());
+			System.out.println("小题数量:"+qs.size());
+			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);
+				}
+			}
+			createPapers(single, c, 1);
+			createPapers(muti, c, 2);
+			createPapers(boo, c, 3);
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+	private static void createPapers(List<KdQuestion> qs, Course c, int structType) throws IOException {
+		if (qs == null || qs.size() == 0) {
+			return;
+		}
+		if (qs.size() <= maxqc) {
+			createPaper(qs, c, structType, 1);
+		} else {
+			int size = qs.size();
+			int len = maxqc;
+			int count = (size + len - 1) / len;
+
+			for (int i = 0; i < count; i++) {
+				List<KdQuestion> subList = qs.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
+				createPaper(subList, c, structType, i + 1);
+			}
+		}
+
+	}
+
+	private static void createPaper(List<KdQuestion> qs, Course c, int structType, int indx) throws IOException {
+		if (qs.size() == 0) {
+			return;
+		}
+		String detailName="";
+		if (structType == 1) {
+			detailName="单选题";
+		} else if (structType == 2) {
+			detailName="多选题";
+		} else if (structType == 3) {
+			detailName="判断题";
+		}
+		KdPaper paper = new KdPaper();
+		paper.setTotalScore((double) qs.size());
+		paper.setName(c.getName()+paperSuff+detailName+"_"+indx);
+		paper.setCourseCode(c.getCode());
+		List<KdDetail> des = new ArrayList<>();
+		KdDetail d = new KdDetail();
+		d.setName(detailName);
+		d.setNumber(1);
+		d.setQuestionCount(qs.size());
+		d.setTotalScore((double) qs.size());
+		d.setQuestions(qs);
+		des.add(d);
+		paper.setDetails(des);
+		paper.setDetailCount(1);
+		File paperdir = new File(excelDir + c.getCode() + "\\paper"+structType+"_"+indx+"\\");
+		paperdir.mkdirs();
+		FileUtil.writeFile(paperdir.getAbsolutePath(), "\\paper.json", JSONObject.toJSONString(paper));
+	}
+	public static List<KdQuestion> getQus(List<Question> qs) {
+		List<KdQuestion> kqs = new ArrayList<>();
+		for (Question q : qs) {
+			KdQuestion kq = new KdQuestion();
+			kqs.add(kq);
+			kq.setBody(q.getBody());
+			AnswerOb ao = JSONObject.parseObject(q.getAnswer(), AnswerOb.class);
+			if ("判断题".equals(q.getType().trim())) {
+				kq.setStructType(3);
+				if("1".equals(ao.getAnswer().get(0).trim())) {
+					kq.setAnswer("true");
+				}else {
+					kq.setAnswer("false");
+				}
+
+			} else if ("单项选择题".equals(q.getType().trim()) || "多项选择题".equals(q.getType().trim())) {
+				int index = 0;
+				List<KdQuesOption> ops = new ArrayList<>();
+				StringBuilder sb = new StringBuilder();
+				for (String a : ao.getSubArea()) {
+					index++;
+					KdQuesOption op = new KdQuesOption();
+					op.setNumber(index);
+					op.setBody(a);
+					if (ao.getAnswer().contains((index-1)+"")) {
+						op.setSelect(true);
+						sb.append(index).append(",");
+					} else {
+						op.setSelect(false);
+					}
+					ops.add(op);
+				}
+				kq.setOptions(ops);
+				if (sb.length() > 0) {
+					sb.deleteCharAt(sb.length() - 1);
+				}
+				if (sb.indexOf(",") > 0) {
+					kq.setStructType(2);
+				} else {
+					kq.setStructType(1);
+				}
+				kq.setAnswer(sb.toString());
+			} else {
+				throw new RuntimeException("错误");
+			}
+		}
+		return kqs;
+	}
+
+	public static List<Question> read() throws IOException {
+		List<Question> list = new ArrayList<>();
+		XSSFWorkbook wb = null;
+		try {
+			wb = new XSSFWorkbook("d:\\dz.xlsx");
+			XSSFSheet sheet = wb.getSheetAt(0);
+			int rows = sheet.getLastRowNum();
+			for (int i = 1; i <= rows; i++) {
+				Question q = new Question();
+				XSSFRow row = sheet.getRow(i);
+				String b = row.getCell(1).getStringCellValue().trim();
+				String a = row.getCell(2).getStringCellValue().trim();
+				String t = row.getCell(4).getStringCellValue().trim();
+				q.setBody(b);
+				q.setAnswer(a);
+				q.setType(t);
+				list.add(q);
+			}
+		} finally {
+			if (wb != null) {
+				wb.close();
+			}
+		}
+		return list;
+	}
+}

+ 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 {
+
+}

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

@@ -0,0 +1,121 @@
+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, 8, 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("===============================================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);
+	}
+}

+ 158 - 0
src/main/java/cn/com/qmth/export/ExportProperty.java

@@ -0,0 +1,158 @@
+package cn.com.qmth.export;
+
+import java.io.File;
+import java.io.IOException;
+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.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.collections4.CollectionUtils;
+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;
+
+import com.alibaba.fastjson.JSONObject;
+
+public class ExportProperty {
+	private static Logger logger = LogManager.getLogger(ExportProperty.class);
+	
+	private static String dirPath="d:/yunkai/prop/";
+	public static void main(String[] args) {
+		logger.debug("导出开始");
+		try {
+			dispose();
+		} catch (Exception e) {
+			logger.error(e.getCause(), e);
+		}
+		logger.debug("导出结束");
+	}
+	
+	private static void dispose() {
+		File dir=new File(dirPath);
+		if(dir.exists()) {
+			FileUtil.clearDirectory(dirPath);
+		}else {
+			dir.mkdirs();
+		}
+		Connection connect = null;
+
+		try {
+			Class.forName("com.mysql.cj.jdbc.Driver");
+
+			String url = "jdbc:mysql://localhost:3306/yunkai_question?serverTimezone=GMT%2B8";
+
+			String user = "root";
+
+			String password = "123456";
+			connect = DriverManager.getConnection(url, user, password);
+			exportProperty(connect);
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (connect != null) {
+				try {
+					connect.close();
+				} catch (SQLException e) {
+				}
+			}
+		}
+	}
+
+	private static void exportProperty(Connection connect) throws InvalidFormatException, IOException, SQLException {
+		List<Long> subIds=readSubjectId();
+		if(CollectionUtils.isEmpty(subIds)) {
+			return;
+		}
+		for(Long subid:subIds) {
+			List<PropertyDto> props=getProperty(connect, subid);
+			if(CollectionUtils.isNotEmpty(props)) {
+				FileUtil.writeFile(dirPath, subid+".json", JSONObject.toJSONString(props));
+			}
+		}
+	}
+	private static List<Long> readSubjectId() throws InvalidFormatException, IOException {
+		File directory = new File("");
+		List<Long> list = new ArrayList<>();
+		Set<Long> set = new LinkedHashSet<>();
+		XSSFWorkbook wb = null;
+		try {
+			wb = new XSSFWorkbook(directory.getAbsolutePath() + "\\subject.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();
+				set.add(Long.valueOf(tem));
+			}
+		} finally {
+			if (wb != null) {
+				wb.close();
+			}
+		}
+		list.addAll(set);
+		return list;
+	}
+	
+	private static List<PropertyDto> getProperty(Connection connect, Long subjectId) throws SQLException, IOException {
+		List<PropertyDto> as = new ArrayList<>();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = " select k.id,k.parent_id,k.type_name,k.type_seq,k.root_id from wq_knowledge_system k "
+					+" where k.root_id in "
+					+" (select DISTINCT ss.root_id "
+					+" from wq_question_bank_subject t "
+					+" left join wq_question_bank b on t.question_bank_id=b.id "
+					+" left join wq_question_question_bank f on t.question_bank_id=f.question_bank_id "
+					+" left join wq_question q on f.question_id=q.id "
+					+" left join wq_question_knowledge_system s on f.question_id=s.question_id "
+					+" left join wq_knowledge_system ss on s.knowledge_system_id=ss.id "
+					+" where t.subject_id="+subjectId+" and b.rent_id=811 and b.is_deleted=0 "
+					+" and q.is_deleted=0 and q.rent_id=811) "
+					+" ORDER BY k.id ";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+			Map<Long,PropertyDto> map=new HashMap<>();
+			while (resultSet.next()) {
+				Long rootId=resultSet.getLong("root_id");
+				PropertyDto a = new PropertyDto();
+				a.setId(resultSet.getLong("id"));
+				a.setParentId(resultSet.getLong("parent_id"));
+				a.setName(resultSet.getString("type_name"));
+				if(a.getParentId()==0) {
+					as.add(a);
+				}else {
+					PropertyDto parent=map.get(a.getParentId());
+					if(parent.getParentId()==0) {
+						a.setName(a.getName());
+					}else {
+						a.setName(parent.getName()+"|"+a.getName());
+					}
+					PropertyDto root=map.get(rootId);
+					root.addSub(a);
+				}
+				map.put(a.getId(), a);
+			}
+			return as;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+}

+ 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);
+        }
+    }
+
+}

+ 32 - 0
src/main/java/cn/com/qmth/export/IdBase.java

@@ -0,0 +1,32 @@
+/*
+ * *************************************************
+ * Copyright (c) 2019 QMTH. All Rights Reserved.
+ * Created by Deason on 2019-05-05 14:12:35.
+ * *************************************************
+ */
+
+package cn.com.qmth.export;
+
+
+import java.io.Serializable;
+
+/**
+ * Mongo Object ID
+ *
+ * @author: QMTH
+ * @since: 2019/5/5
+ */
+public abstract class IdBase implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    protected String id;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+}

+ 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;
+	}
+	
+	
+}

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

@@ -0,0 +1,78 @@
+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;
+	private Boolean valid;
+	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;
+	}
+	public Boolean getValid() {
+		return valid;
+	}
+	public void setValid(Boolean valid) {
+		this.valid = valid;
+	}
+	
+	
+}

+ 38 - 0
src/main/java/cn/com/qmth/export/Knowledge.java

@@ -0,0 +1,38 @@
+package cn.com.qmth.export;
+
+public class Knowledge {
+	/**
+	 * 
+	 */
+
+	private Long id;
+
+	private Long parentId;
+
+	private Long rootId;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getParentId() {
+		return parentId;
+	}
+
+	public void setParentId(Long parentId) {
+		this.parentId = parentId;
+	}
+
+	public Long getRootId() {
+		return rootId;
+	}
+
+	public void setRootId(Long rootId) {
+		this.rootId = rootId;
+	}
+
+}

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

@@ -0,0 +1,10 @@
+package cn.com.qmth.export;
+
+public class MediaNotFoundException extends RuntimeException{
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -1479053529732022388L;
+
+}

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

@@ -0,0 +1,844 @@
+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 org.jsoup.nodes.Node;
+
+import com.alibaba.fastjson.JSONObject;
+
+public class MyConsumer extends Consumer<String> {
+	private String paperSuff = "(211)";
+	private int maxqc = 100;
+
+	private String excelDir = "D:\\kd_export\\";
+	private String picFiles = "D:\\files\\";
+
+	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://210.38.32.28:3306/moodle_question?serverTimezone=GMT%2B8";
+
+			String user = "questionreader";
+
+			String password = "xhhG88tdsk4Rvbgd";
+			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','multichoiceset') ";
+//			+ ") AND f.qtype = 'multichoiceset' ";
+			
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				KdQuestion q = new KdQuestion();
+				q.setId(resultSet.getLong("id"));
+				q.setBody(disBody(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);
+		}
+
+		qs = removeInvalidQuestion(qs, code);
+		if (qs.size() == 0) {
+			noques.add(code);
+			return;
+		}
+		File courseCode = new File(excelDir + c.getCode() + "\\");
+		courseCode.mkdirs();
+		File att = new File(excelDir + c.getCode() + "\\att\\");
+		att.mkdirs();
+		for (KdQuestion q : qs) {
+			try {
+				disposeMedia(connect, c.getIdnumber(), q, att);
+				q.setValid(true);
+			} catch (MediaNotFoundException e) {
+				q.setValid(false);
+			}
+		}
+		List<KdQuestion> tem = new ArrayList<>();
+		for (KdQuestion q : qs) {
+			if (q.getValid()) {
+				tem.add(q);
+			}
+		}
+		qs = tem;
+		if (qs.size() == 0) {
+			noques.add(code);
+			return;
+		}
+
+		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);
+			}
+		}
+		createPapers(single, c, 1);
+		createPapers(muti, c, 2);
+		createPapers(boo, c, 3);
+
+//		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 void createPapers(List<KdQuestion> qs, Course c, int structType) throws IOException {
+		if (qs == null || qs.size() == 0) {
+			return;
+		}
+		if (qs.size() <= maxqc) {
+			createPaper(qs, c, structType, 1);
+		} else {
+			int size = qs.size();
+			int len = maxqc;
+			int count = (size + len - 1) / len;
+
+			for (int i = 0; i < count; i++) {
+				List<KdQuestion> subList = qs.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
+				createPaper(subList, c, structType, i + 1);
+			}
+		}
+
+	}
+
+	private void createPaper(List<KdQuestion> qs, Course c, int structType, int indx) throws IOException {
+		if (qs.size() == 0) {
+			return;
+		}
+		String detailName="";
+		if (structType == 1) {
+			detailName="单选题";
+		} else if (structType == 2) {
+			detailName="多选题";
+		} else if (structType == 3) {
+			detailName="判断题";
+		}
+		KdPaper paper = new KdPaper();
+		paper.setTotalScore((double) qs.size());
+		paper.setName(c.getName()+paperSuff+detailName+"_"+indx);
+		paper.setCourseCode(c.getCode());
+		List<KdDetail> des = new ArrayList<>();
+		KdDetail d = new KdDetail();
+		d.setName(detailName);
+		d.setNumber(1);
+		d.setQuestionCount(qs.size());
+		d.setTotalScore((double) qs.size());
+		d.setQuestions(qs);
+		des.add(d);
+		paper.setDetails(des);
+		paper.setDetailCount(1);
+		File paperdir = new File(excelDir + c.getCode() + "\\paper"+structType+"_"+indx+"\\");
+		paperdir.mkdirs();
+		FileUtil.writeFile(paperdir.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(disBody(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 disBody(String html) {
+		Document doc = Jsoup.parse(html);
+		Element b = doc.body();
+		if (b.childrenSize() == 1 && "p".equals(b.child(0).nodeName())) {
+			b.child(0).tagName("span");
+		}
+		if (b.childrenSize() > 0) {
+			for (Element n : b.children()) {
+				if ("p".equals(n.nodeName())) {
+					boolean[] find = new boolean[] { false };
+					findImg(n, find);
+					if (!find[0] && StringUtils.isBlank(n.text())) {
+						n.tagName("span");
+					}
+				}
+				disNode(n);
+			}
+		}
+		return b.html();
+	}
+
+	private void findImg(Element e, boolean[] find) {
+		if (find[0]) {
+			return;
+		} else {
+			if ("img".equals(e.nodeName())) {
+				find[0] = true;
+				return;
+			} else {
+				if (e.childrenSize() > 0) {
+					for (Element ce : e.children()) {
+						findImg(ce, find);
+					}
+				} else {
+					return;
+				}
+			}
+		}
+	}
+
+	private static void disNode(Node n) {
+		if (n instanceof Element) {
+			if (!"img".equals(n.nodeName())) {
+				n.removeAttr("style");
+			}
+			if ("h1".equals(n.nodeName()) || "h2".equals(n.nodeName()) || "h3".equals(n.nodeName())
+					|| "h4".equals(n.nodeName()) || "h5".equals(n.nodeName())) {
+				((Element) n).tagName("h6");
+			}
+			if ("strong".equals(n.nodeName()) || "a".equals(n.nodeName()) || "u".equals(n.nodeName())
+					|| "em".equals(n.nodeName())) {
+				((Element) n).tagName("span");
+			}
+			if (n.childNodeSize() > 0) {
+				for (Node cn : n.childNodes()) {
+					disNode(cn);
+				}
+			}
+		}
+	}
+
+	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, "[未找到图片文件]");
+					throw new MediaNotFoundException();
+				} else {
+					ExportByCourseCode.addFd();
+					body = body.replaceAll(img.replaceAll("\\?", "\\\\?"), 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, "[未找到音频文件]");
+					throw new MediaNotFoundException();
+				} else {
+					ExportByCourseCode.addFd();
+					File newAudio = new File(att.getAbsoluteFile() + "\\" + file.getName());
+					newAudio.createNewFile();
+					FileUtils.copyFile(file, newAudio);
+					body = body.replaceAll(k.replaceAll("\\?", "\\\\?"), "<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, "[未找到图片文件]");
+							throw new MediaNotFoundException();
+						} else {
+							ExportByCourseCode.addFd();
+							obody = obody.replaceAll(img.replaceAll("\\?", "\\\\?"), 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, "[未找到音频文件]");
+							throw new MediaNotFoundException();
+						} else {
+							ExportByCourseCode.addFd();
+							File newAudio = new File(att.getAbsoluteFile() + "\\" + file.getName());
+							newAudio.createNewFile();
+							FileUtils.copyFile(file, newAudio);
+							obody = obody.replaceAll(k.replaceAll("\\?", "\\\\?"),
+									"<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 file = new File(picFiles + 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 file = new File(picFiles + 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;
+	}
+}

+ 48 - 0
src/main/java/cn/com/qmth/export/PicCompress.java

@@ -0,0 +1,48 @@
+package cn.com.qmth.export;
+
+import java.io.File;
+import java.io.IOException;
+
+import net.coobird.thumbnailator.Thumbnails;
+
+public class PicCompress {
+	private static long size = 100 * 1024;
+
+	private static int count=0;
+	public static void main(String[] args) {
+		File root = new File("d://files/");
+		try {
+			dis(root);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static void dis(File file) throws IOException {
+		if (file.isFile()) {
+			count++;
+			String suff = file.getName().substring(file.getName().lastIndexOf(".") + 1).toLowerCase();
+			if ((suff.equals("png") || suff.equals("jpg") || suff.equals("jpeg") || suff.equals("bmp"))
+					&& file.length() > size) {
+				compressImageFiles(file);
+			}
+			System.out.println(count);
+		} else if (file.listFiles().length > 0) {
+			for (File cf : file.listFiles()) {
+				dis(cf);
+			}
+		}
+	}
+
+	private static void compressImageFiles(File file) throws IOException {
+		Thumbnails.of(file)
+//                .size(800, 800) //按指定大小进行缩放
+//                 .scale(0.8f) //按比例缩小
+//                .scale(1.5f) //按比例放大
+				.scale(1.0f).outputQuality(0.5f) // 尺寸不变,质量压缩
+//                .scale(1.0f).rotate(90) //尺寸不变,旋转N度(0,90,180,270,45)
+//                 .outputFormat("jpg") //按格式输出
+				// .toFiles(Rename.SUFFIX_HYPHEN_THUMBNAIL);
+				.toFile(file);
+	}
+}

+ 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;
+	}
+
+	
+}

+ 50 - 0
src/main/java/cn/com/qmth/export/PropertyDto.java

@@ -0,0 +1,50 @@
+package cn.com.qmth.export;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PropertyDto {
+
+	private Long id;
+	private Long parentId;
+	private String name;
+
+	private List<PropertyDto> subPropertyDto = new ArrayList<>();
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public List<PropertyDto> getSubPropertyDto() {
+		return subPropertyDto;
+	}
+
+	public void setSubPropertyDto(List<PropertyDto> subPropertyDto) {
+		this.subPropertyDto = subPropertyDto;
+	}
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public void addSub(PropertyDto sub) {
+		subPropertyDto.add(sub);
+	}
+
+	public Long getParentId() {
+		return parentId;
+	}
+
+	public void setParentId(Long parentId) {
+		this.parentId = parentId;
+	}
+
+}

+ 26 - 0
src/main/java/cn/com/qmth/export/Question.java

@@ -0,0 +1,26 @@
+package cn.com.qmth.export;
+
+public class Question {
+	private String body;
+	private String answer;
+	private String type;
+	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 String getType() {
+		return type;
+	}
+	public void setType(String type) {
+		this.type = type;
+	}
+	
+}

+ 123 - 0
src/main/java/cn/com/qmth/export/SetRootId.java

@@ -0,0 +1,123 @@
+package cn.com.qmth.export;
+
+import java.io.IOException;
+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.List;
+import java.util.Map;
+
+public class SetRootId {
+	public static void main(String[] args) {
+		System.out.println("开始处理");
+		Connection connect = null;
+
+		try {
+			Class.forName("com.mysql.cj.jdbc.Driver");
+
+			String url = "jdbc:mysql://localhost:3306/yunkai_question?serverTimezone=GMT%2B8";
+
+			String user = "root";
+
+			String password = "123456";
+			connect = DriverManager.getConnection(url, user, password);
+			List<Knowledge> ks=getKnowledge(connect);
+			setRootId(ks);
+			if (ks.size() <= 1000) {
+				updateKnowledge(connect, ks);
+				System.out.println("处理:1");
+			} else {
+				int size = ks.size();
+				int len = 1000;
+				int count = (size + len - 1) / len;
+
+				for (int i = 0; i < count; i++) {
+					List<Knowledge> subList = ks.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
+					updateKnowledge(connect, subList);
+					System.out.println("处理:"+i+"/"+count);
+				}
+			}
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (connect != null) {
+				try {
+					connect.close();
+				} catch (SQLException e) {
+				}
+			}
+			System.out.println("结束处理");
+		}
+	}
+	
+	private static void updateKnowledge(Connection connect,List<Knowledge> ks) throws SQLException, IOException {
+		PreparedStatement preState = null;
+		try {
+			String sql = "update wq_knowledge_system set root_id=? where id=?";
+			preState = connect.prepareStatement(sql);
+
+			for(Knowledge k:ks) {
+				preState.setLong(1, k.getRootId());
+				preState.setLong(2, k.getId());
+				preState.addBatch();
+			}
+			preState.executeBatch();
+			
+		} finally {
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+	
+	private static void setRootId(List<Knowledge> ks) {
+		Map<Long,Knowledge> map=new HashMap<>();
+		for(Knowledge k:ks) {
+			map.put(k.getId(), k);
+		}
+		for(Knowledge k:ks) {
+			getAndSetRootId(map, k);
+		}
+	}
+	
+	private static void getAndSetRootId(Map<Long,Knowledge> map,Knowledge k) {
+		if(k.getParentId()==0) {
+			k.setRootId(k.getId());
+		}else {
+			Knowledge pk=map.get(k.getParentId());
+			getAndSetRootId(map, pk);
+			k.setRootId(pk.getRootId());
+		}
+	}
+
+	private static List<Knowledge> getKnowledge(Connection connect) throws SQLException, IOException {
+		List<Knowledge> as = new ArrayList<>();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = "select id,parent_id,root_id from wq_knowledge_system order by parent_id,id";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				Knowledge a = new Knowledge();
+				a.setId(resultSet.getLong("id"));
+				a.setParentId(resultSet.getLong("parent_id"));
+				as.add(a);
+			}
+			return as;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+}

+ 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>

BIN
subject.xlsx