xiatian 2 yıl önce
ebeveyn
işleme
24ab84240b
26 değiştirilmiş dosya ile 1303 ekleme ve 100 silme
  1. 57 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/CopyDataController.java
  2. 54 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/AopTargetUtils.java
  3. 141 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/Basket.java
  4. 71 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/Consumer.java
  5. 10 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/EndObject.java
  6. 153 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/Producer.java
  7. 2 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/CoursePropertyRepo.java
  8. 2 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/PaperDetailRepo.java
  9. 2 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/PaperDetailUnitRepo.java
  10. 2 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/PaperRepo.java
  11. 4 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/PropertyRepo.java
  12. 2 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/QuesRepo.java
  13. 4 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/QuesTypeNameRepo.java
  14. 2 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/QuestionAudioRepo.java
  15. 11 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/PaperDetail.java
  16. 10 1
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/PaperDetailUnit.java
  17. 109 99
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/QuestionAudio.java
  18. 10 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/CopyDataService.java
  19. 2 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/PaperService.java
  20. 53 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/CopyDataDto.java
  21. 14 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/PaperId.java
  22. 397 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/consumer/CopyDataConsumer.java
  23. 141 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/CopyDataServiceImpl.java
  24. 13 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/PaperServiceImpl.java
  25. 35 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/producer/CopyDataProducer.java
  26. 2 0
      examcloud-core-questions-starter/src/main/java/cn/com/qmth/examcloud/core/questions/starter/CoreQuestionApp.java

+ 57 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/CopyDataController.java

@@ -0,0 +1,57 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.basic.api.UserCloudService;
+import cn.com.qmth.examcloud.core.basic.api.request.GetUserReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetUserResp;
+import cn.com.qmth.examcloud.core.questions.service.CopyDataService;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.ServletUtil;
+
+@RestController
+@RequestMapping("${api_cqb}/copy")
+public class CopyDataController extends ControllerSupport{
+	private static int cacheLockTimeout=60*10;
+	@Autowired
+	private CopyDataService copyDataService;
+	@Autowired
+	private UserCloudService userCloudService;
+	@Autowired
+	private RedisClient redisClient;
+	
+	@GetMapping("data")
+	public void copyData(HttpServletResponse response, @RequestParam Long fromRootOrgId,
+			@RequestParam Long toRootOrgId) {
+		if(!isSuperAdmin()) {
+			ServletUtil.returnJson("请求失败,没有权限", response);
+			return;
+		}
+		String cacheLock = "$_COPY_QUESTION_DATA_LOCK";
+        Boolean lock = redisClient.setIfAbsent(cacheLock, cacheLock, cacheLockTimeout);
+        if (!lock) {
+        	ServletUtil.returnJson("请求失败,正在处理数据", response);
+			return;
+        }
+        GetUserReq req=new GetUserReq();
+        req.setUserId(1L);
+        GetUserResp rs=userCloudService.getUser(req);
+        if(rs.getUserBean()==null) {
+        	ServletUtil.returnJson("请求失败,未找到用户信息", response);
+        }
+        User user=new User();
+        BeanUtils.copyProperties(rs.getUserBean(), user);
+		copyDataService.copyData(user,fromRootOrgId, toRootOrgId);
+		ServletUtil.returnJson("请求成功", response);
+	}
+
+}

+ 54 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/AopTargetUtils.java

@@ -0,0 +1,54 @@
+package cn.com.qmth.examcloud.core.questions.base.multithread;
+
+import org.springframework.aop.framework.AdvisedSupport;
+import org.springframework.aop.framework.AopProxy;
+import org.springframework.aop.support.AopUtils;
+
+import java.lang.reflect.Field;
+
+public class AopTargetUtils {
+
+	public static Object getTarget(Object obj) {
+		if (!AopUtils.isAopProxy(obj)) {
+			return obj;
+		}
+		try {
+
+			// 判断是jdk还是cglib代理
+			if (AopUtils.isJdkDynamicProxy(obj)) {
+				obj = getJdkDynamicProxyTargetObject(obj);
+			} else {
+				obj = getCglibDynamicProxyTargetObject(obj);
+			}
+
+		} catch (Exception e) {
+
+		}
+		return obj;
+
+	}
+
+	private static Object getCglibDynamicProxyTargetObject(Object obj) throws Exception {
+		Field h = obj.getClass().getDeclaredField("CGLIB$CALLBACK_0");
+		h.setAccessible(true);
+
+		Object dynamicAdvisedInterceptor = h.get(obj);
+		Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
+		advised.setAccessible(true);
+		Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
+		return target;
+	}
+
+	private static Object getJdkDynamicProxyTargetObject(Object obj) throws Exception {
+
+		Field h = obj.getClass().getSuperclass().getDeclaredField("h");
+		h.setAccessible(true);
+
+		AopProxy aopProxy = (AopProxy) h.get(obj);
+		Field advised = aopProxy.getClass().getDeclaredField("advised");
+		advised.setAccessible(true);
+		Object target = ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();
+		return target;
+
+	}
+}

+ 141 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/Basket.java

@@ -0,0 +1,141 @@
+package cn.com.qmth.examcloud.core.questions.base.multithread;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.Calculator;
+
+public class Basket {
+    
+    private String taskName;
+
+    private Integer total=0;
+
+    private AtomicInteger process=new AtomicInteger(0);
+    
+    /**
+     * 数据阻塞队列
+     */
+    private BlockingQueue<Object> queue;
+
+    /**
+     * 多线程计数器,子线程都结束后主线程才继续执行
+     */
+    private CountDownLatch endGate;
+
+    /**
+     * 消费者数量
+     */
+    private int consumerCount;
+
+    /**
+     * 判断线程执行是否有出错,生产者、消费者出错都需要修改此值为true
+     */
+    private boolean isExcuteError = false;
+
+    public Basket(int consumerCount,String taskName) {
+        this.consumerCount = consumerCount;
+        this.taskName=taskName;
+        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) {
+            throw new StatusException("线程异常");
+        } else {
+            boolean ret = queue.offer(value, 5, TimeUnit.SECONDS);
+            if (!ret) {
+                this.offer(value);
+            }
+        }
+    }
+
+    /**
+     * 消费数据,不采用take方法防止生产线程全部异常后消费线程阻塞
+     * 
+     * @return
+     * @throws InterruptedException
+     */
+    protected Object consume() throws InterruptedException {
+        if (isExcuteError) {
+            return new EndObject();
+        } else {
+            Object ob = queue.poll(5, TimeUnit.SECONDS);
+            if (ob == null) {
+                return this.consume();
+            } else {
+                return ob;
+            }
+        }
+    }
+
+    protected void endGateReset() {
+        endGate = new CountDownLatch(consumerCount);
+    }
+
+    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;
+    }
+
+    public Integer getTotal() {
+        return total;
+    }
+
+    protected void setTotal(Integer total) {
+        this.total = total;
+    }
+
+    public AtomicInteger getProcess() {
+        return process;
+    }
+
+    protected void updateProcess(int add) {
+        process.addAndGet(add);
+    }
+    
+    public String getProgress() {
+        if(total==0) {
+           return "0%"; 
+        }
+        Double d=Calculator.divide(process.doubleValue(), total.doubleValue(), 4);
+        Double f=Calculator.multiply(d,100);
+        return f+"%";
+    }
+    
+    
+    public String getTaskName() {
+        return taskName;
+    }
+
+}

+ 71 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/Consumer.java

@@ -0,0 +1,71 @@
+package cn.com.qmth.examcloud.core.questions.base.multithread;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class Consumer<T> extends Thread {
+
+    private static final Logger LOG = LoggerFactory.getLogger(Consumer.class);
+
+    private Basket basket;
+    
+    private Consumer<T> consumer;
+
+    public Consumer() {
+    }
+    
+    
+    public Consumer<T> getConsumer() {
+        return consumer;
+    }
+
+    
+    public void setConsumer(Consumer<T> consumer) {
+        this.consumer = consumer;
+    }
+
+
+
+    @Override
+    public void run() {
+        try {
+            while (true) {
+                // 先判断是否有异常结束
+                if (basket.isExcuteError()) {
+                    break;
+                }
+                // 取消费数据
+                Object o = basket.consume();
+                // 判断消费数据是否是结束
+                if (o instanceof EndObject) {
+                    break;
+                }
+                @SuppressWarnings("unchecked")
+                T t = (T) o;
+                // 消费数据实现
+                int disposeCount = consumer.consume(t);
+                if (basket.getTotal() > 0) {
+                    basket.updateProcess(disposeCount);
+                    processInfo();
+                }
+            }
+        } catch (Exception e) {
+            LOG.error(e.getMessage(), e);
+            basket.setExcuteError(true);
+        } finally {
+            basket.countDown();
+        }
+    }
+
+    protected abstract int consume(T t);
+
+    protected void setBasket(Basket basket) {
+        this.basket = basket;
+    }
+    protected Basket getBasket() {
+        return this.basket;
+    }
+    protected void processInfo() {
+        LOG.info(basket.getTaskName()+" 处理进度" + basket.getProgress());
+    }
+}

+ 10 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/EndObject.java

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

+ 153 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/multithread/Producer.java

@@ -0,0 +1,153 @@
+package cn.com.qmth.examcloud.core.questions.base.multithread;
+
+import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+public abstract class Producer<T, C extends Consumer<T>> {
+    private static final Logger LOG = LoggerFactory.getLogger(Producer.class);
+    private Basket basket;
+
+    /**
+     * 消费线程class
+     */
+    private List<Consumer<T>> consumers;
+
+    public void startDispose(int consumerCount){
+        startDispose(consumerCount, null,0);
+    }
+
+    /**
+     * 处理开始方法
+     */
+    public void startDispose(int consumerCount, Map<String, Object> param) {
+        startDispose(consumerCount, param,0);
+    }
+    public void startDispose(int consumerCount,int total) {
+        startDispose(consumerCount, null,total);
+    }
+    public void startDispose(int consumerCount, Map<String, Object> param,int total) {
+        // 启动消费者
+        startConsumer(consumerCount,total);
+        // 开始处理
+        dispose(param);
+    }
+    @SuppressWarnings("unchecked")
+    private void startConsumer(int consumerCount,int total) {
+        if (consumerCount <= 0) {
+            consumerCount = 1;
+        }
+        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
+        Class<C> clazz = (Class<C>) pt.getActualTypeArguments()[1];
+        consumers = new ArrayList<>();
+        this.basket = new Basket(consumerCount,getTaskName());
+        basket.setTotal(total);
+        // 启动消费者
+        int count = basket.getConsumerCount();
+        for (int i = 0; i < count; i++) {
+            Consumer<T> co = SpringContextHolder.getBean(clazz);
+            co.setBasket(basket);
+            co.setConsumer(co);
+            co.start();
+            consumers.add((Consumer<T>) AopTargetUtils.getTarget(co));
+        }
+    }
+
+    private void dispose(Map<String, Object> param) {
+        try {
+            // 生产数据
+            int index = 0;
+            for (;;) {
+                T dto = findData(param, index);
+                if (dto == null) {
+                    // 拿不到数据,结束消费
+                    break;
+                }
+                offer(dto);
+                index++;
+            }
+            // 发送生产结束信息
+            endConsumer();
+            // 等待子线程结束
+            await();
+            // 判断子线程是否正常结束
+            if (basket.isExcuteError()) {
+                throw new StatusException("处理失败,线程异常");
+            }
+        } catch (StatusException e) {
+            LOG.error(e.getMessage(),e);
+            // 获取异常时发送异常结束信息
+            endConsumerAsError();
+            throw e;
+        } catch (Exception e) {
+            LOG.error(e.getMessage(),e);
+            // 获取异常时发送异常结束信息
+            endConsumerAsError();
+            throw new RuntimeException("处理失败", e);
+        }
+    }
+
+    /**
+     * 出异常后修改标识
+     * 
+     */
+    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
+     */
+    private void offer(Object ob) throws InterruptedException {
+        synchronized (basket) {
+            basket.offer(ob);
+        }
+    }
+
+    /**
+     * 等待所有消费者结束
+     * 
+     * @throws InterruptedException
+     */
+    private void await() throws InterruptedException {
+        basket.await();
+    }
+
+    protected abstract T findData(Map<String, Object> param, int index);
+    
+    protected abstract String getTaskName();
+    
+    public Integer getTotal() {
+        return this.basket.getTotal();
+    }
+
+
+    public AtomicInteger getProcess() {
+        return this.basket.getProcess();
+    }
+}

+ 2 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/CoursePropertyRepo.java

@@ -37,4 +37,6 @@ public interface CoursePropertyRepo extends MongoRepository<CourseProperty, Stri
 
 	List<CourseProperty> findByOrgId(Long orgId);
 
+	void deleteByOrgId(Long toRootOrgId);
+
 }

+ 2 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/PaperDetailRepo.java

@@ -11,4 +11,6 @@ public interface PaperDetailRepo extends MongoRepository<PaperDetail, String>, Q
     List<PaperDetail> findByPaperOrderByNumber(Paper paper);
 
     List<PaperDetail> findByPaper(Paper paper);
+
+	void deleteByOrgId(Long toRootOrgId);
 }

+ 2 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/PaperDetailUnitRepo.java

@@ -59,4 +59,6 @@ public interface PaperDetailUnitRepo extends MongoRepository<PaperDetailUnit, St
      * @return
      */
     PaperDetailUnit findByPaperAndNumber(Paper paper, Integer number);
+
+	void deleteByOrgId(Long toRootOrgId);
 }

+ 2 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/PaperRepo.java

@@ -35,4 +35,6 @@ public interface PaperRepo extends MongoRepository<Paper, String>, QueryByExampl
 
     List<Paper> findByPaperType(PaperType paperType);
 
+	void deleteByOrgId(String orgId);
+
 }

+ 4 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/PropertyRepo.java

@@ -24,4 +24,8 @@ public interface PropertyRepo extends MongoRepository<Property, String>, QueryBy
 
 	List<Property> findByCoursePropertyIdOrderByNumber(String id);
 
+	void deleteByOrgId(Long toRootOrgId);
+
+	List<Property> findByOrgId(Long fromRootOrgId);
+
 }

+ 2 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/QuesRepo.java

@@ -28,4 +28,6 @@ public interface QuesRepo extends MongoRepository<Question, String>, QueryByExam
     List<Question> findByCourseAndOrgId(Course course, String orgId);
 
     List<Question> findByIdIn(Set<String> ids);
+
+	void deleteByOrgId(String orgId);
 }

+ 4 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/QuesTypeNameRepo.java

@@ -17,4 +17,8 @@ public interface QuesTypeNameRepo extends MongoRepository<QuesTypeName, String>
 
     @Query(value = "{\"orgId\":?0,\"questionType\":?1}")
     List<QuesTypeName> findQuesName(String orgId, QuesStructType quesType);
+
+	void deleteByOrgId(String toRootOrgId);
+
+	List<QuesTypeName> findByOrgId(Long fromRootOrgId);
 }

+ 2 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/QuestionAudioRepo.java

@@ -19,4 +19,6 @@ public interface QuestionAudioRepo extends MongoRepository<QuestionAudio, String
 
     public List<QuestionAudio> findByQuestionIdIn(List<String> ids);
 
+	public void deleteByOrgId(Long toRootOrgId);
+
 }

+ 11 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/PaperDetail.java

@@ -30,6 +30,9 @@ public class PaperDetail extends MongoBaseEntity implements Comparable<PaperDeta
 
     private String createTime;// 创建时间
     
+    //复制数据用
+    private Long orgId;
+    
     /**
      * 大题排序
      */
@@ -157,6 +160,14 @@ public class PaperDetail extends MongoBaseEntity implements Comparable<PaperDeta
 		this.sortNumberSub = sortNumberSub;
 	}
 
+	public Long getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(Long orgId) {
+		this.orgId = orgId;
+	}
+
 	
     
 }

+ 10 - 1
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/PaperDetailUnit.java

@@ -45,7 +45,8 @@ public class PaperDetailUnit extends MongoBaseEntity implements Comparable<Paper
     private String createTime;// 创建时间
 
     private PaperType paperType;
-
+    //复制数据用
+    private Long orgId;
     /**
      * 作答限时 K12
      */
@@ -310,4 +311,12 @@ public class PaperDetailUnit extends MongoBaseEntity implements Comparable<Paper
         return true;
     }
 
+	public Long getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(Long orgId) {
+		this.orgId = orgId;
+	}
+
 }

+ 109 - 99
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/QuestionAudio.java

@@ -13,109 +13,119 @@ import cn.com.qmth.examcloud.core.questions.dao.entity.base.MongoBaseEntity;
  * @description QuestionAudio.java
  */
 public class QuestionAudio extends MongoBaseEntity {
-    /**
+	/**
 	 * 
 	 */
 	private static final long serialVersionUID = 6995367410867753147L;
 
 	/**
-     * 试题ID
-     */
-    private String questionId;
-
-    /**
-     * 题干OR选项
-     private AudioPositionType audioPositionType;
-
-     */
-    /**
-     * 文件名称
-     */
-    private String fileName;
-    /**
-     * 文件后缀
-     */
-    private String fileSuffixes;
-    /**
-     * 存放路径
-     */
-    private String fileUrl;
-    /**
-     * 创建时间
-     */
-    private Date createTime;
-    /**
-     * 创建人
-     */
-    private String createUser;
-
-    public QuestionAudio() {
-
-    }
-
-    public QuestionAudio(String questionId, String fileName, String fileUrl) {
-        this.questionId = questionId;
-        this.fileName = fileName;
-        if (StringUtils.isNotBlank(fileName)) {
-            this.fileSuffixes = fileName.substring(fileName.indexOf(".") + 1, fileName.length());
-        }
-        this.fileUrl = fileUrl;
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-
-    public String getQuestionId() {
-        return questionId;
-    }
-
-    public void setQuestionId(String questionId) {
-        this.questionId = questionId;
-    }
-
-    public String getFileName() {
-        return fileName;
-    }
-
-    public void setFileName(String fileName) {
-        this.fileName = fileName;
-    }
-
-    public String getFileUrl() {
-        return fileUrl;
-    }
-
-    public void setFileUrl(String fileUrl) {
-        this.fileUrl = fileUrl;
-    }
-
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    public String getCreateUser() {
-        return createUser;
-    }
-
-    public void setCreateUser(String createUser) {
-        this.createUser = createUser;
-    }
-
-    public String getFileSuffixes() {
-        return fileSuffixes;
-    }
-
-    public void setFileSuffixes(String fileSuffixes) {
-        this.fileSuffixes = fileSuffixes;
-    }
+	 * 试题ID
+	 */
+	private String questionId;
+
+	/**
+	 * 题干OR选项 private AudioPositionType audioPositionType;
+	 * 
+	 */
+	/**
+	 * 文件名称
+	 */
+	private String fileName;
+	/**
+	 * 文件后缀
+	 */
+	private String fileSuffixes;
+	/**
+	 * 存放路径
+	 */
+	private String fileUrl;
+	/**
+	 * 创建时间
+	 */
+	private Date createTime;
+	/**
+	 * 创建人
+	 */
+	private String createUser;
+
+	// 复制数据用
+	private Long orgId;
+
+	public QuestionAudio() {
+
+	}
+
+	public QuestionAudio(String questionId, String fileName, String fileUrl) {
+		this.questionId = questionId;
+		this.fileName = fileName;
+		if (StringUtils.isNotBlank(fileName)) {
+			this.fileSuffixes = fileName.substring(fileName.indexOf(".") + 1, fileName.length());
+		}
+		this.fileUrl = fileUrl;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getQuestionId() {
+		return questionId;
+	}
+
+	public void setQuestionId(String questionId) {
+		this.questionId = questionId;
+	}
+
+	public String getFileName() {
+		return fileName;
+	}
+
+	public void setFileName(String fileName) {
+		this.fileName = fileName;
+	}
+
+	public String getFileUrl() {
+		return fileUrl;
+	}
+
+	public void setFileUrl(String fileUrl) {
+		this.fileUrl = fileUrl;
+	}
+
+	public Date getCreateTime() {
+		return createTime;
+	}
+
+	public void setCreateTime(Date createTime) {
+		this.createTime = createTime;
+	}
+
+	public String getCreateUser() {
+		return createUser;
+	}
+
+	public void setCreateUser(String createUser) {
+		this.createUser = createUser;
+	}
+
+	public String getFileSuffixes() {
+		return fileSuffixes;
+	}
+
+	public void setFileSuffixes(String fileSuffixes) {
+		this.fileSuffixes = fileSuffixes;
+	}
+
+	public Long getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(Long orgId) {
+		this.orgId = orgId;
+	}
 
 }

+ 10 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/CopyDataService.java

@@ -0,0 +1,10 @@
+package cn.com.qmth.examcloud.core.questions.service;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+
+public interface CopyDataService {
+
+	void copyData(User user, Long fromRootOrgId, Long toRootOrgId);
+
+}
+

+ 2 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/PaperService.java

@@ -316,4 +316,6 @@ public interface PaperService {
 
     void updatePapersStorage(List<String> paperIds, int storage, String orgId);
 
+	public List<String> findPaperId(Long fromRootOrgId);
+
 }

+ 53 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/CopyDataDto.java

@@ -0,0 +1,53 @@
+package cn.com.qmth.examcloud.core.questions.service.bean;
+
+import java.util.Map;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+
+public class CopyDataDto {
+	private Map<String, Long> courses;
+	private String paperId;
+	private Long toRootOrgId;
+	private User user;
+
+	public Map<String, Long> getCourses() {
+		return courses;
+	}
+
+	public void setCourses(Map<String, Long> courses) {
+		this.courses = courses;
+	}
+
+	public String getPaperId() {
+		return paperId;
+	}
+
+	public void setPaperId(String paperId) {
+		this.paperId = paperId;
+	}
+
+	public Long getToRootOrgId() {
+		return toRootOrgId;
+	}
+
+	public void setToRootOrgId(Long toRootOrgId) {
+		this.toRootOrgId = toRootOrgId;
+	}
+
+	public User getUser() {
+		return user;
+	}
+
+	public void setUser(User user) {
+		this.user = user;
+	}
+
+	public CopyDataDto(Map<String, Long> courses, String paperId, Long toRootOrgId, User user) {
+		super();
+		this.courses = courses;
+		this.paperId = paperId;
+		this.toRootOrgId = toRootOrgId;
+		this.user = user;
+	}
+
+}

+ 14 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/PaperId.java

@@ -0,0 +1,14 @@
+package cn.com.qmth.examcloud.core.questions.service.bean;
+
+public class PaperId {
+	private String id;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+}

+ 397 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/consumer/CopyDataConsumer.java

@@ -0,0 +1,397 @@
+package cn.com.qmth.examcloud.core.questions.service.consumer;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.base.BeanCopierUtil;
+import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
+import cn.com.qmth.examcloud.core.questions.base.IoUtils;
+import cn.com.qmth.examcloud.core.questions.base.Model;
+import cn.com.qmth.examcloud.core.questions.base.multithread.Consumer;
+import cn.com.qmth.examcloud.core.questions.dao.PaperDetailRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PaperDetailUnitRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PaperRepo;
+import cn.com.qmth.examcloud.core.questions.dao.QuesRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetail;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
+import cn.com.qmth.examcloud.core.questions.dao.entity.QuesOption;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
+import cn.com.qmth.examcloud.core.questions.dao.entity.QuestionAudio;
+import cn.com.qmth.examcloud.core.questions.service.QuestionAudioService;
+import cn.com.qmth.examcloud.core.questions.service.UpYunService;
+import cn.com.qmth.examcloud.core.questions.service.bean.CopyDataDto;
+import cn.com.qmth.examcloud.core.questions.service.config.SysProperty;
+import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.web.filestorage.FileStoragePathEnvInfo;
+import cn.com.qmth.examcloud.web.filestorage.YunPathInfo;
+
+@Scope("prototype")
+@Service
+public class CopyDataConsumer extends Consumer<CopyDataDto> {
+	
+	@Autowired
+	private PaperRepo paperRepo;
+	
+	@Autowired
+	private SysProperty sysProperty;
+	
+	@Autowired
+	private PaperDetailRepo paperDetailRepo;
+	
+	@Autowired
+	private PaperDetailUnitRepo paperDetailUnitRepo;
+	
+	@Autowired
+	private QuestionAudioService questionAudioService;
+	
+	@Autowired
+	private QuesRepo quesRepo;
+
+    @Transactional
+    @Override
+    public int consume(CopyDataDto dto) {
+    	clonePaper(dto);
+        return 1;
+    }
+
+    private void clonePaper(CopyDataDto dto){
+    	String paperId=dto.getPaperId();
+    	User user=dto.getUser();
+        if (StringUtils.isBlank(paperId)) {
+            throw new StatusException("待复制的试卷ID不能为空!");
+        }
+
+        if (user == null) {
+            throw new StatusException("当前用户信息不能为空!");
+        }
+
+        if (user.getRootOrgId() == null) {
+            throw new StatusException("当前用户的顶级机构ID不能为空!");
+        }
+
+        Paper oldPaper = Model.of(paperRepo.findById(paperId));
+        if (oldPaper == null) {
+            throw new StatusException("待复制的试卷不存在!");
+        }
+
+        //复制原试卷的所有试题信息
+        Map<PaperDetail, List<PaperDetailUnit>> detailMaps = this.copyPaperDetails(oldPaper, dto);
+
+        //处理试题的音频(下载上传到云存储)
+        try {
+            this.dealQuestionAudios(detailMaps, user.getDisplayName());
+        } catch (Exception e) {
+            throw new StatusException("复制音频失败!");
+        }
+
+        //复制原试卷的信息
+        Paper newPaper = this.copyPaper(oldPaper, dto);
+        //保存新试卷
+        paperRepo.save(newPaper);
+        try {
+            //保存新试卷的所有试题信息
+            this.savePaperDetails(detailMaps, newPaper, user);
+        } catch (Exception e) {
+            throw new StatusException("克隆试卷失败!");
+        }
+    }
+
+	
+	private void savePaperDetails(Map<PaperDetail, List<PaperDetailUnit>> detailMaps, Paper newPaper, User user){
+        if (StringUtils.isEmpty(newPaper.getId())) {
+            throw new StatusException("试卷保存失败!");
+        }
+        for (Map.Entry<PaperDetail, List<PaperDetailUnit>> entry : detailMaps.entrySet()) {
+            PaperDetail detail = entry.getKey();
+            detail.setPaper(newPaper);
+        }
+        //批量保存大题
+        paperDetailRepo.saveAll(detailMaps.keySet());
+        List<PaperDetailUnit> newDetailUnits = new ArrayList<>();
+        List<Question> newQuestions = new ArrayList<>();
+        for (Map.Entry<PaperDetail, List<PaperDetailUnit>> entry : detailMaps.entrySet()) {
+            PaperDetail detail = entry.getKey();
+            List<PaperDetailUnit> detailUnits = entry.getValue();
+            for (PaperDetailUnit detailUnit : detailUnits) {
+                detailUnit.setPaper(newPaper);
+                detailUnit.setPaperDetail(detail);
+                newDetailUnits.add(detailUnit);
+                newQuestions.add(detailUnit.getQuestion());
+            }
+        }
+        //批量新增试题
+        quesRepo.saveAll(newQuestions);
+
+        for (Question question : newQuestions) {
+            List<QuestionAudio> audioList = question.getAudioList();
+            if (audioList != null && audioList.size() > 0) {
+                for (QuestionAudio audio : audioList) {
+                    audio.setQuestionId(question.getId());
+                    questionAudioService.saveQuestionAudio(audio, user);
+                }
+                this.updateQuestionBody(question, audioList);
+                //如果试题存在音频,则更新试题的音频信息
+                quesRepo.save(question);
+            }
+        }
+
+        //批量保存试题单元
+        paperDetailUnitRepo.saveAll(newDetailUnits);
+    }
+
+    /**
+     * 更新试题题干或选项中的音频存储ID
+     */
+    private void updateQuestionBody(Question question, List<QuestionAudio> audioList) {
+        Map<String, String> audioMaps = new HashMap<>();
+        for (QuestionAudio audio : audioList) {
+            audioMaps.put(audio.getFileName(), audio.getId());
+        }
+
+        //在题干html片段中替换音频存储ID
+        String body = question.getQuesBody();
+        List<String> ids = CommonUtils.getTagANames(body);
+        List<String> names = CommonUtils.getTagANames2(body);
+        Map<String, String> oldBodyMap = new HashMap<>();
+        for (int i = 0; i < ids.size(); i++) {
+            oldBodyMap.put(names.get(i), ids.get(i));
+        }
+        for (String key : oldBodyMap.keySet()) {
+            body = body.replace(oldBodyMap.get(key), audioMaps.get(key));
+        }
+        question.setQuesBody(body);
+
+        if (question.getQuesOptions() == null || question.getQuesOptions().size() == 0) {
+            return;
+        }
+
+        //替换选项中的音频存储ID
+        for (QuesOption option : question.getQuesOptions()) {
+            String newOptionBody = option.getOptionBody();
+            List<String> optionIds = CommonUtils.getTagANames(newOptionBody);
+            List<String> optionNames = CommonUtils.getTagANames2(newOptionBody);
+
+            Map<String, String> oldOptionMap = new HashMap<>();
+            for (int i = 0; i < optionIds.size(); i++) {
+                oldOptionMap.put(optionNames.get(i), optionIds.get(i));
+            }
+            for (String key : oldOptionMap.keySet()) {
+                newOptionBody = newOptionBody.replace(oldOptionMap.get(key), audioMaps.get(key));
+            }
+            option.setOptionBody(newOptionBody);
+        }
+    }
+
+
+    /**
+     * 处理试题的音频(下载上传到云存储)
+     */
+    private void dealQuestionAudios(Map<PaperDetail, List<PaperDetailUnit>> detailMaps, String userName){
+//        UpYun upYun = upYunService.getInstance();
+        String copyAudioPath = UpYunService.TEMP_FILE_EXP + File.separator + userName + "_copyAudioPath";
+        File copyAudioDir = new File(copyAudioPath);
+        if (!copyAudioDir.exists()) {
+            copyAudioDir.mkdirs();
+        }
+        for (Map.Entry<PaperDetail, List<PaperDetailUnit>> entry : detailMaps.entrySet()) {
+            List<PaperDetailUnit> detailUnits = entry.getValue();
+            for (PaperDetailUnit detailUnit : detailUnits) {
+                Question question = detailUnit.getQuestion();
+                List<QuestionAudio> audioList = question.getAudioList();
+                if (audioList == null || audioList.size() == 0) {
+                    continue;
+                }
+                for (QuestionAudio audio : audioList) {
+                    //定义文件下载名称,并下载音频文件
+                    String newAudioFileName = randomUUID() + "_" + audio.getFileName();
+                    File audioFile = new File(copyAudioPath + File.separator + newAudioFileName);
+
+//                    upYun.readFile(audio.getFileUrl(), audioFile);
+                  //通用存储
+                    FileStorageUtil.saveUrlAs(FileStorageUtil.realPath(audio.getFileUrl()), audioFile);
+
+                    //重新上传新的音频文件
+                    String newPath = sysProperty.getRadioUploadPath() + newAudioFileName;
+                    try {
+//                        upYun.writeFile(newPath, audioFile, true);
+                        //通用存储
+                		FileStoragePathEnvInfo env=new FileStoragePathEnvInfo();
+                		env.setRelativePath(newPath);
+                		YunPathInfo pi=FileStorageUtil.saveFile("audioFile", env, audioFile,null);
+                        audio.setFileUrl(pi.getRelativePath());//设置新路径
+                    } catch (Exception e) {
+                        throw new StatusException("上传音频文件失败!");
+                    } finally {
+                        IoUtils.removeFile(audioFile);
+                    }
+                }
+            }
+        }
+
+        IoUtils.removeFile(copyAudioDir);
+    }
+
+    /**
+     * 复制试卷的所有试题信息
+     */
+    private Map<PaperDetail, List<PaperDetailUnit>> copyPaperDetails(Paper oldPaper, CopyDataDto dto){
+        //批量查询原试卷的所有试题
+        List<PaperDetailUnit> allDetailUnits = paperDetailUnitRepo.findByPaperOrderByNumber(oldPaper);
+        if (allDetailUnits == null || allDetailUnits.size() == 0) {
+            throw new StatusException("原试卷的试题不存在!");
+        }
+        //按大题有序封装对应的试题列表
+        Map<PaperDetail, List<PaperDetailUnit>> oldDetailMaps = new TreeMap<>();
+        for (PaperDetailUnit detailUnit : allDetailUnits) {
+            PaperDetail detail = detailUnit.getPaperDetail();
+            if (!oldDetailMaps.containsKey(detail)) {
+                oldDetailMaps.put(detail, new ArrayList<>());
+            }
+            List<PaperDetailUnit> detailUnits = oldDetailMaps.get(detail);
+            detailUnits.add(detailUnit);
+        }
+        //开始复制
+        Map<PaperDetail, List<PaperDetailUnit>> detailMaps = new TreeMap<>();
+        for (Map.Entry<PaperDetail, List<PaperDetailUnit>> entry : oldDetailMaps.entrySet()) {
+            //复制大题信息
+            PaperDetail oldPaperDetail = entry.getKey();
+            PaperDetail paperDetail = this.copyPaperDetail(oldPaperDetail, dto);
+            //复制大题下的所有试题单元
+            List<PaperDetailUnit> oldDetailUnits = entry.getValue();
+            List<PaperDetailUnit> detailUnits = new ArrayList<>();
+            for (PaperDetailUnit oldDetailUnit : oldDetailUnits) {
+                PaperDetailUnit detailUnit = this.copyPaperDetailUnit(oldDetailUnit, dto);
+                detailUnits.add(detailUnit);
+            }
+            detailMaps.put(paperDetail, detailUnits);
+        }
+        return detailMaps;
+    }
+
+    /**
+     * 复制试题的音频
+     */
+    private QuestionAudio copyQuestionAudio(QuestionAudio oldAudio, CopyDataDto dto) {
+        QuestionAudio audio = new QuestionAudio();
+        audio.setId(null);
+        audio.setQuestionId(null);
+        audio.setFileUrl(oldAudio.getFileUrl());
+        audio.setFileName(oldAudio.getFileName());
+        audio.setFileSuffixes(oldAudio.getFileSuffixes());
+        audio.setCreateTime(new Date());
+        audio.setCreateUser(dto.getUser().getDisplayName());
+        audio.setOrgId(dto.getToRootOrgId());
+        return audio;
+    }
+
+    /**
+     * 复制试题信息
+     */
+    private Question copyQuestion(Question oldQuestion,CopyDataDto dto) {
+        Question question = BeanCopierUtil.copyProperties(oldQuestion, Question.class);
+        question.setId(null);
+        question.getCourse().setId(getCourseId(dto.getCourses(), question.getCourse().getCode()).toString());
+        question.getCourse().setOrgId(dto.getToRootOrgId().toString());
+        question.setOrgId(dto.getToRootOrgId().toString());
+        question.setQuesProperties(null);
+        question.setCreateTime(CommonUtils.getCurDateTime());
+        question.setUpdateTime(CommonUtils.getCurDateTime());
+        question.setPropertyGroup(null);
+        if (oldQuestion.getHasAudio() == null || !oldQuestion.getHasAudio()) {
+            question.setHasAudio(false);
+            return question;
+        }
+        //复制试题的音频
+        List<QuestionAudio> audioList = questionAudioService.findQuestionAudiosByQuestionId(oldQuestion.getId());
+        if (audioList != null && audioList.size() > 0) {
+            for (QuestionAudio oldAudio : audioList) {
+                QuestionAudio audio = this.copyQuestionAudio(oldAudio, dto);
+                question.addAudio(audio);
+            }
+        }
+        return question;
+    }
+
+    /**
+     * 复制试题单元信息
+     */
+    private PaperDetailUnit copyPaperDetailUnit(PaperDetailUnit oldDetailUnit,CopyDataDto dto){
+        Question oldQuestion = oldDetailUnit.getQuestion();
+        if (oldQuestion == null) {
+            throw new StatusException("原试题详细信息不存在!");
+        }
+        PaperDetailUnit detailUnit = new PaperDetailUnit();
+        detailUnit.setNumber(oldDetailUnit.getNumber());
+        detailUnit.setScore(oldDetailUnit.getScore());
+        detailUnit.setSubScoreList(oldDetailUnit.getSubScoreList());
+        detailUnit.setQuestionType(oldDetailUnit.getQuestionType());
+        detailUnit.setOptionOrder(oldDetailUnit.getOptionOrder());
+        detailUnit.setPaperType(oldDetailUnit.getPaperType());
+        detailUnit.setCreator(dto.getUser().getDisplayName());
+        detailUnit.setCreateTime(CommonUtils.getCurDateTime());
+        detailUnit.setOrgId(dto.getToRootOrgId());
+        detailUnit.setPaper(null);
+        detailUnit.setPaperDetail(null);
+        //复制试题信息
+        Question question = this.copyQuestion(oldQuestion, dto);
+        detailUnit.setQuestion(question);
+        return detailUnit;
+    }
+
+    /**
+     * 复制大题信息
+     */
+    private PaperDetail copyPaperDetail(PaperDetail oldPaperDetail, CopyDataDto dto) {
+        PaperDetail paperDetail = new PaperDetail();
+        paperDetail.setNumber(oldPaperDetail.getNumber());
+        paperDetail.setName(oldPaperDetail.getName());
+        paperDetail.setTitle(oldPaperDetail.getTitle());
+        paperDetail.setScore(oldPaperDetail.getScore());
+        paperDetail.setUnitCount(oldPaperDetail.getUnitCount());
+        paperDetail.setCreator(dto.getUser().getDisplayName());
+        paperDetail.setCreateTime(CommonUtils.getCurDateTime());
+        paperDetail.setPaper(null);
+        paperDetail.setOrgId(dto.getToRootOrgId());
+        return paperDetail;
+    }
+
+    /**
+     * 复制试卷信息
+     */
+    private Paper copyPaper(Paper oldPaper, CopyDataDto dto) {
+        Paper newPaper = BeanCopierUtil.copyProperties(oldPaper, Paper.class);
+        newPaper.getCourse().setId(getCourseId(dto.getCourses(), newPaper.getCourse().getCode()).toString());
+        newPaper.getCourse().setOrgId(dto.getToRootOrgId().toString());
+        newPaper.setOrgId(String.valueOf(dto.getToRootOrgId()));
+        newPaper.setCreateTime(CommonUtils.getCurDateTime());
+        newPaper.setLastModifyName(dto.getUser().getDisplayName());
+        newPaper.setCreator(dto.getUser().getDisplayName());
+        newPaper.setId(null);
+        return newPaper;
+    }
+    private String randomUUID() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+	private Long getCourseId(Map<String,Long> courses,String code) {
+		Long id=courses.get(code);
+		if(id==null) {
+			throw new StatusException("没有课程信息code="+code);
+		}
+		return id;
+	}
+}

+ 141 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/CopyDataServiceImpl.java

@@ -0,0 +1,141 @@
+package cn.com.qmth.examcloud.core.questions.service.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.basic.api.CourseCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetCourseByOrgReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetCourseByOrgResp;
+import cn.com.qmth.examcloud.core.questions.dao.PaperDetailRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PaperDetailUnitRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PaperRepo;
+import cn.com.qmth.examcloud.core.questions.dao.QuesRepo;
+import cn.com.qmth.examcloud.core.questions.dao.QuesTypeNameRepo;
+import cn.com.qmth.examcloud.core.questions.dao.QuestionAudioRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.QuesTypeName;
+import cn.com.qmth.examcloud.core.questions.service.CopyDataService;
+import cn.com.qmth.examcloud.core.questions.service.PaperService;
+import cn.com.qmth.examcloud.core.questions.service.bean.CopyDataDto;
+import cn.com.qmth.examcloud.core.questions.service.producer.CopyDataProducer;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+
+@Service("copyDataService")
+public class CopyDataServiceImpl implements CopyDataService {
+	private static final Logger log = LoggerFactory.getLogger(CopyDataService.class);
+	private static int cacheLockTimeout=60*10;
+	@Autowired
+	private CourseCloudService courseCloudService;
+
+	@Autowired
+	private PaperRepo paperRepo;
+
+	@Autowired
+	private CopyDataProducer copyDataProducer;
+
+	@Autowired
+	private PaperDetailRepo paperDetailRepo;
+
+	@Autowired
+	private PaperDetailUnitRepo paperDetailUnitRepo;
+
+	@Autowired
+	private PaperService paperService;
+
+	@Autowired
+	private QuesRepo quesRepo;
+	@Autowired
+	private QuesTypeNameRepo quesTypeNameRepo;
+	@Autowired
+	private QuestionAudioRepo questionAudioRepo;
+
+	@Autowired
+	private RedisClient redisClient;
+
+	@Async
+	@Override
+	@Transactional
+	public void copyData(User user, Long fromRootOrgId, Long toRootOrgId) {
+		boolean sucss = true;
+		long s1 = System.currentTimeMillis();
+		try {
+			log.info("数据复制开始 | fromRootOrgId=" + fromRootOrgId + " | toRootOrgId=" + toRootOrgId);
+			dispose(user, fromRootOrgId, toRootOrgId);
+		} catch (Exception e) {
+			sucss = false;
+			throw e;
+		} finally {
+			long s2 = System.currentTimeMillis();
+			if (sucss) {
+				log.info("数据复制结束 | 成功 |  " + ((s2 - s1) / 1000) + "秒 |  fromRootOrgId=" + fromRootOrgId
+						+ " | toRootOrgId=" + toRootOrgId);
+			} else {
+				log.info("数据复制结束 | 失败 |  " + ((s2 - s1) / 1000) + "秒 |  fromRootOrgId=" + fromRootOrgId
+						+ " | toRootOrgId=" + toRootOrgId);
+			}
+			String cacheLock = "$_COPY_QUESTION_DATA_LOCK";
+			redisClient.delete(cacheLock);
+		}
+	}
+
+	private void dispose(User user, Long fromRootOrgId, Long toRootOrgId) {
+		clearData(toRootOrgId);
+		redisClient.expire("$_COPY_QUESTION_DATA_LOCK", cacheLockTimeout);
+		Map<String, Long> courses = new HashMap<>();
+		GetCourseByOrgReq courseReq = new GetCourseByOrgReq();
+		courseReq.setRootOrgId(toRootOrgId);
+		GetCourseByOrgResp res = courseCloudService.getCourseByOrg(courseReq);
+		if (CollectionUtils.isEmpty(res.getCourseList())) {
+			throw new StatusException("请先导入课程");
+		}
+		for (CourseBean c : res.getCourseList()) {
+			courses.put(c.getCode(), c.getId());
+		}
+		copyQuesTypeName(courses, fromRootOrgId, toRootOrgId);
+		redisClient.expire("$_COPY_QUESTION_DATA_LOCK", cacheLockTimeout);
+		List<String> paperIds = paperService.findPaperId(fromRootOrgId);
+		if (CollectionUtils.isNotEmpty(paperIds)) {
+			List<CopyDataDto> dtos = new ArrayList<>();
+			for(String paperId:paperIds) {
+				dtos.add(new CopyDataDto(courses, paperId, toRootOrgId, user));
+			}
+			Map<String, Object> param = new HashMap<>();
+			param.put("dtos", dtos);
+			copyDataProducer.startDispose(20, param, dtos.size());
+		}
+		String countInfo = "成功数:" + copyDataProducer.getProcess() + " 总数:" + copyDataProducer.getTotal();
+		log.info(countInfo);
+	}
+
+	private void copyQuesTypeName(Map<String, Long> courses, Long fromRootOrgId, Long toRootOrgId) {
+		List<QuesTypeName> cps = quesTypeNameRepo.findByOrgId(fromRootOrgId);
+		if (CollectionUtils.isNotEmpty(cps)) {
+			for (QuesTypeName cp : cps) {
+				cp.setOrgId(toRootOrgId.toString());
+				cp.setId(null);
+			}
+		}
+		quesTypeNameRepo.saveAll(cps);
+	}
+
+	private void clearData(Long toRootOrgId) {
+		quesTypeNameRepo.deleteByOrgId(toRootOrgId.toString());
+		paperRepo.deleteByOrgId(toRootOrgId.toString());
+		paperDetailRepo.deleteByOrgId(toRootOrgId);
+		paperDetailUnitRepo.deleteByOrgId(toRootOrgId);
+		quesRepo.deleteByOrgId(toRootOrgId.toString());
+		questionAudioRepo.deleteByOrgId(toRootOrgId);
+	}
+}

+ 13 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/PaperServiceImpl.java

@@ -96,6 +96,7 @@ import cn.com.qmth.examcloud.core.questions.service.PaperDetailService;
 import cn.com.qmth.examcloud.core.questions.service.PaperDetailUnitService;
 import cn.com.qmth.examcloud.core.questions.service.PaperService;
 import cn.com.qmth.examcloud.core.questions.service.QuesService;
+import cn.com.qmth.examcloud.core.questions.service.bean.PaperId;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.DownloadPaperDto;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.ObjectiveQuestionStructure;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.PaperDetailExp;
@@ -2457,6 +2458,18 @@ public class PaperServiceImpl implements PaperService {
         return Integer.valueOf(s.trim());
     }
 
+	@Override
+	public List<String> findPaperId(Long fromRootOrgId) {
+		Query query = new Query();
+		query.addCriteria(Criteria.where("orgId").is(fromRootOrgId.toString()));
+		List<PaperId> paperList = this.mongoTemplate.find(query, PaperId.class);
+		if(CollectionUtils.isEmpty(paperList)) {
+			return null;
+		}
+		List<String> ids=paperList.stream().map(e->e.getId()).collect(Collectors.toList());
+		return ids;
+	}
+
     
 
 }

+ 35 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/producer/CopyDataProducer.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.core.questions.service.producer;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.core.questions.base.multithread.Producer;
+import cn.com.qmth.examcloud.core.questions.service.bean.CopyDataDto;
+import cn.com.qmth.examcloud.core.questions.service.consumer.CopyDataConsumer;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+
+@Service
+public class CopyDataProducer extends Producer<CopyDataDto, CopyDataConsumer> {
+	private static int cacheLockTimeout=60*10;
+	@Autowired
+	private RedisClient redisClient;
+    @SuppressWarnings("unchecked")
+    @Override
+    protected CopyDataDto findData(Map<String, Object> param, int index) {
+        List<CopyDataDto> ret=(List<CopyDataDto>)param.get("dtos");
+        if(index>=ret.size()) {
+            return null;
+        }
+        redisClient.expire("$_COPY_QUESTION_DATA_LOCK", cacheLockTimeout);
+        return ret.get(index);
+    }
+
+    @Override
+    protected String getTaskName() {
+        return "数据复制";
+    }
+
+}

+ 2 - 0
examcloud-core-questions-starter/src/main/java/cn/com/qmth/examcloud/core/questions/starter/CoreQuestionApp.java

@@ -14,10 +14,12 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
 import org.springframework.data.mongodb.config.EnableMongoAuditing;
 import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
+import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 import org.springframework.web.multipart.MultipartResolver;
 import org.springframework.web.multipart.commons.CommonsMultipartResolver;
 
+@EnableAsync
 @Configuration
 @EnableJpaAuditing
 @EnableMongoAuditing