Pārlūkot izejas kodu

云阅卷接口部分代码重构

lideyin 5 gadi atpakaļ
vecāks
revīzija
cb111bfea6

+ 53 - 0
src/main/java/cn/com/qmth/examcloud/support/handler/richText/AudioTextHandler.java

@@ -0,0 +1,53 @@
+package cn.com.qmth.examcloud.support.handler.richText;
+
+import cn.com.qmth.examcloud.support.handler.richText.bean.BlockBean;
+import cn.com.qmth.examcloud.support.handler.richText.bean.SectionBean;
+import cn.com.qmth.examcloud.support.handler.richText.bean.SectionCollectionBean;
+import com.mysql.cj.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Description 音频处理器类
+ * @Author lideyin
+ * @Date 2020/5/15 16:29
+ * @Version 1.0
+ */
+public class AudioTextHandler implements RichTextHandler{
+    @Override
+    public SectionCollectionBean handle(String richText) {
+        SectionCollectionBean sectionCollection = new SectionCollectionBean();
+        List<SectionBean> sectionList = new ArrayList<>();
+        SectionBean section = new SectionBean();
+        List<BlockBean> blockList = new ArrayList<>();
+
+        if (validateAudioAnswer(richText)) {
+            BlockBean blockBean = new BlockBean();
+            blockBean.setType("audio");
+            blockBean.setValue(richText);
+            blockList.add(blockBean);
+        }
+
+        section.setBlocks(blockList);
+        sectionList.add(section);
+        sectionCollection.setSections(sectionList);
+
+        return sectionCollection;
+    }
+
+    /**
+     * 校验音频答案格式是否正确
+     *
+     * @param studentAnswer
+     * @return
+     */
+    private boolean validateAudioAnswer(String studentAnswer) {
+        if (StringUtils.isNullOrEmpty(studentAnswer)) {
+            return false;
+        }
+
+        String regExp = "^(ftp|https?)\\:\\/\\/([\\w\\_\\-]+)\\.([\\w\\-]+[\\.]?)*[\\w]+\\.[a-zA-Z]{2,10}(.*)\\.(mp3)";
+        return studentAnswer.matches(regExp);
+    }
+}

+ 135 - 0
src/main/java/cn/com/qmth/examcloud/support/handler/richText/ComplexTextHandler.java

@@ -0,0 +1,135 @@
+package cn.com.qmth.examcloud.support.handler.richText;
+
+import cn.com.qmth.examcloud.support.handler.richText.bean.BlockBean;
+import cn.com.qmth.examcloud.support.handler.richText.bean.SectionBean;
+import cn.com.qmth.examcloud.support.handler.richText.bean.SectionCollectionBean;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @Description 复合文本处理器类
+ * @Author lideyin
+ * @Date 2020/5/15 16:29
+ * @Version 1.0
+ */
+public class ComplexTextHandler implements RichTextHandler {
+    @Override
+    public SectionCollectionBean handle(String richText) {
+        SectionCollectionBean body = new SectionCollectionBean();
+        List<SectionBean> sections = new ArrayList<>();
+        // 得到小题题干或者答案行数
+        if (org.apache.commons.lang3.StringUtils.isBlank(richText)) {
+            return body;
+        }
+
+        String[] questionRowStrings = richText.split("</p>");
+        for (int i = 0; i < questionRowStrings.length; i++) {
+            List<BlockBean> blocks = disposeQuestionBodyOrOption(questionRowStrings[i]);
+            if (blocks != null && blocks.size() > 0) {
+                SectionBean section = new SectionBean();
+                // 将小题题干拆分为Block集合
+                section.setBlocks(blocks);
+                sections.add(section);
+            }
+        }
+        body.setSections(sections);
+        return body;
+    }
+
+
+    private List<BlockBean> disposeQuestionBodyOrOption(String questionRow) {
+        List<BlockBean> 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++) {
+            BlockBean block = new BlockBean();
+            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, "url"));
+                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 "";
+    }
+}

+ 163 - 0
src/main/java/cn/com/qmth/examcloud/support/handler/richText/HtmlTextHandler.java

@@ -0,0 +1,163 @@
+package cn.com.qmth.examcloud.support.handler.richText;
+
+import cn.com.qmth.examcloud.commons.util.RegExpUtil;
+import cn.com.qmth.examcloud.support.handler.richText.bean.BlockBean;
+import cn.com.qmth.examcloud.support.handler.richText.bean.SectionBean;
+import cn.com.qmth.examcloud.support.handler.richText.bean.SectionCollectionBean;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+import com.mysql.cj.util.StringUtils;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.safety.Whitelist;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description html类型的文本处理器
+ * @Author lideyin
+ * @Date 2020/5/15 16:28
+ * @Version 1.0
+ */
+public class HtmlTextHandler implements RichTextHandler {
+    private static Map<String, String> tagMap;
+    private static String DEFAULT_RETAIN_TAG="b,u,i,sup,sub";
+
+    static {
+        tagMap = new HashMap<>();
+        tagMap.put("b", "bold");
+        tagMap.put("u", "underline");
+        tagMap.put("i", "italic");
+        tagMap.put("sup", "sup");
+        tagMap.put("sub", "sub");
+    }
+
+    @Override
+    public SectionCollectionBean handle(String richText) {
+        /*过滤不需要的html标签*/
+        String retainTags = PropertyHolder.getString("cloudMarking.interface.retainTags", DEFAULT_RETAIN_TAG);
+
+        String[] specialRetainTagArr = (retainTags+",br").split(",");
+        richText = Jsoup.clean(richText, new Whitelist().addTags(specialRetainTagArr));
+
+        richText = richText.replace("<br>", "\n");
+
+        SectionCollectionBean sectionCollection = new SectionCollectionBean();
+        List<SectionBean> sectionList = new ArrayList<>();
+        String[] retainTagArr = retainTags.split(",");
+        //有多少个换行,则生成多少个section
+        String[] paragraphArray = richText.split("\n");
+        for (int i = 0; i < paragraphArray.length; i++) {
+            String p = paragraphArray[i];
+            SectionBean section = new SectionBean();
+
+            List<BlockBean> blockList = new ArrayList<>();
+            getSplitList(p, blockList,retainTagArr);
+
+            section.setBlocks(blockList);
+            sectionList.add(section);
+        }
+
+        sectionCollection.setSections(sectionList);
+
+        //html实体符号编码解析
+
+        return sectionCollection;
+    }
+
+    private static List<BlockBean> getSplitList(String str, List<BlockBean> resultList,final String[] retainTagArr) {
+        if (StringUtils.isNullOrEmpty(str)) {
+            return null;
+        }
+
+        String regPattern = "";
+        for (int i = 0; i < retainTagArr.length; i++) {
+            String tag = retainTagArr[i];
+            if (i != 0) {
+                regPattern += "|";
+            }
+            regPattern += String.format("<%s>;\\S*<\\/%s>", tag, tag);
+        }
+
+
+        //找到第一个匹配项
+        String firstMatch = RegExpUtil.find(str, regPattern);
+
+        //如果未找到任何匹配项,则直接返回原字符串
+        if (StringUtils.isNullOrEmpty(firstMatch)) {
+            resultList.add(buildNormalTextAnswer(str));
+            return resultList;
+        }
+
+        //查找到的结果的索引
+        int matchIndex = str.indexOf(firstMatch);
+
+        //如果字符串以特殊标签开头的场景特殊处理
+        if (matchIndex == 0) {
+            //如果字符串中只有特殊标签,则直接返回处理过的数据
+            if (firstMatch.length() == str.length()) {
+                resultList.add(buildSpecialTextAnswer(Jsoup.clean(firstMatch, Whitelist.none()), getTag(firstMatch)));
+                return resultList;
+            }
+
+            //如果字符串以特殊标签开头,且还有其它内容
+            //首先,将特殊标签加入返回集合
+            resultList.add(buildSpecialTextAnswer(Jsoup.clean(firstMatch, Whitelist.none()), getTag(firstMatch)));
+            //其次,将剩下的字符串递归继续处理
+            str = str.substring(firstMatch.length());
+            getSplitList(str, resultList,retainTagArr);
+        }
+
+        //如果字符串不以特殊标签开头,且还有其它内容
+        String value = str.substring(0, matchIndex);
+        //首先,将前面的普通文本添加到结果集
+        resultList.add(buildNormalTextAnswer(value));
+        //其次,将特殊结果添加到结果集
+        resultList.add(buildSpecialTextAnswer(Jsoup.clean(firstMatch, Whitelist.none()), getTag(firstMatch)));
+        //剩下部分的字符串,继续递归处理
+        str = str.substring(matchIndex + firstMatch.length());
+        getSplitList(str, resultList,retainTagArr);
+
+        return resultList;
+    }
+
+    //构建普通的文本作答
+    private static BlockBean buildNormalTextAnswer(String value) {
+        BlockBean obb = new BlockBean();
+        obb.setType("text");
+        obb.setValue(StringEscapeUtils.unescapeHtml(value));
+
+        return obb;
+    }
+
+    //构建带特殊标签的文本作答
+    private static BlockBean buildSpecialTextAnswer(String value, String tag) {
+        BlockBean obb = new BlockBean();
+        obb.setType("text");
+        obb.setValue(StringEscapeUtils.unescapeHtml(value));
+
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put(tag, true);
+        obb.setParam(paramMap);
+
+        return obb;
+    }
+
+    private static String getTag(String target) {
+        if (StringUtils.isNullOrEmpty(target)) {
+            return null;
+        }
+
+        for (String s : tagMap.keySet()) {
+            if (target.indexOf(String.format("<%s>", s)) != -1) {
+                return tagMap.get(s);
+            }
+        }
+
+        return null;
+    }
+
+
+}

+ 148 - 0
src/main/java/cn/com/qmth/examcloud/support/handler/richText/ImageTextHandler.java

@@ -0,0 +1,148 @@
+package cn.com.qmth.examcloud.support.handler.richText;
+
+import cn.com.qmth.examcloud.commons.util.RegExpUtil;
+import cn.com.qmth.examcloud.support.handler.richText.bean.BlockBean;
+import cn.com.qmth.examcloud.support.handler.richText.bean.SectionBean;
+import cn.com.qmth.examcloud.support.handler.richText.bean.SectionCollectionBean;
+import com.mysql.cj.util.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.safety.Whitelist;
+import org.jsoup.select.Elements;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description 图片类型的文本处理器
+ * @Author lideyin
+ * @Date 2020/5/15 16:28
+ * @Version 1.0
+ */
+public class ImageTextHandler implements RichTextHandler {
+    @Override
+    public SectionCollectionBean handle(String richText) {
+        SectionCollectionBean sectionCollection = new SectionCollectionBean();
+        List<SectionBean> sectionList = new ArrayList<>();
+        SectionBean section = new SectionBean();
+        List<BlockBean> blockList = new ArrayList<>();
+
+        //图片文本部分
+        String txtStr = getImgTxt(richText);
+        if (!StringUtils.isNullOrEmpty(txtStr)) {
+            BlockBean blockBean = new BlockBean();
+            blockBean.setType("text");
+            blockBean.setValue(txtStr);
+            blockList.add(blockBean);
+        }
+
+        //图片部分
+        if (validateImgAnswer(richText)) {
+            String imgSrc = getImgSrc(richText);
+            if (imgSrc.indexOf("|") > -1) {
+                String[] imgAnswers = imgSrc.split("\\|");
+
+                for (int i = 0; i < imgAnswers.length; i++) {
+                    BlockBean blockBean = new BlockBean();
+                    blockBean.setType("text");
+                    blockBean.setValue(getPureUrl(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]));
+                        blockBean.setParam(paramMap);
+                    }
+
+                    blockList.add(blockBean);
+                }
+            }
+        }
+
+        section.setBlocks(blockList);
+        sectionList.add(section);
+        sectionCollection.setSections(sectionList);
+
+        return sectionCollection;
+
+    }
+
+    /**
+     * 获取图片作答的文本部分
+     *
+     * @param studentAnswer
+     * @return
+     */
+    private String getImgTxt(String studentAnswer) {
+        if (!StringUtils.isNullOrEmpty(studentAnswer)) {
+            return Jsoup.clean(studentAnswer, Whitelist.none());
+        }
+
+        return null;
+    }
+
+    /**
+     * 校验图片答案格式是否正确
+     *
+     * @param studentAnswer
+     * @return
+     */
+    private boolean validateImgAnswer(String studentAnswer) {
+        if (StringUtils.isNullOrEmpty(studentAnswer)) {
+            return false;
+        }
+
+        String regExp = "[\\s\\S]*(ftp|https?)\\:\\/\\/([\\w\\_\\-]+)\\.([\\w\\-]+[\\.]?)*[\\w]+\\.[a-zA-Z]{2,10}(.*)\\.(png|jpg|gif|jpeg).*[\\s\\S]*";
+        return studentAnswer.matches(regExp);
+    }
+
+    /**
+     * 获取图片作答图片路径
+     *
+     * @param studentAnswer
+     * @return
+     */
+    private String getImgSrc(String studentAnswer) {
+        if (StringUtils.isNullOrEmpty(studentAnswer)) {
+            return studentAnswer;
+        }
+
+        //图片题特殊处理(因为图片作答题中有html标签,只需要取url即可)
+        Document doc = Jsoup.parse(studentAnswer);
+        Elements imgElements = doc.select("img[src]");
+
+        String imgSrc = "";
+
+        for (Element el : imgElements) {
+            String src = el.attr("src");
+            if (!StringUtils.isNullOrEmpty(src)) {
+                imgSrc += src + "|";
+            }
+        }
+        if (!StringUtils.isNullOrEmpty(imgSrc)) {
+            return imgSrc.substring(0, imgSrc.length() - 1);
+        }
+
+        return imgSrc;
+    }
+
+    /**
+     * 获取图片url中不带参数部分,去除地址中的多余后缀
+     *
+     * @param imgUrl 图片地址
+     * @return
+     */
+    private String getPureUrl(String imgUrl) {
+        if (StringUtils.isNullOrEmpty(imgUrl)) {
+            return "";
+        }
+
+        String regExp = "(ftp|https?)\\:\\/\\/([\\w\\_\\-]+)\\.([\\w\\-]+[\\.]?)*[\\w]+\\.[a-zA-Z]{2,10}(.*)\\.(png|jpg|gif|jpeg)";
+        return RegExpUtil.find(imgUrl, regExp);
+    }
+}

+ 13 - 0
src/main/java/cn/com/qmth/examcloud/support/handler/richText/RichTextHandler.java

@@ -0,0 +1,13 @@
+package cn.com.qmth.examcloud.support.handler.richText;
+
+import cn.com.qmth.examcloud.support.handler.richText.bean.SectionCollectionBean;
+
+/**
+ * @Description 富文本处理器接口
+ * @Author lideyin
+ * @Date 2020/5/15 17:00
+ * @Version 1.0
+ */
+public interface RichTextHandler {
+    SectionCollectionBean handle(String richText);
+}

+ 41 - 0
src/main/java/cn/com/qmth/examcloud/support/handler/richText/RichTextHandlerFactory.java

@@ -0,0 +1,41 @@
+package cn.com.qmth.examcloud.support.handler.richText;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import com.mysql.cj.util.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @Description 富文本处理器工厂类
+ * @Author lideyin
+ * @Date 2020/5/15 16:33
+ * @Version 1.0
+ */
+public class RichTextHandlerFactory {
+    private static final Map<String,RichTextHandler> handlerMap=new HashMap<>();
+
+    static {
+        handlerMap.put("text",new HtmlTextHandler());
+        handlerMap.put("image",new ImageTextHandler());
+        handlerMap.put("audio",new AudioTextHandler());
+        handlerMap.put("complex",new ComplexTextHandler());
+    }
+
+    /**
+     * 根据类型获取实例
+     * @param type 文本类型
+     * @return
+     */
+    public static RichTextHandler getHandler(String type) {
+        if (StringUtils.isNullOrEmpty(type)) {
+            throw new StatusException("900101","参数type不允许为空");
+        }
+
+        if (!handlerMap.containsKey(type)) {
+            throw new StatusException("900102","不支持的文本类型");
+        }
+
+        return handlerMap.get(type);
+    }
+}

+ 63 - 0
src/main/java/cn/com/qmth/examcloud/support/handler/richText/bean/BlockBean.java

@@ -0,0 +1,63 @@
+package cn.com.qmth.examcloud.support.handler.richText.bean;
+
+import java.util.Map;
+
+/**
+ * @Description 块
+ * @Author lideyin
+ * @Date 2020/3/2 18:35
+ * @Version 1.0
+ */
+public class BlockBean {
+    /**
+     * 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/support/handler/richText/bean/SectionBean.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.support.handler.richText.bean;
+
+import java.util.List;
+
+/**
+ * @Description 节点
+ * @Author lideyin
+ * @Date 2020/3/30 17:00
+ * @Version 1.0
+ */
+public class SectionBean {
+
+    private List<BlockBean> blocks;
+
+    public List<BlockBean> getBlocks() {
+        return blocks;
+    }
+
+    public void setBlocks(List<BlockBean> blocks) {
+        this.blocks = blocks;
+    }
+
+
+}
+

+ 25 - 0
src/main/java/cn/com/qmth/examcloud/support/handler/richText/bean/SectionCollectionBean.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.support.handler.richText.bean;
+
+import java.util.List;
+
+/**
+ * @Description 节点集合
+ * @Author lideyin
+ * @Date 2020/3/30 16:59
+ * @Version 1.0
+ */
+public class SectionCollectionBean {
+
+    private List<SectionBean> sections;
+
+    public List<SectionBean> getSections() {
+        return sections;
+    }
+
+    public void setSections(List<SectionBean> sections) {
+        this.sections = sections;
+    }
+
+
+}
+