xiatian 1 年之前
當前提交
fcc94e78cf
共有 41 個文件被更改,包括 3707 次插入0 次删除
  1. 20 0
      .gitignore
  2. 二進制
      file/subject_info.xlsx
  3. 62 0
      file/临时.txt
  4. 113 0
      pom.xml
  5. 33 0
      src/main/java/cn/com/qmth/export/Answer.java
  6. 25 0
      src/main/java/cn/com/qmth/export/AnswerVo.java
  7. 101 0
      src/main/java/cn/com/qmth/export/Basket.java
  8. 53 0
      src/main/java/cn/com/qmth/export/BatchGetDataUtil.java
  9. 169 0
      src/main/java/cn/com/qmth/export/Calculator.java
  10. 40 0
      src/main/java/cn/com/qmth/export/ChuanNongExportPaperByCourseCode.java
  11. 47 0
      src/main/java/cn/com/qmth/export/ChuanNongExportProperty.java
  12. 74 0
      src/main/java/cn/com/qmth/export/Consumer.java
  13. 28 0
      src/main/java/cn/com/qmth/export/Course.java
  14. 55 0
      src/main/java/cn/com/qmth/export/DetailDto.java
  15. 55 0
      src/main/java/cn/com/qmth/export/DetailDto2.java
  16. 10 0
      src/main/java/cn/com/qmth/export/EndObject.java
  17. 782 0
      src/main/java/cn/com/qmth/export/FileUtil.java
  18. 25 0
      src/main/java/cn/com/qmth/export/FillBlankAnswerVo.java
  19. 32 0
      src/main/java/cn/com/qmth/export/IdBase.java
  20. 43 0
      src/main/java/cn/com/qmth/export/KdDetail.java
  21. 56 0
      src/main/java/cn/com/qmth/export/KdPaper.java
  22. 26 0
      src/main/java/cn/com/qmth/export/KdQuesOption.java
  23. 160 0
      src/main/java/cn/com/qmth/export/KdQuestion.java
  24. 39 0
      src/main/java/cn/com/qmth/export/Knowledge.java
  25. 10 0
      src/main/java/cn/com/qmth/export/MediaNotFoundException.java
  26. 517 0
      src/main/java/cn/com/qmth/export/MyConsumer.java
  27. 114 0
      src/main/java/cn/com/qmth/export/MyConsumer2.java
  28. 123 0
      src/main/java/cn/com/qmth/export/MyProducer.java
  29. 69 0
      src/main/java/cn/com/qmth/export/MyProducer2.java
  30. 46 0
      src/main/java/cn/com/qmth/export/PaperExportDto.java
  31. 48 0
      src/main/java/cn/com/qmth/export/PicCompress.java
  32. 152 0
      src/main/java/cn/com/qmth/export/Producer.java
  33. 50 0
      src/main/java/cn/com/qmth/export/PropertyDto.java
  34. 77 0
      src/main/java/cn/com/qmth/export/QuesStructType.java
  35. 26 0
      src/main/java/cn/com/qmth/export/Question.java
  36. 23 0
      src/main/java/cn/com/qmth/export/QuestionProp.java
  37. 86 0
      src/main/java/cn/com/qmth/export/QuestionVo.java
  38. 124 0
      src/main/java/cn/com/qmth/export/SetRootId.java
  39. 73 0
      src/main/java/cn/com/qmth/export/StatusException.java
  40. 89 0
      src/main/java/cn/com/qmth/export/YunkaiDifficulty.java
  41. 32 0
      src/main/resources/log4j2.xml

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

二進制
file/subject_info.xlsx


+ 62 - 0
file/临时.txt

@@ -0,0 +1,62 @@
+--状态:正常;审核通过;排除上传题
+SELECT ShiTiALL.* 
+FROM dbo.Exam_e_QuestionInfo AS ShiTiALL
+INNER JOIN dbo.Exam_e_QuestionType AS LeiXingALL
+ON LeiXingALL.QuestionTypeID = ShiTiALL.QuestionTypeID
+WHERE 1=1
+AND LeiXingALL.QuestionTypeName not LIKE '%上传%'
+AND ShiTiALL.Status=0--正常
+AND ShiTiALL.AuditingFlag=1--审核通过;
+
+select c.CourseCode,c.CourseName from exam_e_courseinfo c 
+inner join 
+( SELECT distinct q.CourseCode
+FROM Exam_e_QuestionInfo AS q
+INNER JOIN Exam_e_QuestionType AS t
+ON t.QuestionTypeID = q.QuestionTypeID
+WHERE 1=1
+AND t.QuestionTypeName not LIKE '%上传%'
+AND q.Status=0
+AND q.AuditingFlag=1
+and q.OuterSystemID=3372)tm
+on c.CourseCode=tm.CourseCode
+where c.SystemID=3372;
+
+select tm.* from
+(select count(DISTINCT t.root_id) cc,t.CourseCode
+from exam_e_coursechapter t
+inner join temp_course c on t.CourseCode=c.`code`
+inner join exam_e_questioninfo q on t.ChapterID=q.ChapterID
+where t.OuterSystemID=3372 and t.ParentChapterlID !=0
+GROUP BY t.CourseCode)
+tm where tm.cc=1;
+
+select t.ChapterID id,t.ParentChapterlID parent_id,t.root_id,t.ChapterTitle type_name
+from exam_e_coursechapter t
+where t.OuterSystemID=3372 and t.CourseCode='209001' and t.ParentChapterlID !=0
+and EXISTS(select 1 from exam_e_questioninfo q where t.ChapterID=q.ChapterID)
+order by t.OrderNumber;
+
+
+http://any.cnzx.info:81/
+
+成教考试平台的前缀
+
+是只筛选3372系统的数据
+
+answerType 1-单选
+2-多选
+3-简单文本
+4-复杂文本
+5-文件上传
+6-英译汉
+7-汉译英
+
+1-易
+3-中
+5-难
+
+题型表获取OuterSystemlD in (0,3372)就行
+
+主观题就是简答题
+填空题没有空标识当简答题处理

+ 113 - 0
pom.xml

@@ -0,0 +1,113 @@
+<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>chuannong-export</groupId>
+	<artifactId>chuannong-export</artifactId>
+	<version>1.0.0-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>
+		<finalName>${project.artifactId}</finalName>
+
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+				<configuration>
+					<includeSystemScope>true</includeSystemScope>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>

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

@@ -0,0 +1,33 @@
+package cn.com.qmth.export;
+
+public class Answer {
+	private Integer questionId;
+	private String body;
+	private Integer right;
+
+	public String getBody() {
+		return body;
+	}
+
+	public void setBody(String body) {
+		this.body = body;
+	}
+
+	public Integer getRight() {
+		return right;
+	}
+
+	public void setRight(Integer right) {
+		this.right = right;
+	}
+
+	public Integer getQuestionId() {
+		return questionId;
+	}
+
+	public void setQuestionId(Integer questionId) {
+		this.questionId = questionId;
+	}
+
+
+}

+ 25 - 0
src/main/java/cn/com/qmth/export/AnswerVo.java

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

+ 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, 10, TimeUnit.SECONDS);
+			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(10, TimeUnit.SECONDS);
+			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;
+	}
+
+}

+ 53 - 0
src/main/java/cn/com/qmth/export/BatchGetDataUtil.java

@@ -0,0 +1,53 @@
+package cn.com.qmth.export;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+/**
+ *	多次批量获取数据
+ * @author xiatian
+ * @param <R> 结果类
+ * @param <P> 参数类
+ */
+public abstract  class BatchGetDataUtil<R,P> {
+	/**
+	 * @param resultList 全部结果集合
+	 * @param paramList 全部参数集合
+	 * @param batchSize 每批参数数量
+	 */
+	public final List<R> getDataForBatch(List<P> paramList,int batchSize) {
+		if(CollectionUtils.isEmpty(paramList)) {
+			return null;
+		}
+		List<R> resultList=new ArrayList<>();
+		if(paramList.size()<=batchSize) {
+			List<R> temlist = getData(paramList);
+			if(temlist!=null&&temlist.size()>0) {
+				resultList.addAll(temlist);
+			}
+		}else {
+			int size = paramList.size();
+			int len=batchSize;
+			int count = (size + len - 1) / len;
+
+			for (int i = 0; i < count; i++) {
+				List<P> subList = paramList.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
+				List<R> temlist = getData(subList);
+				if(temlist!=null&&temlist.size()>0) {
+					resultList.addAll(temlist);
+				}
+			}
+		}
+		return resultList;
+	}
+	/**
+	 * 	每批获取数据方法
+	 * @param <R>
+	 * @param <P>
+	 * @param paramList 获取每批数据时参数
+	 * @return
+	 */
+	protected abstract  List<R> getData(List<P> paramList);
+}

+ 169 - 0
src/main/java/cn/com/qmth/export/Calculator.java

@@ -0,0 +1,169 @@
+package cn.com.qmth.export;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+
+/**
+ * 计算器
+ *
+ * @author
+ * @date 2019年7月30日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class Calculator {
+
+    /**
+     * 加法 保留两位小数
+     * 
+     * @param v1
+     * @param v2
+     * @return
+     */
+    public static double add(double v1, double v2) {
+        return add(v1, v2, 2);
+
+    }
+
+    /**
+     * 加法 保留指定位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double add(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return b1.add(b2).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+
+    }
+    
+    /** 加法 保留指定位小数
+     * @param ds
+     * @param len
+     * @return
+     */
+    public static double add(List<Double> ds, int len) {
+        if(CollectionUtils.isEmpty(ds)) {
+            throw new StatusException("数组为空");
+        }
+        BigDecimal ret = new BigDecimal(0.0);
+        for(Double d:ds) {
+            if(d==null) {
+                throw new StatusException("数组元素为空");
+            }
+            ret=ret.add(new BigDecimal(d));
+        }
+        return ret.setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+
+    }
+
+    /**
+     * 减法 保留两位小数
+     * 
+     * @param v1
+     * @param v2
+     * @return
+     */
+    public static double subtract(double v1, double v2) {
+        return subtract(v1, v2, 2);
+
+    }
+
+    /**
+     * 减法 保留指定位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double subtract(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return b1.subtract(b2).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+
+    }
+    /**差值绝对值
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double absoluteDiff(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        if(v1>v2) {
+            return b1.subtract(b2).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+        }else {
+            return b2.subtract(b1).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+        }
+
+    }
+    /**
+     * 乘法 保留两位小数
+     * 
+     * @param v1
+     * @param v2
+     * @return
+     */
+    public static double multiply(double v1, double v2) {
+        return multiply(v1, v2, 2);
+
+    }
+
+    /**
+     * 乘法 保留指定位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double multiply(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return b1.multiply(b2).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+
+    }
+    
+    
+    /**
+     * 除法 保留两位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double divide(double v1, double v2) {
+        return divide(v1, v2, 2);
+    }
+
+    /**
+     * 除法 保留指定位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double divide(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return b1.divide(b2, len, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+    public static String divide2String(Double v1, Double v2, int len) {
+        if(v1==null||v2==null||v2==0) {
+            return "-";
+        }
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return String.valueOf(b1.divide(b2, len, BigDecimal.ROUND_HALF_UP).doubleValue());
+    }
+}

+ 40 - 0
src/main/java/cn/com/qmth/export/ChuanNongExportPaperByCourseCode.java

@@ -0,0 +1,40 @@
+package cn.com.qmth.export;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+public class ChuanNongExportPaperByCourseCode {
+	private static Logger logger = LogManager.getLogger(ChuanNongExportPaperByCourseCode.class);
+	//试卷后缀
+	private static String paperSuff = "(240418)";
+	//数据库名
+	private static String dbName="chuannong";
+	private static AtomicInteger count=new AtomicInteger(0);
+	
+	public static void main(String[] args) {
+		logger.debug("导出开始");
+		Date start=new Date();
+		try {
+			MyProducer pro=new MyProducer();
+			Map<String, Object> param=new HashMap<>();
+			param.put("paperSuff", paperSuff);
+			param.put("dbName", dbName);
+			pro.startDispose(MyConsumer.class, 8, param);
+		} catch (Exception e) {
+			logger.error(e.getCause(), e);
+		}
+		Date end=new Date();
+		logger.debug("导出结束,耗时:"+((end.getTime()-start.getTime())/1000));
+	}
+
+
+	public static void  addDisposeCount() {
+		count.addAndGet(1);
+		logger.debug("处理了"+count);
+	}
+}

+ 47 - 0
src/main/java/cn/com/qmth/export/ChuanNongExportProperty.java

@@ -0,0 +1,47 @@
+package cn.com.qmth.export;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+public class ChuanNongExportProperty {
+	private static Logger logger = LogManager.getLogger(ChuanNongExportProperty.class);
+	private static AtomicInteger count=new AtomicInteger(0);
+	private static String dbName="chuannong";
+	private final static String sourceDir = "d:/chuannong/";
+	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(sourceDir+"prop/");
+		if(dir.exists()) {
+			FileUtil.clearDirectory(sourceDir+"prop/");
+		}else {
+			dir.mkdirs();
+		}
+		try {
+			MyProducer2 pro=new MyProducer2();
+			Map<String, Object> param=new HashMap<>();
+			param.put("dbName", dbName);
+			pro.startDispose(MyConsumer2.class, 8, param);
+		} catch (Exception e) {
+			logger.error(e.getCause(), e);
+		}
+	}
+	
+	public static void  addDisposeCount() {
+		count.addAndGet(1);
+		logger.debug("处理了"+count);
+	}
+}

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

@@ -0,0 +1,74 @@
+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 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;
+	}
+	
+}

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

@@ -0,0 +1,28 @@
+package cn.com.qmth.export;
+
+public class Course {
+	private String ecCode;
+	private String yunkaiCode;
+	private String name;
+	
+	public String getEcCode() {
+		return ecCode;
+	}
+	public void setEcCode(String ecCode) {
+		this.ecCode = ecCode;
+	}
+	public String getYunkaiCode() {
+		return yunkaiCode;
+	}
+	public void setYunkaiCode(String yunkaiCode) {
+		this.yunkaiCode = yunkaiCode;
+	}
+	public String getName() {
+		return name;
+	}
+	public void setName(String name) {
+		this.name = name;
+	}
+
+
+}

+ 55 - 0
src/main/java/cn/com/qmth/export/DetailDto.java

@@ -0,0 +1,55 @@
+package cn.com.qmth.export;
+
+public class DetailDto {
+	private String typeId;
+	private String typeName;
+	
+	
+	public DetailDto(String typeId, String typeName) {
+		super();
+		this.typeId = typeId;
+		this.typeName = typeName;
+	}
+	public String getTypeId() {
+		return typeId;
+	}
+	public void setTypeId(String typeId) {
+		this.typeId = typeId;
+	}
+	public String getTypeName() {
+		return typeName;
+	}
+	public void setTypeName(String typeName) {
+		this.typeName = typeName;
+	}
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((typeId == null) ? 0 : typeId.hashCode());
+		result = prime * result + ((typeName == null) ? 0 : typeName.hashCode());
+		return result;
+	}
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		DetailDto other = (DetailDto) obj;
+		if (typeId == null) {
+			if (other.typeId != null)
+				return false;
+		} else if (!typeId.equals(other.typeId))
+			return false;
+		if (typeName == null) {
+			if (other.typeName != null)
+				return false;
+		} else if (!typeName.equals(other.typeName))
+			return false;
+		return true;
+	}
+	
+}

+ 55 - 0
src/main/java/cn/com/qmth/export/DetailDto2.java

@@ -0,0 +1,55 @@
+package cn.com.qmth.export;
+
+public class DetailDto2 {
+	private String pname;
+	private String tname;
+	public String getPname() {
+		return pname;
+	}
+	public void setPname(String pname) {
+		this.pname = pname;
+	}
+	public String getTname() {
+		return tname;
+	}
+	public void setTname(String tname) {
+		this.tname = tname;
+	}
+	public DetailDto2(String pname, String tname) {
+		super();
+		this.pname = pname;
+		this.tname = tname;
+	}
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((pname == null) ? 0 : pname.hashCode());
+		result = prime * result + ((tname == null) ? 0 : tname.hashCode());
+		return result;
+	}
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		DetailDto2 other = (DetailDto2) obj;
+		if (pname == null) {
+			if (other.pname != null)
+				return false;
+		} else if (!pname.equals(other.pname))
+			return false;
+		if (tname == null) {
+			if (other.tname != null)
+				return false;
+		} else if (!tname.equals(other.tname))
+			return false;
+		return true;
+	}
+	
+	
+	
+}

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

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

@@ -0,0 +1,782 @@
+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();
+            connection.setConnectTimeout(30000); 
+            connection.setReadTimeout(30000); 
+        } 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("下载出错:"+fileUrl,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);
+        }
+    }
+
+}

+ 25 - 0
src/main/java/cn/com/qmth/export/FillBlankAnswerVo.java

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

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

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

@@ -0,0 +1,56 @@
+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 Integer unitCount;
+	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;
+	}
+	public Integer getUnitCount() {
+		return unitCount;
+	}
+	public void setUnitCount(Integer unitCount) {
+		this.unitCount = unitCount;
+	}
+	
+}

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

@@ -0,0 +1,26 @@
+package cn.com.qmth.export;
+
+public class KdQuesOption {
+	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;
+	}
+	
+}

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

@@ -0,0 +1,160 @@
+package cn.com.qmth.export;
+
+import java.util.List;
+
+public class KdQuestion {
+	private String pname;
+	private String tname;
+	private String cname;
+	private Integer id;
+	private Integer pid;
+	private String typeId;
+	private String detailName;
+	private QuesStructType quesStructType;
+	private YunkaiDifficulty difficulty;
+	private String body;
+	private String answer;
+	private Boolean haveAudio;
+	private List<KdQuesOption> options;
+	private List<Long> propIds;
+	private Double score;
+	private List<Double> subScores;
+	private List<KdQuestion> subQuestions;
+
+	public Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer id) {
+		this.id = id;
+	}
+
+	public QuesStructType getQuesStructType() {
+		return quesStructType;
+	}
+
+	public void setQuesStructType(QuesStructType quesStructType) {
+		this.quesStructType = quesStructType;
+	}
+
+	public YunkaiDifficulty getDifficulty() {
+		return difficulty;
+	}
+
+	public void setDifficulty(YunkaiDifficulty difficulty) {
+		this.difficulty = difficulty;
+	}
+
+	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 Boolean getHaveAudio() {
+		return haveAudio;
+	}
+
+	public void setHaveAudio(Boolean haveAudio) {
+		this.haveAudio = haveAudio;
+	}
+
+	public List<KdQuesOption> getOptions() {
+		return options;
+	}
+
+	public void setOptions(List<KdQuesOption> options) {
+		this.options = options;
+	}
+
+	public List<Long> getPropIds() {
+		return propIds;
+	}
+
+	public void setPropIds(List<Long> propIds) {
+		this.propIds = propIds;
+	}
+
+	public Double getScore() {
+		return score;
+	}
+
+	public void setScore(Double score) {
+		this.score = score;
+	}
+
+	public List<Double> getSubScores() {
+		return subScores;
+	}
+
+	public void setSubScores(List<Double> subScores) {
+		this.subScores = subScores;
+	}
+
+	public List<KdQuestion> getSubQuestions() {
+		return subQuestions;
+	}
+
+	public void setSubQuestions(List<KdQuestion> subQuestions) {
+		this.subQuestions = subQuestions;
+	}
+
+	public String getDetailName() {
+		return detailName;
+	}
+
+	public void setDetailName(String detailName) {
+		this.detailName = detailName;
+	}
+
+	public String getTypeId() {
+		return typeId;
+	}
+
+	public void setTypeId(String typeId) {
+		this.typeId = typeId;
+	}
+
+	public String getPname() {
+		return pname;
+	}
+
+	public void setPname(String pname) {
+		this.pname = pname;
+	}
+
+	public String getTname() {
+		return tname;
+	}
+
+	public void setTname(String tname) {
+		this.tname = tname;
+	}
+
+	public String getCname() {
+		return cname;
+	}
+
+	public void setCname(String cname) {
+		this.cname = cname;
+	}
+
+	public Integer getPid() {
+		return pid;
+	}
+
+	public void setPid(Integer pid) {
+		this.pid = pid;
+	}
+
+}

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

@@ -0,0 +1,39 @@
+package cn.com.qmth.export;
+
+public class Knowledge {
+	/**
+	 * 
+	 */
+
+	private Integer id;
+
+	private Integer parentId;
+
+	private Integer rootId;
+
+	public Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer id) {
+		this.id = id;
+	}
+
+	public Integer getParentId() {
+		return parentId;
+	}
+
+	public void setParentId(Integer parentId) {
+		this.parentId = parentId;
+	}
+
+	public Integer getRootId() {
+		return rootId;
+	}
+
+	public void setRootId(Integer 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;
+
+}

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

@@ -0,0 +1,517 @@
+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.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import com.alibaba.fastjson.JSONObject;
+
+public class MyConsumer extends Consumer<PaperExportDto> {
+	private int maxqc = 200;
+	private static String[] sucStr = new String[] { "对", "正确", "T", "是", "True", "right", "能" };
+	private static String[] errStr = new String[] { "错", "错误", "F", "不正确", "否", "False", "wrong", "不能" };
+	private static String paperDir = "d:/chuannong/paper/";
+	private static String imgDir = "d:/chuannong/img";
+	private static String imgDomain = "http://any.cnzx.info:81";
+
+	private Pattern imgPat = Pattern.compile("<img[^<]+src=['\"]([^<\"]+)['\"][^<]*>");
+
+	@Override
+	public void consume(PaperExportDto dto) {
+		Connection connect = null;
+		File sub = new File(paperDir + dto.getCourseCode() + "/");
+		sub.mkdir();
+		try {
+			Class.forName("com.mysql.cj.jdbc.Driver");
+
+			String url = "jdbc:mysql://localhost:3306/" + dto.getDbName() + "?serverTimezone=GMT%2B8";
+
+			String user = "root";
+
+			String password = "123456";
+			connect = DriverManager.getConnection(url, user, password);
+			exportPaper(connect, dto);
+			ChuanNongExportPaperByCourseCode.addDisposeCount();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (connect != null) {
+				try {
+					connect.close();
+				} catch (SQLException e) {
+				}
+			}
+		}
+	}
+
+	private List<KdQuestion> of(List<QuestionVo> vos, List<QuestionVo> subvosList) {
+		List<KdQuestion> ret = new ArrayList<>();
+		Map<Integer, List<QuestionVo>> submap = new HashMap<>();
+		if (CollectionUtils.isNotEmpty(subvosList)) {
+			for (QuestionVo vo : subvosList) {
+				List<QuestionVo> tem = submap.get(vo.getPid());
+				if (tem == null) {
+					tem = new ArrayList<>();
+					submap.put(vo.getPid(), tem);
+				}
+				tem.add(vo);
+			}
+		}
+		for (QuestionVo vo : vos) {
+			try {
+				KdQuestion q = of(vo);
+				if (QuesStructType.NESTED_ANSWER_QUESTION.equals(q.getQuesStructType())) {
+					List<QuestionVo> subvos = submap.get(q.getId());
+					try {
+						q.setScore((double) subvos.size());
+					} catch (Exception e) {
+						throw e;
+					}
+					List<Double> subScores = new ArrayList<>();
+					List<KdQuestion> subQues = new ArrayList<>();
+					q.setSubScores(subScores);
+					q.setSubQuestions(subQues);
+//					int num = 0;
+					for (QuestionVo subvo : subvos) {
+//						num++;
+						subScores.add(1.0);
+						KdQuestion subq = of(subvo);
+						subq.setId(null);
+//						if (CusQuesStructType.cloze.equals(q.getCusType()) && StringUtils.isBlank(subq.getBody())) {
+//							subq.setBody("___" + num + "___");
+//						}
+						subQues.add(subq);
+					}
+				}
+				ret.add(q);
+			} catch (StatusException e) {
+				continue;
+			}
+		}
+		return ret;
+	}
+
+	private KdQuestion of(QuestionVo vo) {
+		KdQuestion q = new KdQuestion();
+		q.setQuesStructType(vo.getqType());
+		q.setDetailName(vo.getTypeName());
+		q.setBody(vo.getBody());
+		q.setDifficulty(vo.getDifficulty());
+		q.setHaveAudio(false);
+		q.setId(vo.getQid());
+		q.setScore(1.0);
+		disposeQuestionImg(q);
+
+		return q;
+	}
+
+	private List<QuestionVo> getQuestion(Connection connect, PaperExportDto dto) throws SQLException, IOException {
+		List<QuestionVo> qs = new ArrayList<>();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = " SELECT q.QuestionID qid,q.QuestionDesc body,t.QuestionTypeName typeName, q.AnswerTypeID atype,q.DiffCoef "
+					+ " FROM Exam_e_QuestionInfo AS q " + " INNER JOIN Exam_e_QuestionType AS t "
+					+ " ON t.QuestionTypeID = q.QuestionTypeID " + " WHERE  t.QuestionTypeName not LIKE '%上传%' "
+					+ " AND q.Status=0 AND q.AuditingFlag=1 and q.OuterSystemID=3372 "
+					+ " and q.ParentQuesID=0 and q.CourseCode='" + dto.getCourseCode() + "' ";
+
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				QuestionVo q = new QuestionVo();
+				q.setQid(resultSet.getInt("qid"));
+				String typeName = resultSet.getString("typeName");
+				q.setTypeName(typeName);
+				q.setDifficulty(getDiff(resultSet.getInt("DiffCoef")));
+				q.setAtype(resultSet.getInt("atype"));
+				q.setqType(getQuesType(q.getQid(), typeName, dto.getNestedQids()));
+				q.setBody(resultSet.getString("body"));
+				qs.add(q);
+			}
+			return qs;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	private YunkaiDifficulty getDiff(Integer diff) {
+		if (diff == 1) {
+			return YunkaiDifficulty.RONGYI;
+		} else if (diff == 2) {
+			return YunkaiDifficulty.JIAORONGYI;
+		} else if (diff == 3) {
+			return YunkaiDifficulty.ZHONGDENG;
+		} else if (diff == 4) {
+			return YunkaiDifficulty.JIAONAN;
+		} else if (diff == 5) {
+			return YunkaiDifficulty.KUNNAN;
+		}
+		throw new StatusException("难度值错误");
+	}
+
+	private QuesStructType getQuesType(Integer qid, String typeName, Set<Integer> nestedQids) {
+		if (nestedQids.contains(qid)) {
+			return QuesStructType.NESTED_ANSWER_QUESTION;
+		}
+		if (typeName.startsWith("单选")) {
+			return QuesStructType.SINGLE_ANSWER_QUESTION;
+		}
+		if (typeName.startsWith("多选")) {
+			return QuesStructType.MULTIPLE_ANSWER_QUESTION;
+		}
+		if (typeName.startsWith("判断")) {
+			return QuesStructType.BOOL_ANSWER_QUESTION;
+		}
+		return QuesStructType.TEXT_ANSWER_QUESTION;
+	}
+
+	private List<QuestionVo> getSubQuestion(Connection connect, List<Integer> pids) throws SQLException, IOException {
+		List<QuestionVo> qs = new ArrayList<>();
+		if (CollectionUtils.isEmpty(pids)) {
+			return qs;
+		}
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = " SELECT q.QuestionID qid,q.QuestionDesc body ,q.DiffCoef,q.ParentQuesID pid "
+					+ " FROM Exam_e_QuestionInfo AS q  where q.ParentQuesID in (" + StringUtils.join(pids, ",")
+					+ ") order by q.OrderFlag";
+
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				QuestionVo q = new QuestionVo();
+				q.setQid(resultSet.getInt("qid"));
+				q.setPid(resultSet.getInt("pid"));
+				q.setDifficulty(getDiff(resultSet.getInt("DiffCoef")));
+				q.setqType(QuesStructType.SINGLE_ANSWER_QUESTION);
+				q.setBody(resultSet.getString("body"));
+				qs.add(q);
+			}
+			return qs;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	private void disposeQuestionImg(KdQuestion q) {
+		q.setBody(disposeImg(q.getBody(), q.getId()));
+		if (CollectionUtils.isNotEmpty(q.getOptions())) {
+			for (KdQuesOption o : q.getOptions()) {
+				o.setBody(disposeImg(o.getBody(), q.getId()));
+			}
+		}
+		q.setAnswer(disposeImg(q.getAnswer(), q.getId()));
+	}
+
+	private String disposeImg(String str, Integer qid) {
+		try {
+			if (StringUtils.isBlank(str)) {
+				return str;
+			}
+			Matcher matcher = imgPat.matcher(str);
+			Map<String, String> srcMap = new HashMap<>();
+			while (matcher.find()) {
+				String imgSrc = matcher.group(1).trim();
+				if (imgSrc.startsWith("data:image/")) {
+
+				} else if (imgSrc.startsWith("/")) {
+					if (srcMap.get(imgSrc) == null) {
+						String[] ss = imgSrc.split("\\?");
+						String suff = ss[0].substring(ss[0].lastIndexOf("."));
+						File img = new File(imgDir + "/" + UUID.randomUUID() + suff);
+						FileUtil.saveUrlAs(imgDomain + imgSrc, img.getAbsolutePath());
+						String base64 = FileUtil.fileToBase64Src(img);
+						img.delete();
+						srcMap.put(imgSrc, base64);
+					}
+				} else if (imgSrc.toLowerCase().trim().startsWith("http")) {
+					if (srcMap.get(imgSrc) == null) {
+						String[] ss = imgSrc.split("\\?");
+						String suff = ss[0].substring(ss[0].lastIndexOf("."));
+						File img = new File(imgDir + "/" + UUID.randomUUID() + suff);
+						FileUtil.saveUrlAs(imgSrc, img.getAbsolutePath());
+						String base64 = FileUtil.fileToBase64Src(img);
+						img.delete();
+						srcMap.put(imgSrc, base64);
+					}
+				} else {
+					throw new StatusException("图片有误:" + qid);
+				}
+			}
+			for (String imgSrc : srcMap.keySet()) {
+				str = str.replaceAll(imgSrc, srcMap.get(imgSrc));
+			}
+			return str;
+		} catch (Exception e) {
+			throw new RuntimeException("图片有误:" + qid, e);
+		}
+	}
+
+	private void exportPaper(Connection connect, PaperExportDto dto) throws Exception {
+		List<QuestionVo> vos = getQuestion(connect, dto);
+		if (CollectionUtils.isEmpty(vos)) {
+			return;
+		}
+		List<Integer> pids = vos.stream().filter(e -> QuesStructType.NESTED_ANSWER_QUESTION.equals(e.getqType()))
+				.map(QuestionVo::getQid).collect(Collectors.toList());
+		List<QuestionVo> subvos = getSubQuestion(connect, pids);
+		List<KdQuestion> qs = of(vos, subvos);
+//		fillProp(connect, qs);
+		fillAnswer(connect, qs);
+
+		Map<DetailDto, List<KdQuestion>> qmap = new HashMap<>();
+		for (KdQuestion q : qs) {
+			DetailDto tem = new DetailDto(q.getTypeId(), q.getDetailName());
+			List<KdQuestion> list = qmap.get(tem);
+			if (list == null) {
+				list = new ArrayList<>();
+				qmap.put(tem, list);
+			}
+			list.add(q);
+		}
+		for (DetailDto qt : qmap.keySet()) {
+			createPapers(qmap.get(qt), qt, dto);
+		}
+	}
+
+	private void createPapers(List<KdQuestion> qs, DetailDto qt, PaperExportDto dto) {
+		if (qs == null || qs.size() == 0) {
+			return;
+		}
+		if (qs.size() <= maxqc) {
+			createPaper(qs, qt, dto, 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, qt, dto, i + 1);
+			}
+		}
+
+	}
+
+	private void createPaper(List<KdQuestion> qs, DetailDto qt, PaperExportDto dto, int indx) {
+		if (qs.size() == 0) {
+			return;
+		}
+		double score = 0.0;
+		int unit = 0;
+		for (KdQuestion q : qs) {
+			if (CollectionUtils.isNotEmpty(q.getSubQuestions())) {
+				unit = unit + q.getSubQuestions().size();
+			} else {
+				unit++;
+			}
+			score = Calculator.add(score, q.getScore(), 1);
+		}
+		String detailName = qt.getTypeName();
+		KdPaper paper = new KdPaper();
+		paper.setDetailCount(1);
+		paper.setUnitCount(unit);
+		paper.setTotalScore(score);
+		paper.setName(dto.getPaperSuff() + detailName + "_" + indx);
+		paper.setCourseCode(dto.getCourseCode());
+		List<KdDetail> des = new ArrayList<>();
+		KdDetail d = new KdDetail();
+		d.setName(detailName);
+		d.setNumber(1);
+		d.setQuestionCount(unit);
+		d.setTotalScore(score);
+		d.setQuestions(qs);
+		des.add(d);
+		paper.setDetails(des);
+		paper.setDetailCount(1);
+		paper.setUnitCount(qs.size());
+		File paperdir = new File(paperDir + dto.getCourseCode() + "/" + qt.getTypeId() + "/");
+		paperdir.mkdirs();
+		try {
+			FileUtil.writeFile(paperdir.getAbsolutePath(), "/paper_" + indx + ".json", JSONObject.toJSONString(paper));
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private void fillAnswer(Connection connect, List<KdQuestion> qs) {
+		List<Answer> qps = new BatchGetDataUtil<Answer, KdQuestion>() {
+
+			@Override
+			protected List<Answer> getData(List<KdQuestion> paramList) {
+				return getAnswerBatch(connect, paramList);
+			}
+		}.getDataForBatch(qs, 200);
+		if (CollectionUtils.isEmpty(qps)) {
+			return;
+		}
+
+		Map<Integer, List<Answer>> map = new HashMap<>();
+		for (Answer qp : qps) {
+			List<Answer> list = map.get(qp.getQuestionId());
+			if (list == null) {
+				list = new ArrayList<>();
+				map.put(qp.getQuestionId(), list);
+			}
+			list.add(qp);
+		}
+
+		for (KdQuestion q : qs) {
+			if (QuesStructType.SINGLE_ANSWER_QUESTION.equals(q.getQuesStructType())) {
+				List<KdQuesOption> ops = new ArrayList<>();
+				q.setOptions(ops);
+				int num = 0;
+				int answerCount = 0;
+				for (Answer a : map.get(q.getId())) {
+					num++;
+					KdQuesOption op = new KdQuesOption();
+					ops.add(op);
+					op.setNumber(num);
+					op.setBody(a.getBody());
+					op.setSelect(a.getRight() == 1);
+					if (op.getSelect()) {
+						q.setAnswer(getOptionNum(num));
+						answerCount++;
+					}
+				}
+				if (answerCount == 0) {
+					throw new StatusException("没有答案:" + q.getId());
+				}
+				if (answerCount > 1) {
+					throw new StatusException("答案过多:" + q.getId());
+				}
+			} else if (QuesStructType.MULTIPLE_ANSWER_QUESTION.equals(q.getQuesStructType())) {
+				List<String> as = new ArrayList<>();
+				List<KdQuesOption> ops = new ArrayList<>();
+				q.setOptions(ops);
+				int num = 0;
+				int answerCount = 0;
+				for (Answer a : map.get(q.getId())) {
+					num++;
+					KdQuesOption op = new KdQuesOption();
+					ops.add(op);
+					op.setNumber(num);
+					op.setBody(a.getBody());
+					op.setSelect(a.getRight() == 1);
+					if (op.getSelect()) {
+						as.add(getOptionNum(num));
+						answerCount++;
+					}
+				}
+				if (answerCount == 0) {
+					throw new StatusException("没有答案" + q.getId());
+				}
+				if (answerCount == 1) {
+					throw new StatusException("答案只有一个:" + q.getId());
+				}
+				q.setAnswer(StringUtils.join(as, ","));
+			} else if (QuesStructType.BOOL_ANSWER_QUESTION.equals(q.getQuesStructType())) {
+				for (Answer a : map.get(q.getId())) {
+					if (a.getRight() == 1) {
+						q.setAnswer(getBool(a.getBody()));
+					}
+				}
+
+			} else {
+				StringBuilder sb = new StringBuilder();
+				if (CollectionUtils.isNotEmpty(map.get(q.getId()))) {
+					for (Answer a : map.get(q.getId())) {
+						sb.append(a.getBody());
+					}
+				}
+				q.setAnswer(sb.toString());
+			}
+		}
+	}
+
+	private String getBool(String val) {
+		for (String suc : sucStr) {
+			if (val.contains(suc)) {
+				return "正确";
+			}
+		}
+		for (String err : errStr) {
+			if (val.contains(err)) {
+				return "错误";
+			}
+		}
+		return "正确";
+	}
+
+	private List<Answer> getAnswerBatch(Connection connect, List<KdQuestion> qs) {
+		List<Answer> as = new ArrayList<>();
+		List<Integer> ids = qs.stream().map(e -> e.getId()).collect(Collectors.toList());
+		String idsstr = StringUtils.join(ids, ",");
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = " select t.QuestionID qid,t.AnswerDesc answer_text,t.IsCorrent from exam_r_answerinfo t "
+					+ " where t.QuestionID in (" + idsstr + ")  " + " order by t.QuestionID,t.OrderFlag ";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				Integer qid = resultSet.getInt("qid");
+				Answer a = new Answer();
+				a.setBody(disposeImg(resultSet.getString("answer_text"), qid));
+				a.setRight(resultSet.getInt("IsCorrent"));
+				a.setQuestionId(qid);
+				as.add(a);
+			}
+			return as;
+		} catch (Exception e1) {
+			throw new RuntimeException(e1);
+		} finally {
+			if (resultSet != null) {
+				try {
+					resultSet.close();
+				} catch (SQLException e1) {
+				}
+			}
+			if (preState != null) {
+				try {
+					preState.close();
+				} catch (SQLException e1) {
+				}
+			}
+		}
+	}
+
+	private String getOptionNum(int number) {
+		char optionNum = (char) (65 + number);
+		return String.valueOf(optionNum);
+	}
+}

+ 114 - 0
src/main/java/cn/com/qmth/export/MyConsumer2.java

@@ -0,0 +1,114 @@
+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;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+import com.alibaba.fastjson.JSONObject;
+
+public class MyConsumer2 extends Consumer<PaperExportDto> {
+	private static String paperDir = "d:/chuannong/prop/";
+
+
+	@Override
+	public void consume(PaperExportDto dto) {
+		Connection connect = null;
+		try {
+			Class.forName("com.mysql.cj.jdbc.Driver");
+
+			String url = "jdbc:mysql://localhost:3306/"+dto.getDbName()+"?serverTimezone=GMT%2B8";
+
+			String user = "root";
+
+			String password = "123456";
+			connect = DriverManager.getConnection(url, user, password);
+			exportPaper(connect, dto);
+			ChuanNongExportPaperByCourseCode.addDisposeCount();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (connect != null) {
+				try {
+					connect.close();
+				} catch (SQLException e) {
+				}
+			}
+		}
+	}
+
+
+
+
+	private void exportPaper(Connection connect, PaperExportDto dto) throws Exception {
+		List<PropertyDto> props=getProperty(connect, dto.getCourseCode());
+		if(CollectionUtils.isNotEmpty(props)) {
+			FileUtil.writeFile(paperDir, dto.getCourseCode()+".json", JSONObject.toJSONString(props));
+		}else {
+			System.out.println("无文件:"+dto.getCourseCode());
+		}
+	}
+	private static List<PropertyDto> getProperty(Connection connect, String subjectCode) throws SQLException, IOException {
+		List<PropertyDto> as = new ArrayList<>();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql =" select t.ChapterID id,t.ParentChapterlID parent_id,t.root_id,t.ChapterTitle type_name "
+					+" from exam_e_coursechapter t "
+					+" where t.root_id=(select DISTINCT t.root_id "
+					+" from exam_e_coursechapter t "
+					+" where t.OuterSystemID=3372 and t.CourseCode='"+subjectCode+"' "
+					+" and EXISTS(select 1 from exam_e_questioninfo q where t.ChapterID=q.ChapterID) "
+					+" ) "
+					+" order by t.Depth,t.OrderNumber ";
+			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==null) {
+						throw new RuntimeException("parent is null:"+a.getId());
+					}
+					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();
+			}
+		}
+	}
+
+	@Override
+	public void initResult() {
+
+	}
+}

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

@@ -0,0 +1,123 @@
+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.HashSet;
+import java.util.LinkedHashSet;
+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.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 dir = "d:/chuannong/";
+	private static String paperDir = "d:/chuannong/paper/";
+
+	@Override
+	protected void produce(Map<String, Object> param) throws Exception {
+		Connection connect = null;
+		try {
+			String dbName = (String) param.get("dbName");
+			Class.forName("com.mysql.cj.jdbc.Driver");
+
+			String url = "jdbc:mysql://localhost:3306/" + dbName + "?serverTimezone=GMT%2B8";
+
+			String user = "root";
+
+			String password = "123456";
+			connect = DriverManager.getConnection(url, user, password);
+			logger.info("***************************任务生产开始");
+			Set<Integer> nestedQids = getNestedQids(connect);
+			String paperSuff = (String) param.get("paperSuff");
+
+			File excelFolder = new File(paperDir);
+			if (excelFolder.exists()) {
+				FileUtil.clearDirectory(paperDir);
+			} else {
+				excelFolder.mkdirs();
+			}
+			LinkedHashSet<String> cs = readSubject();
+			if (CollectionUtils.isEmpty(cs)) {
+				logger.debug("无数据导出");
+				return;
+			} else {
+				logger.debug(cs.size() + "个课程");
+			}
+			for (String c : cs) {
+				offer(new PaperExportDto(nestedQids, c, paperSuff, dbName));
+			}
+			logger.info("***************************任务生产结束");
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (connect != null) {
+				try {
+					connect.close();
+				} catch (SQLException e) {
+				}
+			}
+		}
+
+	}
+
+	private Set<Integer> getNestedQids(Connection connect) throws SQLException, IOException {
+		Set<Integer> ret = new HashSet<>();
+		PreparedStatement preState = null;
+		ResultSet resultSet = null;
+		try {
+			String sql = " select DISTINCT  q2.ParentQuesID id from Exam_e_QuestionInfo q2 where q2.ParentQuesID!=0 ";
+
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				ret.add(resultSet.getInt("id"));
+			}
+			return ret;
+		} finally {
+			if (resultSet != null) {
+				resultSet.close();
+			}
+			if (preState != null) {
+				preState.close();
+			}
+		}
+	}
+
+	private static LinkedHashSet<String> readSubject() {
+		LinkedHashSet<String> list = new LinkedHashSet<>();
+		XSSFWorkbook wb = null;
+		try {
+			wb = new XSSFWorkbook(dir + "subject_info.xlsx");
+			XSSFSheet sheet = wb.getSheetAt(0);
+			int rows = sheet.getLastRowNum();
+			for (int i = 1; i <= rows; i++) {
+				XSSFRow row = sheet.getRow(i);
+				String yunkaiCode = row.getCell(1).getStringCellValue().trim();
+				list.add(yunkaiCode);
+			}
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (wb != null) {
+				try {
+					wb.close();
+				} catch (IOException e) {
+				}
+			}
+		}
+		return list;
+	}
+}

+ 69 - 0
src/main/java/cn/com/qmth/export/MyProducer2.java

@@ -0,0 +1,69 @@
+package cn.com.qmth.export;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedHashSet;
+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.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+
+public class MyProducer2 extends Producer {
+	private static Logger logger = LogManager.getLogger(MyProducer2.class);
+	private static String dir = "d:/chuannong/";
+	private static String paperDir = "d:/chuannong/prop/";
+
+	@Override
+	protected void produce(Map<String, Object> param) throws Exception {
+		logger.info("***************************任务生产开始");
+		String paperSuff = (String)param.get("paperSuff");
+		String dbName = (String)param.get("dbName");
+		File excelFolder = new File(paperDir);
+		if (excelFolder.exists()) {
+			FileUtil.clearDirectory(paperDir);
+		} else {
+			excelFolder.mkdirs();
+		}
+		Set<String> subjectCodes=readSubject();
+		if(CollectionUtils.isEmpty(subjectCodes)) {
+			return;
+		}
+		System.out.println("总科目数:"+subjectCodes.size());
+		for (String c : subjectCodes) {
+			offer(new PaperExportDto(null,c, paperSuff,dbName));
+		}
+		logger.info("***************************任务生产结束");
+	}
+	
+	
+	private static LinkedHashSet<String> readSubject() {
+		LinkedHashSet<String> list = new LinkedHashSet<>();
+		XSSFWorkbook wb = null;
+		try {
+			wb = new XSSFWorkbook(dir + "subject_info.xlsx");
+			XSSFSheet sheet = wb.getSheetAt(0);
+			int rows = sheet.getLastRowNum();
+			for (int i = 1; i <= rows; i++) {
+				XSSFRow row = sheet.getRow(i);
+				String yunkaiCode = row.getCell(1).getStringCellValue().trim();
+				list.add(yunkaiCode);
+			}
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (wb != null) {
+				try {
+					wb.close();
+				} catch (IOException e) {
+				}
+			}
+		}
+		return list;
+	}
+}

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

@@ -0,0 +1,46 @@
+package cn.com.qmth.export;
+
+import java.util.Set;
+
+public class PaperExportDto {
+	private Set<Integer> nestedQids;
+	private String courseCode;
+	private String paperSuff;
+	private String dbName;
+	
+	
+	
+	public PaperExportDto(Set<Integer> nestedQids, String courseCode, String paperSuff, String dbName) {
+		super();
+		this.nestedQids = nestedQids;
+		this.courseCode = courseCode;
+		this.paperSuff = paperSuff;
+		this.dbName = dbName;
+	}
+	public String getCourseCode() {
+		return courseCode;
+	}
+	public void setCourseCode(String courseCode) {
+		this.courseCode = courseCode;
+	}
+	public String getPaperSuff() {
+		return paperSuff;
+	}
+	public void setPaperSuff(String paperSuff) {
+		this.paperSuff = paperSuff;
+	}
+	
+	public String getDbName() {
+		return dbName;
+	}
+	public void setDbName(String dbName) {
+		this.dbName = dbName;
+	}
+	public Set<Integer> getNestedQids() {
+		return nestedQids;
+	}
+	public void setNestedQids(Set<Integer> nestedQids) {
+		this.nestedQids = nestedQids;
+	}
+	
+}

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

+ 77 - 0
src/main/java/cn/com/qmth/export/QuesStructType.java

@@ -0,0 +1,77 @@
+package cn.com.qmth.export;
+
+public enum QuesStructType {
+
+    /**
+     * 单选
+     */
+    SINGLE_ANSWER_QUESTION(1, "单选", true, false),
+    /**
+     * 多选
+     */
+    MULTIPLE_ANSWER_QUESTION(2, "多选", true, false),
+    /**
+     * 判断
+     */
+    BOOL_ANSWER_QUESTION(3, "判断", true, false),
+    /**
+     * 填空
+     */
+    FILL_BLANK_QUESTION(4, "填空", false, false),
+    /**
+     * 问答
+     */
+    TEXT_ANSWER_QUESTION(5, "问答", false, false),
+    /**
+     * 套题
+     */
+    NESTED_ANSWER_QUESTION(6, "套题", false, true);
+
+    private Integer id;
+    private String name;
+    private boolean objective;//是否是客观题
+    private boolean combline;//是否是组合题
+
+    QuesStructType(Integer id, String name, boolean objective, boolean combline) {
+        this.id = id;
+        this.name = name;
+        this.objective = objective;
+        this.combline = combline;
+    }
+
+    /**
+     * 通过ID获取试题类型
+     *
+     * @param id
+     * @return
+     */
+    public static QuesStructType getQuesStructTypeById(Integer id) {
+        for (QuesStructType type : QuesStructType.values()) {
+            if (id.equals(type.getId())) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isObjective() {
+        return objective;
+    }
+
+    public boolean isCombline() {
+        return combline;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+}

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

+ 23 - 0
src/main/java/cn/com/qmth/export/QuestionProp.java

@@ -0,0 +1,23 @@
+package cn.com.qmth.export;
+
+public class QuestionProp {
+	private Long questionId;
+	private Long knowledgeId;
+
+	public Long getQuestionId() {
+		return questionId;
+	}
+
+	public void setQuestionId(Long questionId) {
+		this.questionId = questionId;
+	}
+
+	public Long getKnowledgeId() {
+		return knowledgeId;
+	}
+
+	public void setKnowledgeId(Long knowledgeId) {
+		this.knowledgeId = knowledgeId;
+	}
+
+}

+ 86 - 0
src/main/java/cn/com/qmth/export/QuestionVo.java

@@ -0,0 +1,86 @@
+package cn.com.qmth.export;
+
+public class QuestionVo {
+	private Integer atype;
+	private String typeName;
+	private YunkaiDifficulty difficulty;
+	private QuesStructType qType;
+	private Integer qid;
+	private String body;
+	private Integer pid;
+	private AnswerVo answer;
+	private Integer seq;
+
+	public Integer getAtype() {
+		return atype;
+	}
+
+	public void setAtype(Integer atype) {
+		this.atype = atype;
+	}
+
+	public String getTypeName() {
+		return typeName;
+	}
+
+	public void setTypeName(String typeName) {
+		this.typeName = typeName;
+	}
+
+	public QuesStructType getqType() {
+		return qType;
+	}
+
+	public void setqType(QuesStructType qType) {
+		this.qType = qType;
+	}
+
+	public Integer getQid() {
+		return qid;
+	}
+
+	public void setQid(Integer qid) {
+		this.qid = qid;
+	}
+
+	public String getBody() {
+		return body;
+	}
+
+	public void setBody(String body) {
+		this.body = body;
+	}
+
+	public Integer getPid() {
+		return pid;
+	}
+
+	public void setPid(Integer pid) {
+		this.pid = pid;
+	}
+
+	public AnswerVo getAnswer() {
+		return answer;
+	}
+
+	public void setAnswer(AnswerVo answer) {
+		this.answer = answer;
+	}
+
+	public Integer getSeq() {
+		return seq;
+	}
+
+	public void setSeq(Integer seq) {
+		this.seq = seq;
+	}
+
+	public YunkaiDifficulty getDifficulty() {
+		return difficulty;
+	}
+
+	public void setDifficulty(YunkaiDifficulty difficulty) {
+		this.difficulty = difficulty;
+	}
+
+}

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

@@ -0,0 +1,124 @@
+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 {
+	private static String dbName="chuannong";
+	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/"+dbName+"?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+1)+"/"+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 exam_e_coursechapter set root_id=? where ChapterID=?";
+			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<Integer,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<Integer,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 ChapterID,ParentChapterlID,root_id from exam_e_coursechapter order by OrderNumber";
+			preState = connect.prepareStatement(sql);
+
+			resultSet = preState.executeQuery();
+
+			while (resultSet.next()) {
+				Knowledge a = new Knowledge();
+				a.setId(resultSet.getInt("ChapterID"));
+				a.setParentId(resultSet.getInt("ParentChapterlID"));
+				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;
+	}
+
+}

+ 89 - 0
src/main/java/cn/com/qmth/export/YunkaiDifficulty.java

@@ -0,0 +1,89 @@
+package cn.com.qmth.export;
+
+public enum YunkaiDifficulty {
+	
+	RONGYI(1,"容易",1.0,"易"),
+	JIAORONGYI(2,"较容易",0.7,"易"),
+	ZHONGDENG(3,"中等",0.5,"中"),
+	JIAONAN(4,"较难",0.3,"难"),
+	KUNNAN(5,"困难",0.1,"难"),
+	;
+	
+	private Integer yunKaiType;
+	private String yunKaiDesc;
+	private Double type;
+	private String desc;
+	
+	private YunkaiDifficulty(Integer yunKaiType, String yunKaiDesc, Double type, String desc) {
+		this.yunKaiType = yunKaiType;
+		this.yunKaiDesc = yunKaiDesc;
+		this.type = type;
+		this.desc = desc;
+	}
+	
+	
+		
+	public Integer getYunKaiType() {
+		return yunKaiType;
+	}
+
+
+
+	public void setYunKaiType(Integer yunKaiType) {
+		this.yunKaiType = yunKaiType;
+	}
+
+
+
+	public String getYunKaiDesc() {
+		return yunKaiDesc;
+	}
+
+
+
+	public void setYunKaiDesc(String yunKaiDesc) {
+		this.yunKaiDesc = yunKaiDesc;
+	}
+
+
+
+	public Double getType() {
+		return type;
+	}
+
+
+
+	public void setType(Double type) {
+		this.type = type;
+	}
+
+
+
+	public String getDesc() {
+		return desc;
+	}
+
+
+
+	public void setDesc(String desc) {
+		this.desc = desc;
+	}
+
+
+
+
+
+	public static YunkaiDifficulty getByYunKaiType(Integer yunKaiType) {
+		if(yunKaiType==null) {
+			return null;
+		}
+		for(YunkaiDifficulty t:YunkaiDifficulty.values()) {
+			if(t.getYunKaiType().equals(yunKaiType)) {
+				return t;
+			}
+		}
+		return null;
+	}
+	
+}
+

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