deason 5 年之前
父節點
當前提交
e4b6def342
共有 32 個文件被更改,包括 1610 次插入2 次删除
  1. 12 0
      .gitignore
  2. 0 2
      README.md
  3. 30 0
      assembly.xml
  4. 4 0
      jenkins-prod.sh
  5. 18 0
      jenkins-test.sh
  6. 93 0
      pom.xml
  7. 1 0
      shell/start.args
  8. 38 0
      shell/start.sh
  9. 1 0
      shell/start.vmoptions
  10. 18 0
      shell/stop.sh
  11. 1 0
      shell/version
  12. 55 0
      src/main/java/cn/com/qmth/examcloud/bridge/BridgeApp.java
  13. 40 0
      src/main/java/cn/com/qmth/examcloud/bridge/Tester.java
  14. 27 0
      src/main/java/cn/com/qmth/examcloud/bridge/commons/BridgeDataSource.java
  15. 27 0
      src/main/java/cn/com/qmth/examcloud/bridge/commons/BridgeDatabaseUtil.java
  16. 63 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/bean/Block.java
  17. 25 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/bean/Section.java
  18. 25 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/bean/Sections.java
  19. 430 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/controller/CloudMarkingClientController.java
  20. 355 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/controller/CloudMarkingController.java
  21. 7 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/package-info.java
  22. 50 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/school1/controller/School1Controller.java
  23. 6 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/school1/package-info.java
  24. 5 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/school1/service/School1Service.java
  25. 6 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/school2/package-info.java
  26. 168 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/swjtu/controller/SwjtuController.java
  27. 6 0
      src/main/java/cn/com/qmth/examcloud/bridge/modules/swjtu/package-info.java
  28. 24 0
      src/main/java/cn/com/qmth/examcloud/bridge/开发说明.txt
  29. 8 0
      src/main/resources/application.properties
  30. 1 0
      src/main/resources/classpath.location
  31. 0 0
      src/main/resources/limited.properties
  32. 66 0
      src/main/resources/log4j2.xml

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+.project
+.classpath
+.settings
+target/
+.idea/
+*.iml
+*test/
+# Package Files #
+*.jar
+logs/
+
+

+ 0 - 2
README.md

@@ -1,2 +0,0 @@
-# examcloud-bridge
-

+ 30 - 0
assembly.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+	<id>distribution</id>
+	<formats>
+		<format>zip</format>
+	</formats>
+	<fileSets>
+		<fileSet>
+			<directory>${project.basedir}/src/main/resources</directory>
+			<outputDirectory>/config</outputDirectory>
+		</fileSet>
+		<fileSet>
+			<directory>${project.basedir}/shell</directory>
+			<excludes>
+				<exclude>start.args</exclude>
+				<exclude>start.vmoptions</exclude>
+			</excludes>
+			<outputDirectory>/</outputDirectory>
+			<fileMode>0777</fileMode>
+		</fileSet>
+	</fileSets>
+	<dependencySets>
+		<dependencySet>
+			<useProjectArtifact>true</useProjectArtifact>
+			<outputDirectory>lib</outputDirectory>
+			<scope>runtime</scope>
+		</dependencySet>
+	</dependencySets>
+</assembly>

+ 4 - 0
jenkins-prod.sh

@@ -0,0 +1,4 @@
+#!/bin/bash
+pwd
+
+cp target/examcloud-bridge-distribution.zip ~/packages

+ 18 - 0
jenkins-test.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+pwd
+
+rm -rf ~/project/examcloud/examcloud-bridge-distribution.zip
+rm -rf ~/project/examcloud/examcloud-bridge/lib/
+
+cp target/examcloud-bridge-distribution.zip ~/project/examcloud/
+
+cd  ~/project/examcloud/
+unzip -o examcloud-bridge-distribution.zip
+
+cd examcloud-bridge
+echo "--spring.profiles.active=test --examcloud.startup.configCenterHost=localhost" > start.args
+echo "-server -Xms128m -Xmx128m  -XX:-UseGCOverheadLimit" > start.vmoptions
+
+bash stop.sh
+BUILD_ID=DONTKILLME
+bash start.sh jenkins

+ 93 - 0
pom.xml

@@ -0,0 +1,93 @@
+<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>
+	<parent>
+		<groupId>cn.com.qmth.examcloud</groupId>
+		<artifactId>examcloud-parent</artifactId>
+		<version>2019</version>
+	</parent>
+	<artifactId>examcloud-bridge</artifactId>
+	<version>2019-SNAPSHOT</version>
+	<packaging>jar</packaging>
+
+	<properties>
+		<!-- 云平台版本 -->
+		<examcloud.version>2019-SNAPSHOT</examcloud.version>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-web</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-support</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-basic-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-examwork-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-oe-admin-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<configuration>
+					<archive>
+						<manifest>
+							<mainClass>cn.com.qmth.examcloud.bridge.BridgeApp</mainClass>
+							<addClasspath>true</addClasspath>
+							<classpathPrefix>./</classpathPrefix>
+						</manifest>
+						<manifestEntries>
+							<Class-Path>../config/</Class-Path>
+						</manifestEntries>
+					</archive>
+					<excludes>
+						<exclude>*.xml </exclude>
+						<exclude>*.properties</exclude>
+						<exclude>classpath.location</exclude>
+					</excludes>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<configuration>
+					<finalName>examcloud-bridge</finalName>
+					<descriptors>
+						<descriptor> assembly.xml</descriptor>
+					</descriptors>
+				</configuration>
+				<executions>
+					<execution>
+						<id>make-assembly</id>
+						<phase>install</phase>
+						<goals>
+							<goal>assembly</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>

+ 1 - 0
shell/start.args

@@ -0,0 +1 @@
+--spring.profiles.active=test

+ 38 - 0
shell/start.sh

@@ -0,0 +1,38 @@
+#!/bin/bash
+
+FILE_PATH=$(cd `dirname $0`; pwd)
+APP_VERSION=`cat $FILE_PATH/version`
+APP_MAIN_JAR="examcloud-bridge-"$APP_VERSION"-SNAPSHOT.jar"
+
+JAVA_OPTS=`cat $FILE_PATH/start.vmoptions`
+APP_ARGS=`cat $FILE_PATH/start.args`
+
+PID_LIST=`ps -ef|grep $APP_MAIN_JAR|grep java|awk '{print $2}'`
+
+if [ ! -z "$PID_LIST" ]; then
+    echo "[ERROR] : APP is already running!"
+    exit -1
+fi
+
+if [ "$1" ];then
+    echo "startupCode:"$1;
+else
+    echo "[ERROR] : no arguments"
+    exit -1
+fi
+
+APP_ARGS=$APP_ARGS" --examcloud.startup.startupCode="$1
+
+echo "java options:"
+echo "$JAVA_OPTS"
+echo "args:"
+echo "$APP_ARGS"
+    
+nohup java $JAVA_OPTS -jar $FILE_PATH/lib/$APP_MAIN_JAR $APP_ARGS >/dev/null 2>&1 &
+
+echo "starting......"
+
+exit 0
+
+
+

+ 1 - 0
shell/start.vmoptions

@@ -0,0 +1 @@
+-server -Xms1g -Xmx1g

+ 18 - 0
shell/stop.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+
+FILE_PATH=$(cd `dirname $0`; pwd)
+APP_VERSION=`cat $FILE_PATH/version`
+APP_MAIN_JAR="examcloud-bridge-"$APP_VERSION"-SNAPSHOT.jar"
+
+PID_LIST=`ps -ef|grep $APP_MAIN_JAR|grep java|awk '{print $2}'`
+
+if [ ! -z "$PID_LIST" ]; then
+    echo "Runnable jar is $APP_MAIN_JAR."
+    for PID in $PID_LIST 
+    do
+        kill -9 $PID
+    done
+    echo "stopped !"
+fi
+
+exit 0

+ 1 - 0
shell/version

@@ -0,0 +1 @@
+2019

+ 55 - 0
src/main/java/cn/com/qmth/examcloud/bridge/BridgeApp.java

@@ -0,0 +1,55 @@
+package cn.com.qmth.examcloud.bridge;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+import cn.com.qmth.examcloud.web.bootstrap.AppBootstrap;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+/**
+ * 启动类
+ *
+ * @author WANGWEI
+ * @date 2019年10月12日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@SpringBootApplication
+@Configuration
+@ComponentScan(basePackages = {"cn.com.qmth"})
+@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
+public class BridgeApp {
+
+	static {
+		String runtimeLevel = System.getProperty("log.commonLevel");
+		if (null == runtimeLevel) {
+			System.setProperty("log.commonLevel", "DEBUG");
+		}
+	}
+
+	/**
+	 * main
+	 *
+	 * @author WANGWEI
+	 * @param args
+	 */
+	public static void main(String[] args) {
+
+		AppBootstrap.run(BridgeApp.class, args);
+
+		test();
+	}
+
+	/**
+	 * 测试方法
+	 *
+	 * @author WANGWEI
+	 */
+	private static void test() {
+		Tester tester = SpringContextHolder.getBean(Tester.class);
+		tester.test();
+	}
+
+}

+ 40 - 0
src/main/java/cn/com/qmth/examcloud/bridge/Tester.java

@@ -0,0 +1,40 @@
+package cn.com.qmth.examcloud.bridge;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.bridge.commons.BridgeDataSource;
+import cn.com.qmth.examcloud.bridge.commons.BridgeDatabaseUtil;
+
+@Component
+public class Tester {
+
+	public void test() {
+		// testJdbc();
+	}
+
+	/**
+	 * 测试JDBC
+	 *
+	 * @author WANGWEI
+	 */
+	public void testJdbc() {
+		try {
+			Connection conn = BridgeDatabaseUtil.getConnection(BridgeDataSource.EXAM_CLOUD);
+			PreparedStatement ps = conn.prepareStatement("select 'jdbc test' from dual");
+			ResultSet rs = ps.executeQuery();
+
+			while (rs.next()) {
+				Object object = rs.getObject(1);
+				System.out.println(object);
+			}
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+	}
+
+}

+ 27 - 0
src/main/java/cn/com/qmth/examcloud/bridge/commons/BridgeDataSource.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.examcloud.bridge.commons;
+
+/**
+ * 数据源名称枚举
+ *
+ * @author WANGWEI
+ * @date 2019年10月14日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum BridgeDataSource {
+
+	EXAM_CLOUD("examcloud.db");
+
+	/**
+	 * 数据源名称
+	 */
+	private String name;
+
+	BridgeDataSource(String name) {
+		this.name = name;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+}

+ 27 - 0
src/main/java/cn/com/qmth/examcloud/bridge/commons/BridgeDatabaseUtil.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.examcloud.bridge.commons;
+
+import java.sql.Connection;
+
+import cn.com.qmth.examcloud.commons.util.DBUtil;
+
+/**
+ * 数据库工具
+ *
+ * @author WANGWEI
+ * @date 2019年10月14日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class BridgeDatabaseUtil {
+
+	/**
+	 * get JDBC connection
+	 *
+	 * @author WANGWEI
+	 * @param bridgeDataSource
+	 * @return
+	 */
+	public static Connection getConnection(BridgeDataSource bridgeDataSource) {
+		return DBUtil.getConnection(bridgeDataSource.getName());
+	}
+
+}

+ 63 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/bean/Block.java

@@ -0,0 +1,63 @@
+package cn.com.qmth.examcloud.bridge.modules.cloudmarking.bean;
+
+import java.util.Map;
+
+/**
+ * @Description 从题库拷过来的代码,用于处理导出阅卷数据 ,见ExamRecordQuestionsCloudServiceProvider
+ * @Author lideyin
+ * @Date 2020/3/2 18:35
+ * @Version 1.0
+ */
+public class Block {
+    /**
+     * text:文字
+     * image:图片
+     * audio:音频
+     * video:视频
+     */
+    private String type;
+    /**
+     * 资源相对路径
+     */
+    private String value;
+    /**
+     * 播放次数
+     */
+    private Integer playTime;
+
+    private Map<String, Object> param;
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public Map<String, Object> getParam() {
+        return param;
+    }
+
+    public void setParam(Map<String, Object> param) {
+        this.param = param;
+    }
+
+    public Integer getPlayTime() {
+        return playTime;
+    }
+
+    public void setPlayTime(Integer playTime) {
+        this.playTime = playTime;
+    }
+
+}
+

+ 25 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/bean/Section.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.bridge.modules.cloudmarking.bean;
+
+import java.util.List;
+
+/**
+ * @Description 从题库拷过来的代码,用于处理导出阅卷数据
+ * @Author lideyin
+ * @Date 2020/3/2 18:36
+ * @Version 1.0
+ */
+public class Section {
+
+    private List<Block> blocks;
+
+    public List<Block> getBlocks() {
+        return blocks;
+    }
+
+    public void setBlocks(List<Block> blocks) {
+        this.blocks = blocks;
+    }
+
+
+}
+

+ 25 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/bean/Sections.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.bridge.modules.cloudmarking.bean;
+
+import java.util.List;
+
+/**
+ * @Description 从题库拷过来的代码,用于处理导出阅卷数据
+ * @Author lideyin
+ * @Date 2020/3/2 18:35
+ * @Version 1.0
+ */
+public class Sections {
+
+    private List<Section> sections;
+
+    public List<Section> getSections() {
+        return sections;
+    }
+
+    public void setSections(List<Section> sections) {
+        this.sections = sections;
+    }
+
+
+}
+

+ 430 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/controller/CloudMarkingClientController.java

@@ -0,0 +1,430 @@
+package cn.com.qmth.examcloud.bridge.modules.cloudmarking.controller;
+
+import cn.com.qmth.examcloud.bridge.modules.cloudmarking.bean.Block;
+import cn.com.qmth.examcloud.bridge.modules.cloudmarking.bean.Section;
+import cn.com.qmth.examcloud.bridge.modules.cloudmarking.bean.Sections;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.*;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordQuestionsCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.OeExamStudentCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ToBeMarkExamRecordBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ToBeMarkExamStudentBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ToBeMarkSubjectiveAnswerBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetToBeMarkExamRecordReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetToBeMarkExamStudentReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetToBeMarkExamRecordResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetToBeMarkExamStudentResp;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
+import com.google.common.collect.Maps;
+import com.mysql.cj.util.StringUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @Description 云阅卷客户端接口
+ * 错误码范围(102001 至 101999)
+ * @Author lideyin
+ * @Date 2019/11/20 14:49
+ * @Version 1.0
+ */
+@RestController
+@RequestMapping("cmcClient")
+public class CloudMarkingClientController {
+    @Autowired
+    ExamRecordQuestionsCloudService examRecordQuestionsCloudService;
+
+    @Autowired
+    SystemProperties systemConfig;
+
+    @Autowired
+    ExamRecordCloudService examRecordCloudService;
+
+    @Autowired
+    OeExamStudentCloudService oeExamStudentCloudService;
+
+    Logger logger = LoggerFactory.getLogger(CloudMarkingClientController.class);
+
+    /**
+     * 批量生成主观题答案,以压缩文件方式返回
+     */
+    @GetMapping(value = "batchGetAnswer/{examId}")
+    public ResponseEntity<?> batchGetSubjectiveAnswer(@PathVariable Long examId) {
+        if (null == examId) {
+            throw new StatusException("101001", "考试批次id不允许为空");
+        }
+
+        GetToBeMarkExamStudentReq req = new GetToBeMarkExamStudentReq();
+        req.setExamId(examId);
+        //获取考试批次下所有考生
+        GetToBeMarkExamStudentResp toBeMarkExamStudentResp = oeExamStudentCloudService.getToBeMarkExamStudent(req);
+        List<ToBeMarkExamStudentBean> examStudentList = toBeMarkExamStudentResp.getExamStudentList();
+        if (examStudentList.isEmpty()) {
+            throw new StatusException("101002", "该考试批次下无待阅考生数据");
+        }
+
+        if (logger.isDebugEnabled()) {
+//            logger.debug("[BATCH_GET_ANSWER-" + examId + "],获取考试批次下所有考生:" + JsonUtil.toJson(examStudentList));
+            logger.debug("[001.BATCH_GET_ANSWER-" + examId + "],获取考试批次下所有考生数量:" + examStudentList.size());
+        }
+
+        for (ToBeMarkExamStudentBean stu : examStudentList) {
+            GetToBeMarkExamRecordReq getExamRecordReq = new GetToBeMarkExamRecordReq();
+            getExamRecordReq.setExamStudentIdList(Arrays.asList(stu.getExamStudentId()));
+
+            GetToBeMarkExamRecordResp getExamRecordResp = examRecordCloudService.getToBeMarkExamRecord(getExamRecordReq);
+
+            saveDataToLocal(getExamRecordResp,examId);
+        }
+
+        //最终文件的生成目录
+        final String tempAnswerDir = systemConfig.getTempDataDir() + "/exam-" + examId;
+        String zipPath = tempAnswerDir + "-" + System.currentTimeMillis() + ".zip";
+        File zipFile = new File(zipPath);
+        ZipUtil.zip(new File(tempAnswerDir), zipFile);
+
+        byte[] bytes = IOUtil.toByteArray(zipFile);
+
+        String downLoadFileName = "exam-" + examId + ".zip";
+        ResponseEntity<byte[]> responseEntity = ResponseEntity.ok().
+                header("Content-Disposition", "attachment; filename=" + downLoadFileName).
+                contentType(MediaType.APPLICATION_OCTET_STREAM).body(bytes);
+
+        clearTempAnswerFile(tempAnswerDir);
+        return responseEntity;
+
+
+//        return ResponseEntity.ok().body("数据生成完毕.");
+    }
+
+    /**
+     * 清空临时目录中的文件
+     *
+     * @param tempAnswerDir
+     */
+    private void clearTempAnswerFile(String tempAnswerDir) {
+        File t1 = new File(tempAnswerDir);
+        for (File f : t1.listFiles()) {
+            FileUtils.deleteQuietly(f);
+        }
+    }
+
+    private void saveDataToLocal(GetToBeMarkExamRecordResp resp, Long examId) {
+
+        if (!resp.getToBeMarkExamRecordBeanList().isEmpty()) {
+
+            StringBuilder sb = new StringBuilder();
+            String COL_SEPARATOR = "\t";//列分隔符
+            String LINE_BREAK = "\r\n";//换行
+            //循环待审阅的考试记录
+            for (ToBeMarkExamRecordBean rb : resp.getToBeMarkExamRecordBeanList()) {
+                //构建文本文件串
+                sb.append(rb.getStudentName()).append(COL_SEPARATOR)
+                        .append(rb.getStudentCode()).append(COL_SEPARATOR)
+                        .append(rb.getCourseName()).append(COL_SEPARATOR)
+                        .append(rb.getCourseCode()).append(COL_SEPARATOR)
+                        .append(rb.getPaperType()).append(COL_SEPARATOR)
+                        .append(rb.getExamRecordDataId()).append(LINE_BREAK);
+
+                //答案临时存放目录
+                final String tempAnswerDir =
+                        systemConfig.getTempDataDir() + "/exam-" + rb.getExamId() + "/" + rb.getCourseCode() + "-" + rb.getPaperType();
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("[BATCH_GET_ANSWER-" + examId + "],临时文件目录:" + JsonUtil.toJson(tempAnswerDir));
+                }
+
+                List<Map<String, Object>> resultMapList = new ArrayList<>();
+
+                //循环主观题答案
+                for (ToBeMarkSubjectiveAnswerBean sab : rb.getSubjectiveAnswerList()) {
+                    Map<String, Object> resultMap = Maps.newHashMap();
+                    //构建基本信息
+                    resultMap.put("mainNumber", sab.getMainNumber());//大题号
+                    resultMap.put("subNumber", sab.getOrder());//小题号
+
+                    //构建题干
+                    resultMap.put("body",getBodyOrAnswer(sab.getBody()));
+
+                    //构建标准答案
+                    resultMap.put("answer",getBodyOrAnswer(sab.getAnswer()));
+
+                    //构建学生作答
+                    resultMap.put("studentAnswer", buildStudentAnswerMap(sab));
+
+                    resultMapList.add(resultMap);
+                }
+
+                //将考生结果保存至临时文件
+                String answerJson = JsonUtil.toJson(resultMapList);
+                String jsonFilePath = tempAnswerDir + "/" + rb.getExamRecordDataId() + ".json";
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("[BATCH_GET_ANSWER-" + examId + "],最终作答结果路径:jsonFilePath=" + jsonFilePath);
+                }
+                if (logger.isDebugEnabled()) {
+                    logger.debug("[BATCH_GET_ANSWER-" + examId + "],最终作答结果:answerJson=" + answerJson);
+                }
+
+                IOUtil.toFile(answerJson.getBytes(Charset.forName("UTF-8")), jsonFilePath);
+            }
+
+            final String tempAnswerDir = systemConfig.getTempDataDir() + "/exam-" + examId;
+            if (!StringUtils.isNullOrEmpty(sb.toString())) {
+                String txtFilePath = tempAnswerDir + "/examStudent.txt";
+                IOUtil.toFile(sb.toString().getBytes(Charset.forName("UTF-8")), txtFilePath);
+            }
+
+        } else {
+            if (logger.isDebugEnabled()) {
+                logger.debug("[006.BATCH_GET_ANSWER-" + examId + "],enter saveDataToLocal-----GetToBeMarkExamRecordResp 空数据");
+            }
+        }
+    }
+
+    private Map<String, Object> buildStudentAnswerMap(ToBeMarkSubjectiveAnswerBean sab) {
+        Map<String, Object> studentAnswerMap = Maps.newHashMap();
+
+        List<Map<String, Object>> sectionMapList = new ArrayList<>();
+        Map<String, Object> sectionMap = Maps.newHashMap();
+        List<Map<String, Object>> blockMapList = new ArrayList<>();
+
+        //校验答案格式,校验成功才追加至集合,否则不追加
+        if (validateAnswer(sab.getRealAnswerType(), sab.getStudentAnswer())) {
+            //图片文件特殊处理
+            if (!StringUtils.isNullOrEmpty(sab.getStudentAnswer()) &&
+                    "image".equals(sab.getRealAnswerType())) {
+                if (sab.getStudentAnswer().indexOf("|") > -1) {
+                    String[] imgAnswers = sab.getStudentAnswer().split("\\|");
+                    for (int i = 0; i < imgAnswers.length; i++) {
+                        Map<String, Object> blockMap = Maps.newHashMap();
+                        blockMap.put("type", sab.getRealAnswerType());
+                        blockMap.put("value", transformedAnswer(sab.getRealAnswerType(), imgAnswers[i]));
+
+                        //又拍云图片的宽*高
+                        String imgWH = RegExpUtil.find(imgAnswers[i], "(\\d+)x(\\d+)");
+                        if (!StringUtils.isNullOrEmpty(imgWH)) {
+                            Map<String, Object> paramMap = new HashMap<>();
+                            paramMap.put("width", Integer.valueOf(imgWH.split("x")[0]));
+                            paramMap.put("height", Integer.valueOf(imgWH.split("x")[1]));
+                            blockMap.put("param", paramMap);
+                        }
+
+                        blockMapList.add(blockMap);
+                    }
+                }
+            } else {
+                Map<String, Object> blockMap = Maps.newHashMap();
+                blockMap.put("type", sab.getRealAnswerType());
+                blockMap.put("value", transformedAnswer(sab.getRealAnswerType(), sab.getStudentAnswer()));
+                blockMapList.add(blockMap);
+            }
+        }
+
+        sectionMap.put("blocks", blockMapList);
+        sectionMapList.add(sectionMap);
+
+        studentAnswerMap.put("sections", sectionMapList);
+        return studentAnswerMap;
+    }
+
+    private String transformedAnswer(String answerType, String studentAnswer) {
+        if ("image".equals(answerType)) {
+            String regExp = "(ftp|https?)\\:\\/\\/([\\w\\_\\-]+)\\.([\\w\\-]+[\\.]?)*[\\w]+\\.[a-zA-Z]{2,10}(.*)\\.(png|jpg|gif|jpeg)";
+            return RegExpUtil.find(studentAnswer, regExp);
+        }
+        return studentAnswer;
+    }
+
+    /**
+     * 校验答案格式是否正确
+     *
+     * @param realAnswerType
+     * @param studentAnswer
+     * @return
+     */
+    private boolean validateAnswer(String realAnswerType, String studentAnswer) {
+        if (StringUtils.isNullOrEmpty(studentAnswer)) {
+            return false;
+        }
+
+        String regExp;
+        switch (realAnswerType) {
+            case "image":
+                //图片必须包含.png|jpg|gif|jpeg
+                regExp = "^(ftp|https?)\\:\\/\\/([\\w\\_\\-]+)\\.([\\w\\-]+[\\.]?)*[\\w]+\\.[a-zA-Z]{2,10}(.*)\\.(png|jpg|gif|jpeg).*$";
+                return studentAnswer.matches(regExp);
+            case "audio":
+                //音频必须包含.mp3
+                regExp = "^(ftp|https?)\\:\\/\\/([\\w\\_\\-]+)\\.([\\w\\-]+[\\.]?)*[\\w]+\\.[a-zA-Z]{2,10}(.*)\\.(mp3)";
+                return studentAnswer.matches(regExp);
+        }
+
+        return true;
+    }
+
+    /**
+     * 获取格式化后后的题干或标准答案
+     * @param str
+     * @return
+     */
+    private Sections getBodyOrAnswer(String str) {
+        Sections body = new Sections();
+        List<Section> sections = new ArrayList<>();
+        // 得到小题题干或者答案行数
+        if (org.apache.commons.lang3.StringUtils.isBlank(str)) {
+            return body;
+        }
+        String[] questionRowStrings = str.split("</p>");
+        for (int i = 0; i < questionRowStrings.length; i++) {
+            List<Block> blocks = disposeQuestionBodyOrOption(questionRowStrings[i]);
+            if (blocks != null && blocks.size() > 0) {
+                Section section = new Section();
+                // 将小题题干拆分为Block集合
+                section.setBlocks(blocks);
+                sections.add(section);
+            }
+        }
+        body.setSections(sections);
+        return body;
+    }
+
+    private List<Block> disposeQuestionBodyOrOption(String questionRow) {
+        List<Block> blocks = new ArrayList<>();
+        // 去掉每行里面的<p>,<span>,</span>标签
+        questionRow = questionRow.replaceAll("<p>", "").replaceAll("</p>", "").replaceAll("<span>", "")
+                .replaceAll("</span>", "").replaceAll("</a>", "");
+        String[] questionRowStrings = questionRow.split("<|/>|>");
+        for (int i = 0; i < questionRowStrings.length; i++) {
+            Block block = new Block();
+            String rowStr = questionRowStrings[i];
+            // 判断是否有图片
+            if (rowStr.startsWith("img")) {
+                rowStr = "<" + rowStr + ">";
+                Map<String, Object> param = new HashMap<>();
+                // 需要继续做截取,取到Parma
+                block.setType("image");
+                // 获取图片的路径
+                List<String> strSrcList = getImg(rowStr, "src");
+                if (strSrcList.size() > 0) {
+                    String strSrc = strSrcList.get(0).replaceAll("src=\"", "").replaceAll("\"", "");
+                    block.setValue(strSrc);
+                }
+                // 获取图片的高度
+                List<String> strHeightList = getImg(rowStr, "height");
+                if (strHeightList.size() > 0) {
+                    String strHeight = strHeightList.get(0).replaceAll("height=\"", "").replaceAll("\"", "");
+                    param.put("height", strHeight);
+                }
+                // 获取图片的宽度
+                List<String> strWidthList = getImg(rowStr, "width");
+                if (strHeightList.size() > 0) {
+                    String strWidth = strWidthList.get(0).replaceAll("width=\"", "").replaceAll("\"", "");
+                    param.put("width", strWidth);
+                }
+                block.setParam(param);
+                blocks.add(block);
+            } else if (rowStr.startsWith("a") && rowStr.contains("id") && rowStr.contains("name")) { // 处理音频
+                rowStr = "<" + rowStr + ">";
+                block.setPlayTime(1);
+                block.setType("audio");
+                block.setValue(this.getAttrValue(rowStr, "id"));
+                blocks.add(block);
+            } else {
+                block.setType("text");
+                rowStr = rowStr.replace("&nbsp;", "");// 消除空格
+                rowStr = rowStr.replace("&quot;", "\"");// 将&quot;转换成\"
+                rowStr = rowStr.replace("&lt;", "<");// 将&lt;转换成<
+                rowStr = rowStr.replace("&gt;", ">");// 将&gt;转换成>
+                rowStr = rowStr.replace("&amp;", "&");// 将&amp;转换成&
+                if (org.apache.commons.lang3.StringUtils.isNotBlank(rowStr)) {
+                    block.setValue(rowStr);
+                    blocks.add(block);
+                }
+            }
+        }
+
+        return blocks;
+    }
+
+    /**
+     * 获取图片里面的路径,长度,宽度
+     */
+    private List<String> getImg(String s, String str) {
+        String regex;
+        List<String> list = new ArrayList<>();
+        regex = str + "=\"(.*?)\"";
+        Pattern pa = Pattern.compile(regex, Pattern.DOTALL);
+        Matcher ma = pa.matcher(s);
+        while (ma.find()) {
+            list.add(ma.group());
+        }
+        return list;
+    }
+
+    private String getAttrValue(String questionStr, String attrName) {
+        Pattern aPattern = Pattern.compile("a.*");
+        Matcher aMatcher = aPattern.matcher(questionStr);
+
+        if (aMatcher.find()) {
+            String idRegex = attrName + "=\".*?\"";
+            Pattern idPattern = Pattern.compile(idRegex);
+            Matcher idMatcher = idPattern.matcher(aMatcher.group());
+            if (idMatcher.find()) {
+                return idMatcher.group()
+                        .replaceAll(attrName + "=\"", "")
+                        .replaceAll("\"", "");
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * 校验字符串和正则表达式是否匹配
+     *
+     * @param regExp
+     * @param input
+     * @return
+     */
+    private boolean isRegExpMatch(String regExp, String input) {
+        Pattern pattern = Pattern.compile(regExp);
+        Matcher matcher = pattern.matcher(input);
+        return matcher.matches();
+    }
+
+    public static void main(String[] args) {
+        String regExp = "^(ftp|https?)\\:\\/\\/([\\w\\_\\-]+)\\.([\\w\\-]+[\\.]?)*[\\w]+\\.[a-zA-Z]{2,10}(.*)\\.(png|jpg|gif|jpeg)(\\s|\\S)*$";
+        String regExp1 = "(ftp|https?)\\:\\/\\/([\\w\\_\\-]+)\\.([\\w\\-]+[\\.]?)*[\\w]+\\.[a-zA-Z]{2,10}(.*)\\.(png|jpg|gif|jpeg)";
+        String regExp2 = "(\\d+)x(\\d+)";
+        String input = "https://ecs-test-static.qmth.com.cn/oe-answer-file/3/7/20/3_7_20_15748449608356000.jpeg!/both/200x200";
+        Pattern pattern = Pattern.compile(regExp);
+        Matcher matcher = pattern.matcher(input);
+//
+        System.out.println(matcher.matches());
+        System.out.println(matcher.group());
+        System.out.println(RegExpUtil.find(input, regExp1));
+        System.out.println(RegExpUtil.find(input, regExp2));
+
+    }
+}

+ 355 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/controller/CloudMarkingController.java

@@ -0,0 +1,355 @@
+package cn.com.qmth.examcloud.bridge.modules.cloudmarking.controller;
+
+import cn.com.qmth.examcloud.bridge.modules.cloudmarking.bean.Block;
+import cn.com.qmth.examcloud.bridge.modules.cloudmarking.bean.Section;
+import cn.com.qmth.examcloud.bridge.modules.cloudmarking.bean.Sections;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.IOUtil;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.ZipUtil;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordQuestionsCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.SubjectiveAnswerBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetSubjectiveAnswerReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetToBeMarkExamRecordReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetSubjectiveAnswerResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetToBeMarkExamRecordResp;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
+import com.google.common.collect.Maps;
+import org.apache.commons.io.FileUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @Description 云阅卷服务端接口
+ * 错误码范围(101001 至 101999)
+ * @Author lideyin
+ * @Date 2019/11/20 14:49
+ * @Version 1.0
+ */
+@RestController
+@RequestMapping("cmc")
+public class CloudMarkingController {
+    @Autowired
+    ExamRecordQuestionsCloudService examRecordQuestionsCloudService;
+
+    @Autowired
+    SystemProperties systemConfig;
+
+    @Autowired
+    ExamRecordCloudService examRecordCloudService;
+
+    /**
+     * 根据考生获取待阅卷的主观题作答记录
+     *
+     * @param examStudentId 考生id
+     * @return
+     */
+    @GetMapping(value = "getSubjectiveAnswer/{examStudentId}")
+    public GetToBeMarkExamRecordResp getSubjectiveAnswer(@PathVariable Long examStudentId) {
+        if (null == examStudentId) {
+            throw new StatusException("101001", "考生id不允许为空");
+        }
+
+        GetToBeMarkExamRecordReq req = new GetToBeMarkExamRecordReq();
+        req.setExamStudentIdList(Arrays.asList(examStudentId));
+
+        return examRecordCloudService.getToBeMarkExamRecord(req);
+    }
+
+    /**
+     * 批量生成主观题答案,以压缩文件方式返回
+     */
+    @GetMapping(value = "batchGetAnswer/{examId}")
+    public ResponseEntity<?> batchGetSubjectiveAnswer(@PathVariable Long examId) {
+        if (null == examId) {
+            throw new StatusException("101001", "考试批次id不允许为空");
+        }
+
+        //答案临时存放目录
+        final String tempAnswerDir = systemConfig.getTempDataDir() + "/tempAnswer" + System.currentTimeMillis();
+
+        Long startId = 0L;
+        Boolean hasData = false;
+        while (true) {
+            GetSubjectiveAnswerReq req = new GetSubjectiveAnswerReq();
+            req.setExamId(examId);
+            req.setStartExamRecordId(startId);
+
+            GetSubjectiveAnswerResp resp = examRecordQuestionsCloudService.getSubjectiveAnswerList(req);
+//            GetSubjectiveAnswerResp resp = JsonUtil.fromJson(testJson(), GetSubjectiveAnswerResp.class);
+            if (resp.getNextExamRecordDataId().equals(startId)) {
+                break;
+            }
+
+            if (!hasData) {
+                hasData = true;
+            }
+
+            List<SubjectiveAnswerBean> subjectiveAnswerList = resp.getSubjectiveAnswerList();
+
+            List<Long> examStudentIdList = subjectiveAnswerList.stream().
+                    map(p -> p.getExamStudentId()).
+                    distinct().collect(Collectors.toList());
+
+            String courseCode = subjectiveAnswerList.get(0).getCourseCode();
+
+            for (int i = 0; i < examStudentIdList.size(); i++) {
+                Long examStudentId = examStudentIdList.get(i);
+
+                List<Map<String, Object>> resultMapList = new ArrayList<>();
+
+                //当前学生的作答记录
+                List<SubjectiveAnswerBean> curStuAnswerList = subjectiveAnswerList.
+                        stream().
+                        filter(p -> p.getExamStudentId().equals(examStudentId)).
+                        collect(Collectors.toList());
+
+                for (SubjectiveAnswerBean sab : curStuAnswerList) {
+                    Map<String, Object> resultMap = Maps.newHashMap();
+                    //构建基本信息
+                    resultMap.put("mainNumber", sab.getMainNumber());//大题号
+                    resultMap.put("subNumber", sab.getOrder());//小题号
+
+                    //构建题干
+                    resultMap.put("body",getBodyOrAnswer(sab.getBody()));
+
+                    //构建标准答案
+                    resultMap.put("answer",getBodyOrAnswer(sab.getAnswer()));
+
+                    //构建学生作答
+                    resultMap.put("studentAnswer", buildStudentAnswerMap(sab));
+
+                    resultMapList.add(resultMap);
+                }
+
+                //将考生结果保存至临时文件
+                String json = JsonUtil.toJson(resultMapList);
+                String filePath = tempAnswerDir + "/" + examId + "-" + courseCode + "-" + examStudentId + ".json";
+                IOUtil.toFile(json.getBytes(Charset.forName("UTF-8")), filePath);
+            }
+            startId = resp.getNextExamRecordDataId();
+        }
+
+        if (!hasData) {
+            return ResponseEntity.ok().body("There's no data to download.");
+        }
+
+        String zipPath = tempAnswerDir + "/" + examId + "-" + System.currentTimeMillis() + ".zip";
+        File zipFile = new File(zipPath);
+        ZipUtil.zip(new File(tempAnswerDir), zipFile);
+
+        byte[] bytes = IOUtil.toByteArray(zipFile);
+
+        String downLoadFileName = "exam-" + examId + ".zip";
+        ResponseEntity<byte[]> responseEntity = ResponseEntity.ok().
+                header("Content-Disposition", "attachment; filename=" + downLoadFileName).
+                contentType(MediaType.APPLICATION_OCTET_STREAM).body(bytes);
+
+        clearTempAnswerFile(tempAnswerDir);
+        return responseEntity;
+    }
+
+    private Map<String, Object> buildStudentAnswerMap(SubjectiveAnswerBean sab) {
+        //构建学生作答
+        Map<String, Object> studentAnswerMap = Maps.newHashMap();
+
+        List<Map<String, Object>> sectionMapList = new ArrayList<>();
+        Map<String, Object> sectionMap = Maps.newHashMap();
+        List<Map<String, Object>> blockMapList = new ArrayList<>();
+
+        Map<String, Object> blockMap = Maps.newHashMap();
+        blockMap.put("type", sab.getRealAnswerType());
+        blockMap.put("value", sab.getStudentAnswer());
+        blockMapList.add(blockMap);
+
+        sectionMap.put("blocks", blockMapList);
+        sectionMapList.add(sectionMap);
+
+        studentAnswerMap.put("sections", sectionMapList);
+        return studentAnswerMap;
+    }
+
+    /**
+     * 获取格式化后后的题干或标准答案
+     * @param str
+     * @return
+     */
+    private Sections getBodyOrAnswer(String str) {
+        Sections body = new Sections();
+        List<Section> sections = new ArrayList<>();
+        // 得到小题题干或者答案行数
+        if (org.apache.commons.lang3.StringUtils.isBlank(str)) {
+            return body;
+        }
+        String[] questionRowStrings = str.split("</p>");
+        for (int i = 0; i < questionRowStrings.length; i++) {
+            List<Block> blocks = disposeQuestionBodyOrOption(questionRowStrings[i]);
+            if (blocks != null && blocks.size() > 0) {
+                Section section = new Section();
+                // 将小题题干拆分为Block集合
+                section.setBlocks(blocks);
+                sections.add(section);
+            }
+        }
+        body.setSections(sections);
+        return body;
+    }
+
+    private List<Block> disposeQuestionBodyOrOption(String questionRow) {
+        List<Block> blocks = new ArrayList<>();
+        // 去掉每行里面的<p>,<span>,</span>标签
+        questionRow = questionRow.replaceAll("<p>", "").replaceAll("</p>", "").replaceAll("<span>", "")
+                .replaceAll("</span>", "").replaceAll("</a>", "");
+        String[] questionRowStrings = questionRow.split("<|/>|>");
+        for (int i = 0; i < questionRowStrings.length; i++) {
+            Block block = new Block();
+            String rowStr = questionRowStrings[i];
+            // 判断是否有图片
+            if (rowStr.startsWith("img")) {
+                rowStr = "<" + rowStr + ">";
+                Map<String, Object> param = new HashMap<>();
+                // 需要继续做截取,取到Parma
+                block.setType("image");
+                // 获取图片的路径
+                List<String> strSrcList = getImg(rowStr, "src");
+                if (strSrcList.size() > 0) {
+                    String strSrc = strSrcList.get(0).replaceAll("src=\"", "").replaceAll("\"", "");
+                    block.setValue(strSrc);
+                }
+                // 获取图片的高度
+                List<String> strHeightList = getImg(rowStr, "height");
+                if (strHeightList.size() > 0) {
+                    String strHeight = strHeightList.get(0).replaceAll("height=\"", "").replaceAll("\"", "");
+                    param.put("height", strHeight);
+                }
+                // 获取图片的宽度
+                List<String> strWidthList = getImg(rowStr, "width");
+                if (strHeightList.size() > 0) {
+                    String strWidth = strWidthList.get(0).replaceAll("width=\"", "").replaceAll("\"", "");
+                    param.put("width", strWidth);
+                }
+                block.setParam(param);
+                blocks.add(block);
+            } else if (rowStr.startsWith("a") && rowStr.contains("id") && rowStr.contains("name")) { // 处理音频
+                rowStr = "<" + rowStr + ">";
+                block.setPlayTime(1);
+                block.setType("audio");
+                block.setValue(this.getAttrValue(rowStr, "id"));
+                blocks.add(block);
+            } else {
+                block.setType("text");
+                rowStr = rowStr.replace("&nbsp;", "");// 消除空格
+                rowStr = rowStr.replace("&quot;", "\"");// 将&quot;转换成\"
+                rowStr = rowStr.replace("&lt;", "<");// 将&lt;转换成<
+                rowStr = rowStr.replace("&gt;", ">");// 将&gt;转换成>
+                rowStr = rowStr.replace("&amp;", "&");// 将&amp;转换成&
+                if (org.apache.commons.lang3.StringUtils.isNotBlank(rowStr)) {
+                    block.setValue(rowStr);
+                    blocks.add(block);
+                }
+            }
+        }
+
+        return blocks;
+    }
+
+    /**
+     * 获取图片里面的路径,长度,宽度
+     */
+    private List<String> getImg(String s, String str) {
+        String regex;
+        List<String> list = new ArrayList<>();
+        regex = str + "=\"(.*?)\"";
+        Pattern pa = Pattern.compile(regex, Pattern.DOTALL);
+        Matcher ma = pa.matcher(s);
+        while (ma.find()) {
+            list.add(ma.group());
+        }
+        return list;
+    }
+
+    private String getAttrValue(String questionStr, String attrName) {
+        Pattern aPattern = Pattern.compile("a.*");
+        Matcher aMatcher = aPattern.matcher(questionStr);
+
+        if (aMatcher.find()) {
+            String idRegex = attrName + "=\".*?\"";
+            Pattern idPattern = Pattern.compile(idRegex);
+            Matcher idMatcher = idPattern.matcher(aMatcher.group());
+            if (idMatcher.find()) {
+                return idMatcher.group()
+                        .replaceAll(attrName + "=\"", "")
+                        .replaceAll("\"", "");
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * 清空临时目录中的文件
+     *
+     * @param tempAnswerDir
+     */
+    private void clearTempAnswerFile(String tempAnswerDir) {
+        File t1 = new File(tempAnswerDir);
+        for (File f : t1.listFiles()) {
+            FileUtils.deleteQuietly(f);
+        }
+    }
+
+    private String testJson() {
+        return "{\n" +
+                "    \"cost\": 6317,\n" +
+                "    \"nextExamRecordDataId\": 80703,\n" +
+                "    \"subjectiveAnswerList\": [{\n" +
+                "        \"examId\": 198,\n" +
+                "        \"courseCode\": \"6000459\",\n" +
+                "        \"examStudentId\": 1837550,\n" +
+                "        \"examRecordDataId\": 80507,\n" +
+                "        \"mainNumber\": 5,\n" +
+                "        \"questionId\": \"5c45384def8fce45257834d8\",\n" +
+                "        \"order\": 19,\n" +
+                "        \"studentAnswer\": \"https://ecs-test-static.qmth.com.cn/oe-answer-file/1837550/80507/19/1837550_80507_19_1558433562519.jpg|https://ecs-test-static.qmth.com.cn/oe-answer-file/1837550/80507/19/1837550_80507_19_1558433889599.png\",\n" +
+                "        \"answerType\": \"SINGLE_AUDIO\",\n" +
+                "        \"realAnswerType\": \"audio\"\n" +
+                "    }, {\n" +
+                "        \"examId\": 198,\n" +
+                "        \"courseCode\": \"6000459\",\n" +
+                "        \"examStudentId\": 1837550,\n" +
+                "        \"examRecordDataId\": 80507,\n" +
+                "        \"mainNumber\": 5,\n" +
+                "        \"questionId\": \"5c45384def8fce45257834d9\",\n" +
+                "        \"order\": 20,\n" +
+                "        \"studentAnswer\": null,\n" +
+                "        \"answerType\": \"SINGLE_AUDIO\",\n" +
+                "        \"realAnswerType\": \"audio\"\n" +
+                "    }, {\n" +
+                "        \"examId\": 198,\n" +
+                "        \"courseCode\": \"6000459\",\n" +
+                "        \"examStudentId\": 1837550,\n" +
+                "        \"examRecordDataId\": 80507,\n" +
+                "        \"mainNumber\": 6,\n" +
+                "        \"questionId\": \"5c45384def8fce45257834da\",\n" +
+                "        \"order\": 23,\n" +
+                "        \"studentAnswer\": null,\n" +
+                "        \"answerType\": null,\n" +
+                "        \"realAnswerType\": \"text\"\n" +
+                "    }]\n" +
+                "}";
+    }
+}

+ 7 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/cloudmarking/package-info.java

@@ -0,0 +1,7 @@
+package cn.com.qmth.examcloud.bridge.modules.cloudmarking;
+/**
+ * @Description 云阅卷专用
+ * @Author lideyin
+ * @Date 2019/11/21 14:50
+ * @Version 1.0
+ */

+ 50 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/school1/controller/School1Controller.java

@@ -0,0 +1,50 @@
+package cn.com.qmth.examcloud.bridge.modules.school1.controller;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.RandomUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.google.common.collect.Maps;
+
+import cn.com.qmth.examcloud.commons.util.DateUtil;
+import cn.com.qmth.examcloud.commons.util.Util;
+
+@RestController
+@RequestMapping("school1")
+public class School1Controller {
+
+	/**
+	 * 测试
+	 *
+	 * @author WANGWEI
+	 * @param bug
+	 * @param elapsedTime
+	 * @return
+	 */
+	@RequestMapping(value = {"/", ""}, method = RequestMethod.GET)
+	public ResponseEntity<?> get(@RequestParam(required = false) Boolean bug,
+			@RequestParam(required = false) Integer elapsedTime) {
+		if (null != bug && bug) {
+			if (RandomUtils.nextBoolean()) {
+				throw new RuntimeException("Ha Ha Ha ... ...");
+			}
+		}
+
+		if (null != elapsedTime && 0 < elapsedTime && elapsedTime < 10000) {
+			Util.sleep(TimeUnit.MILLISECONDS, elapsedTime);
+		}
+		Map<String, Object> map = Maps.newHashMap();
+		map.put("date", DateUtil.chinaNow());
+		map.put("author", "qmth");
+		ResponseEntity<Map<String, Object>> entity = ResponseEntity.status(HttpStatus.OK).body(map);
+		return entity;
+	}
+
+}

+ 6 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/school1/package-info.java

@@ -0,0 +1,6 @@
+package cn.com.qmth.examcloud.bridge.modules.school1;
+
+/**
+ * XXXX.qmth.com.cn<br>
+ * 学校1
+ */

+ 5 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/school1/service/School1Service.java

@@ -0,0 +1,5 @@
+package cn.com.qmth.examcloud.bridge.modules.school1.service;
+
+public interface School1Service {
+
+}

+ 6 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/school2/package-info.java

@@ -0,0 +1,6 @@
+package cn.com.qmth.examcloud.bridge.modules.school2;
+
+/**
+ * XXXX.qmth.com.cn<br>
+ * 学校2
+ */

+ 168 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/swjtu/controller/SwjtuController.java

@@ -0,0 +1,168 @@
+package cn.com.qmth.examcloud.bridge.modules.swjtu.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.google.common.collect.Maps;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.BooleanUtil;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.StringUtil;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamScoreDataCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ScoreDataBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetFinalScoreDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetFinalScoreDataResp;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.OrgPropertyCacheBean;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+
+@RestController
+@RequestMapping("swjtu")
+public class SwjtuController {
+
+	@Autowired
+	ExamCloudService examCloudService;
+
+	@Autowired
+	ExamScoreDataCloudService examScoreDataCloudService;
+
+	@PostMapping("getScore")
+	public ResponseEntity<?> getScore(HttpServletRequest request, @RequestParam String studentCode,
+			@RequestParam(required = false) Long examId, @RequestParam String subjectCode,
+			@RequestParam(required = false, defaultValue = "true") boolean encrypt,
+			@RequestParam(required = false, defaultValue = "false") boolean detail) {
+
+		String authInfo = request.getHeader("auth-info");
+		Map<String, String> infoMap = buildInfoMap(authInfo);
+		String loginname = infoMap.get("loginname");
+		String password = infoMap.get("password");
+
+		String rootOrgIdString = PropertyHolder.getString("swjtu.rootOrgId");
+		String loginnameConf = PropertyHolder.getString("swjtu.loginName");
+		String passwordConf = PropertyHolder.getString("swjtu.password");
+
+		if (StringUtils.isBlank(rootOrgIdString)) {
+			throw new StatusException("001001", "rootOrgId is not configured");
+		}
+		if (StringUtils.isBlank(loginnameConf)) {
+			throw new StatusException("001002", "loginname is not configured");
+		}
+		if (StringUtils.isBlank(passwordConf)) {
+			throw new StatusException("001003", "password is not configured");
+		}
+
+		Long rootOrgId = null;
+		try {
+			rootOrgId = StringUtil.toLong(rootOrgIdString);
+		} catch (Exception e1) {
+			throw new StatusException("001004", "rootOrgId is not Long value");
+		}
+
+		if (null == examId) {
+			OrgPropertyCacheBean orgPropertyCacheBean = CacheHelper.getOrgProperty(rootOrgId,
+					"THIRD_PARTY_API_DEFAULT_EXAM_ID");
+			String value = orgPropertyCacheBean.getValue();
+			if (StringUtils.isBlank(value)) {
+				throw new StatusException("001005", "examId is not configured");
+			}
+			examId = StringUtil.toLong(value);
+		}
+
+		if (2 != BooleanUtil.countTrue(loginnameConf.equals(loginname),
+				passwordConf.equals(password))) {
+			ResponseEntity<?> entity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+			return entity;
+		}
+
+		Map<String, Object> map = Maps.newHashMap();
+
+		try {
+			GetFinalScoreDataReq gfsdReq = new GetFinalScoreDataReq();
+			gfsdReq.setCourseCode(subjectCode);
+			gfsdReq.setExamId(examId);
+			gfsdReq.setRootOrgId(rootOrgId);
+			gfsdReq.setStudentCode(studentCode);
+
+			GetFinalScoreDataResp gfsdReqResp = examScoreDataCloudService
+					.getFinalScoreData(gfsdReq);
+			ScoreDataBean scoreDataBean = gfsdReqResp.getScoreDataBean();
+
+			if (null == scoreDataBean) {
+				map.put("exist", false);
+			} else {
+				map.put("exist", true);
+				map.put("totalScore", String.valueOf(scoreDataBean.getTotalScore()));
+				map.put("examId", String.valueOf(scoreDataBean.getExamId()));
+				map.put("studentCode", scoreDataBean.getStudentCode());
+			}
+
+		} catch (Exception e) {
+			map.put("exception", "500");
+		}
+		String result = JsonUtil.toJson(map);
+		if (encrypt) {
+			result = encrypt(JsonUtil.toJson(result));
+		}
+
+		ResponseEntity<String> entity = ResponseEntity.status(HttpStatus.OK).body(result);
+		return entity;
+	}
+
+	private static final String ENTRY_SPLITE = ";";
+
+	private static final String KV_SPLITE = "=";
+
+	private Map<String, String> buildInfoMap(String infoString) {
+		Map<String, String> infoMap = new HashMap<String, String>();
+		String[] values = StringUtils.split(infoString, ENTRY_SPLITE);
+		if (values != null && values.length > 0) {
+			for (String value : values) {
+				String[] pair = StringUtils.split(value, KV_SPLITE);
+				if (pair != null && pair.length == 2) {
+					infoMap.put(pair[0].trim(), pair[1].trim());
+				}
+			}
+		}
+		return infoMap;
+	}
+
+	@SuppressWarnings("restriction")
+	public static String byte2base64(byte[] b) {
+		return new sun.misc.BASE64Encoder().encode(b);
+	}
+
+	private static final String AES = "AES";
+
+	private static final String CRYPT_KEY = "ZwFOcwz6QtIQ983m";
+
+	public final static String encrypt(String data) {
+		try {
+			return byte2base64(encrypt(data.getBytes("ISO-8859-1"), CRYPT_KEY));
+		} catch (Exception e) {
+		}
+		return null;
+	}
+
+	public static byte[] encrypt(byte[] src, String key) throws Exception {
+		Cipher cipher = Cipher.getInstance(AES);
+		SecretKeySpec securekey = new SecretKeySpec(key.getBytes(), AES);
+		cipher.init(Cipher.ENCRYPT_MODE, securekey);// 设置密钥和加密形式
+		return cipher.doFinal(src);
+	}
+
+}

+ 6 - 0
src/main/java/cn/com/qmth/examcloud/bridge/modules/swjtu/package-info.java

@@ -0,0 +1,6 @@
+package cn.com.qmth.examcloud.bridge.modules.swjtu;
+
+/**
+ * swjtu.ecs.qmth.com.cn<br>
+ * 西南交通大学
+ */

+ 24 - 0
src/main/java/cn/com/qmth/examcloud/bridge/开发说明.txt

@@ -0,0 +1,24 @@
+bridge服务主要用于定制化服务方案与云平台对接
+
+不同的第三方系统的定制化服务方案涉及的代码按规则放在固定的package,方便管理
+如: 
+school1的定制化服务方案涉及的代码放在cn.com.qmth.examcloud.bridge.modules.school1下
+school2的定制化服务方案涉及的代码放在cn.com.qmth.examcloud.bridge.modules.school2下
+
+本服务开发说明:
+
+1. 测试
+    测试类cn.com.qmth.examcloud.bridge.Tester
+
+2. 该服务涉及多个第三方系统的定制化方案,尽量保持工程结构的纯洁性. 如: 
+    尽量不使用第三方SDK,而是以httpclient来实现请求.
+    尽量不使用Web Service客户端代码,而是以httpclient来实现SOAP请求.
+
+3. 涉及连接关系型数据库
+    由于数据源连接池数量不可预估,为保持新增数据源的便捷性,降低操作的复杂度
+    关系型数据库使用 cn.com.qmth.examcloud.commons.util.DBUtil.BridgeDatabaseUtil 获取数据库连接.
+    避免使用ORM框架,springboot JPA或spring JdbcTemplate等方案.
+    
+4. 关于接口鉴权
+     由于接口鉴权方案千奇百怪,接口鉴权应该在请求处理方法上添加鉴权判断,避免使用拦截器或AOP处理.  
+

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

@@ -0,0 +1,8 @@
+spring.profiles.active=dev
+
+examcloud.startup.startupCode=8020
+examcloud.startup.configCenterHost=127.0.0.1
+examcloud.startup.configCenterPort=9999
+examcloud.startup.appCode=BRG
+
+

+ 1 - 0
src/main/resources/classpath.location

@@ -0,0 +1 @@
+classpath 定位文件

+ 0 - 0
src/main/resources/limited.properties


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

@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN" monitorInterval="30">
+
+	<Properties>
+		<Property name="commonLevel" value="${sys:log.commonLevel}" />
+	</Properties>
+
+	<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_APPENDER" 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>
+				<TimeBasedTriggeringPolicy interval="1" />
+				<SizeBasedTriggeringPolicy size="100 MB" />
+			</Policies>
+			<DefaultRolloverStrategy max="10000">
+				<Delete basePath="./logs/debug" maxDepth="1">
+					<IfFileName glob="debug-*.log">
+						<IfAccumulatedFileSize exceeds="2 GB" />
+					</IfFileName>
+				</Delete>
+			</DefaultRolloverStrategy>
+		</RollingFile>
+		<!-- 接口日志 -->
+		<RollingFile name="INTERFACE_APPENDER" fileName="./logs/interface/interface.log"
+			filePattern="./logs/interface/interface-%d{yyyy.MM.dd.HH}-%i.log">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m%n" />
+			<Policies>
+				<TimeBasedTriggeringPolicy interval="1" />
+				<SizeBasedTriggeringPolicy size="100 MB" />
+			</Policies>
+			<DefaultRolloverStrategy max="10000">
+				<Delete basePath="./logs/interface" maxDepth="1">
+					<IfFileName glob="interface-*.log">
+						<IfAccumulatedFileSize exceeds="10 GB" />
+					</IfFileName>
+				</Delete>
+			</DefaultRolloverStrategy>
+		</RollingFile>
+	</Appenders>
+
+	<Loggers>
+		<Logger name="cn.com.qmth" level="${commonLevel}" additivity="false">
+			<AppenderRef ref="DEBUG_APPENDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+		<Logger name="INTERFACE_LOGGER" level="INFO" additivity="false">
+			<AppenderRef ref="INTERFACE_APPENDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+		<Logger name="cn.com.qmth.examcloud.web.actuator" level="ERROR" />
+
+		<Root level="INFO">
+			<AppenderRef ref="Console" />
+			<AppenderRef ref="DEBUG_APPENDER" />
+		</Root>
+	</Loggers>
+
+</Configuration>