();
+ }
+
+ /**
+ * Emulate GNU diff's format.
+ * Header: @@ -382,8 +481,9 @@
+ * Indices are printed as 1-based, not 0-based.
+ *
+ * @return The GNU diff string.
+ */
+ public String toString() {
+ String coords1, coords2;
+ if (this.length1 == 0) {
+ coords1 = this.start1 + ",0";
+ } else if (this.length1 == 1) {
+ coords1 = Integer.toString(this.start1 + 1);
+ } else {
+ coords1 = (this.start1 + 1) + "," + this.length1;
+ }
+ if (this.length2 == 0) {
+ coords2 = this.start2 + ",0";
+ } else if (this.length2 == 1) {
+ coords2 = Integer.toString(this.start2 + 1);
+ } else {
+ coords2 = (this.start2 + 1) + "," + this.length2;
+ }
+ StringBuilder text = new StringBuilder();
+ text.append("@@ -").append(coords1).append(" +").append(coords2)
+ .append(" @@\n");
+ // Escape the body of the patch with %xx notation.
+ for (Diff aDiff : this.diffs) {
+ switch (aDiff.operation) {
+ case INSERT:
+ text.append('+');
+ break;
+ case DELETE:
+ text.append('-');
+ break;
+ case EQUAL:
+ text.append(' ');
+ break;
+ }
+ try {
+ text.append(URLEncoder.encode(aDiff.text, "UTF-8").replace('+', ' '))
+ .append("\n");
+ } catch (UnsupportedEncodingException e) {
+ // Not likely on modern system.
+ throw new Error("This system does not support UTF-8.", e);
+ }
+ }
+ return unescapeForEncodeUriCompatability(text.toString());
+ }
+ }
+
+ /**
+ * Unescape selected chars for compatability with JavaScript's encodeURI.
+ * In speed critical applications this could be dropped since the
+ * receiving application will certainly decode these fine.
+ * Note that this function is case-sensitive. Thus "%3f" would not be
+ * unescaped. But this is ok because it is only called with the output of
+ * URLEncoder.encode which returns uppercase hex.
+ *
+ * Example: "%3F" -> "?", "%24" -> "$", etc.
+ *
+ * @param str The string to escape.
+ * @return The escaped string.
+ */
+ private static String unescapeForEncodeUriCompatability(String str) {
+ return str.replace("%21", "!").replace("%7E", "~")
+ .replace("%27", "'").replace("%28", "(").replace("%29", ")")
+ .replace("%3B", ";").replace("%2F", "/").replace("%3F", "?")
+ .replace("%3A", ":").replace("%40", "@").replace("%26", "&")
+ .replace("%3D", "=").replace("%2B", "+").replace("%24", "$")
+ .replace("%2C", ",").replace("%23", "#");
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/FileParser.java b/src/main/java/org/codedream/epaper/component/datamanager/FileParser.java
new file mode 100644
index 0000000..d17ac77
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/FileParser.java
@@ -0,0 +1,101 @@
+package org.codedream.epaper.component.datamanager;
+
+import org.codedream.epaper.exception.innerservererror.RuntimeIOException;
+import org.codedream.epaper.model.file.File;
+import org.codedream.epaper.repository.file.FileRepository;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+
+/**
+ * 文件解析器
+ */
+@Component
+public class FileParser {
+
+ @Resource
+ private FileRepository fileRepository;
+
+ @Resource
+ private SHA512Encoder encoder;
+
+ /**
+ * 查找缓存
+ * @param hash 哈希值
+ * @return 文件信息对象
+ */
+ public Optional find(String hash){
+ Iterable files = fileRepository.findAllByHash(hash);
+ Iterator fileIterator = files.iterator();
+ if(!fileIterator.hasNext()) return Optional.empty();
+
+ return Optional.of(fileIterator.next());
+ }
+
+ /**
+ * 数据散列值计算
+ * @param bytesData 文件数据
+ * @return 散列值
+ */
+ public String encode(byte[] bytesData){
+ return encoder.encode(Base64.getEncoder().encodeToString(bytesData));
+ }
+
+ /**
+ * 读取文件数据
+ * @param stream 输入流
+ * @return 文件数据字节数组
+ */
+ public Optional read(InputStream stream){
+ ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
+ try {
+ // 双重数据写出
+ int readBits = 0;
+ byte[] rawBytes = new byte[1024];
+ while ((readBits = stream.read(rawBytes)) != -1) {
+ arrayOutputStream.write(rawBytes, 0, readBits);
+ }
+
+ return Optional.of(arrayOutputStream.toByteArray());
+ } catch (IOException e){
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * 将文件数据写入到文件系统
+ * @param file 文件信息对象
+ * @param bytesData 文件数据
+ * @return 文件信息对象(更新后)
+ */
+ public File writeOut(File file, byte[] bytesData){
+ String storageName = UUID.randomUUID().toString();
+
+ Path path = Paths.get(file.getPath(), storageName);
+ try {
+ Files.createFile(path);
+ OutputStream outputStream = Files.newOutputStream(path);
+ ByteArrayInputStream stream = new ByteArrayInputStream(bytesData);
+
+ // 数据写出文件系统
+ int readBits = 0;
+ byte[] rawBytes = new byte[1024];
+ while ((readBits = stream.read(rawBytes)) != -1) {
+ outputStream.write(rawBytes, 0, readBits);
+ }
+ outputStream.close();
+ stream.close();
+
+ file.setStorageName(storageName);
+ file.setSize(bytesData.length);
+ return file;
+ } catch (IOException e){
+ throw new RuntimeIOException(e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/JSONParameter.java b/src/main/java/org/codedream/epaper/component/datamanager/JSONParameter.java
new file mode 100644
index 0000000..735d229
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/JSONParameter.java
@@ -0,0 +1,120 @@
+package org.codedream.epaper.component.datamanager;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.fge.jsonpatch.JsonPatch;
+import com.github.fge.jsonpatch.JsonPatchException;
+import org.codedream.epaper.exception.innerservererror.HandlingErrorsException;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.util.Optional;
+
+/**
+ * JSON请求参数处理器
+ */
+@Component
+public class JSONParameter {
+
+ /**
+ * 获得HTTP请求内容
+ * @param request HTTP请求
+ * @return 内容字符串
+ */
+ public String getRequestBody(HttpServletRequest request){
+ try {
+ StringBuilder stringBuilder = new StringBuilder();
+ BufferedReader reader = request.getReader();
+ reader.reset();
+ String line;
+ while ((line = reader.readLine()) != null)
+ stringBuilder.append(line);
+ return stringBuilder.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 提取Request内容并解析为JSON对象
+ * @param request HTTP请求
+ * @return JSON对象
+ */
+ public Optional getJSONByRequest(HttpServletRequest request){
+ try {
+ JSONObject jsonParam = null;
+ String content = getRequestBody(request);
+ jsonParam = JSONObject.parseObject(content);
+ return Optional.ofNullable(jsonParam);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return Optional.empty();
+ }
+
+ }
+
+ /**
+ * 根据JSON对象构造JSON字符串用于返回
+ * @param json JSON对象
+ * @return JSON字符串
+ */
+ public String getJSONString(JSONObject json){
+ return json.toJSONString();
+ }
+
+ /**
+ * 根据Java对象构造JSON字符串用于返回
+ * @param object Java对象
+ * @return JSON字符串
+ */
+ public String getJSONString(Object object){
+ return JSON.toJSONString(object);
+ }
+
+ /**
+ * 由JSON对象获得对应的Java对象
+ * @param json JSON对象
+ * @param type 对应的Java对象类型
+ * @param 对应的Java对象类型
+ * @return 指定的Java对象
+ */
+ public T getJavaObject(JSONObject json, Class type){
+ return json.toJavaObject(type);
+ }
+
+ /**
+ * 由HTTP请求获得对应的Java对象(一般用于Post请求中)
+ * @param request HTTP请求
+ * @param type 对应的Java对象类型
+ * @param 对应的Java对象类型
+ * @return 指定的Java对象
+ */
+ public Optional getJavaObjectByRequest(HttpServletRequest request, Class type){
+ Optional json = getJSONByRequest(request);
+ return json.map(jsonObject -> getJavaObject(jsonObject, type));
+ }
+
+ /**
+ * 将JsonPath对象转换成Java对象(Restful API的Path动词)
+ * @param patch JsonPath对象
+ * @param object Java对象
+ * @param 对应的Java对象类型
+ * @return 指定的Java对象(更新后)
+ */
+ public T parsePathToObject(JsonPatch patch, T object){
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode patched = patch.apply(mapper.convertValue(object, JsonNode.class));
+ return (T) mapper.treeToValue(patched, object.getClass());
+ } catch (JsonPatchException | JsonProcessingException e) {
+ throw new HandlingErrorsException(e.getMessage());
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/ParagraphDivider.java b/src/main/java/org/codedream/epaper/component/datamanager/ParagraphDivider.java
new file mode 100644
index 0000000..cf69cdf
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/ParagraphDivider.java
@@ -0,0 +1,50 @@
+package org.codedream.epaper.component.datamanager;
+
+import org.codedream.epaper.configure.PunctuationConfiguration;
+import org.codedream.epaper.model.article.Paragraph;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 将一个段落分为几个句子
+ */
+@Component
+public class ParagraphDivider {
+
+ /**
+ * 将一个段落划分为若干句,并进行相应的持久化
+ *
+ * @param text 需要划分的段落文本
+ * @return 段落中的所有被划分好的句子
+ */
+ public List divideParagraph(String text) {
+
+ if (text.isEmpty()) return null;
+ Paragraph paragraph = new Paragraph();
+ paragraph.setText(text);
+ List back = PunctuationConfiguration.getBackPunctuations();
+ String[] arr = text.split("。|!|?|……|\\?|:");
+ List sentenceTexts = Arrays.asList(arr);
+
+ List sentences = new ArrayList<>();
+ Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
+ for (int i = 0; i < sentenceTexts.size(); i++) {
+ String sentenceText = sentenceTexts.get(i);
+ Matcher m = p.matcher(sentenceText);
+ int len = sentenceTexts.get(i).length();
+ if (!m.find() || len < 15 || len > 510) continue;
+ if (back.contains(sentenceTexts.get(i).charAt(0)) && sentences.size() > 1) {
+ sentences.set(i - 1, sentences.get(i - 1) + sentenceTexts.get(i).charAt(0));
+ }
+
+ sentences.add(sentenceTexts.get(i));
+ }
+
+ return sentences;
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/PdfParser.java b/src/main/java/org/codedream/epaper/component/datamanager/PdfParser.java
new file mode 100644
index 0000000..5d735e2
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/PdfParser.java
@@ -0,0 +1,94 @@
+package org.codedream.epaper.component.datamanager;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.text.PDFTextStripper;
+import org.codedream.epaper.exception.innerservererror.HandlingErrorsException;
+import org.codedream.epaper.exception.innerservererror.RuntimeIOException;
+import org.codedream.epaper.model.article.Article;
+import org.codedream.epaper.model.article.Paragraph;
+import org.codedream.epaper.model.file.File;
+import org.codedream.epaper.repository.article.ParagraphRepository;
+import org.codedream.epaper.service.IArticleService;
+import org.codedream.epaper.service.IFileService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Optional;
+
+@Component
+public class PdfParser {
+
+ @Resource
+ private IFileService fileService;
+
+ @Resource
+ private IArticleService articleService;
+
+ @Resource
+ private ParagraphRepository paragraphRepository;
+
+ @Resource
+ private SHA512Encoder encoder;
+
+ public Article parse(InputStream stream){
+
+ try {
+
+ Article article = articleService.createArticle(null);
+
+ PDDocument doc = PDDocument.load(stream);
+
+ PDFTextStripper textStripper = new PDFTextStripper();
+
+ String content = textStripper.getText(doc);
+
+ String regA = "^[\\u4e00-\\u9fa5a-zA-Z0-9。,?!;、:()“”]";
+ String regB = "[\\s|.,/?\"%$#@*^~`()+=\\-{}<>\\///_]";
+ content = content.replaceAll(regA, "");
+ content = content.replaceAll(regB, "");
+
+
+ saveParagraph(article, content);
+
+ doc.close();
+
+ return article;
+
+ } catch (IOException e) {
+ throw new RuntimeIOException(e.getMessage());
+ }
+ }
+
+ public Integer parse(Integer fileId){
+ File file = fileService.getFileInfo(fileId);
+ if(file.getType().equals("pdf")){
+ Article article = parse(fileService.getFile(fileId));
+
+ article.setFileId(fileId);
+ article = articleService.save(article);
+ return article.getId();
+ }
+ else throw new HandlingErrorsException(file.getType());
+
+ }
+
+ // 储存段结构,并考虑缓存情况
+ private void saveParagraph(Article article, String text){
+ String hash = encoder.encode(text);
+ Paragraph paragraph;
+ Optional paragraphOptional = paragraphRepository.findBySha512Hash(hash);
+ if(!paragraphOptional.isPresent()){
+ paragraph = articleService.createParagraph(text);
+ paragraph.setSha512Hash(hash);
+ paragraph = articleService.save(paragraph);
+ }
+ else{
+ paragraph = paragraphOptional.get();
+ }
+
+ articleService.addParagraph(article, paragraph);
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/ReportGenerator.java b/src/main/java/org/codedream/epaper/component/datamanager/ReportGenerator.java
new file mode 100644
index 0000000..d0488e1
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/ReportGenerator.java
@@ -0,0 +1,235 @@
+package org.codedream.epaper.component.datamanager;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.log4j.helpers.Loader;
+import org.codedream.epaper.component.article.GetSentenceFromArticle;
+import org.codedream.epaper.component.json.model.JsonableSTNResult;
+import org.codedream.epaper.component.json.model.JsonableTaskResult;
+import org.codedream.epaper.component.task.JsonableTaskResultGenerator;
+import org.codedream.epaper.exception.innerservererror.HandlingErrorsException;
+import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException;
+import org.codedream.epaper.exception.innerservererror.RuntimeIOException;
+import org.codedream.epaper.model.article.Sentence;
+import org.codedream.epaper.model.file.File;
+import org.codedream.epaper.model.task.Task;
+import org.codedream.epaper.service.FileService;
+import org.codedream.epaper.service.IFileService;
+import org.codedream.epaper.service.ITaskService;
+import org.docx4j.Docx4J;
+import org.docx4j.fonts.IdentityPlusMapper;
+import org.docx4j.fonts.Mapper;
+import org.docx4j.fonts.PhysicalFont;
+import org.docx4j.fonts.PhysicalFonts;
+import org.docx4j.openpackaging.exceptions.Docx4JException;
+import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ResourceUtils;
+
+import javax.annotation.Resource;
+import java.io.*;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+@Slf4j
+@Component
+public class ReportGenerator {
+ @Resource
+ private ITaskService taskService;
+
+ @Resource
+ private IFileService fileService;
+
+ @Resource
+ private JsonableTaskResultGenerator taskResultGenerator;
+
+ @Resource
+ private GetSentenceFromArticle getSentenceFromArticle;
+
+ Mapper fontMapper = null;
+
+ public byte[] generate(Integer taskId){
+ Optional taskOptional =taskService.getTaskInfo(taskId);
+
+ if(!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString());
+
+ Task task = taskOptional.get();
+
+ // 获得数据列表
+ Map dataMap = processDataMap(task);
+
+ StringWriter stringWriter = new StringWriter();
+ BufferedWriter writer = new BufferedWriter(stringWriter);
+ Template template = getTemplate("report.ftl");
+
+ try {
+
+ template.process(dataMap, writer);
+
+ // 转换为Word
+ String xmlStr = stringWriter.toString();
+ ByteArrayInputStream in = new ByteArrayInputStream(xmlStr.getBytes());
+ WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(in);
+
+ wordMLPackage.save(new java.io.File("./Report.docx"));
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+
+ wordMLPackage.setFontMapper(generateFrontMapper());
+
+ // 转换为PDF
+ Docx4J.toPDF(wordMLPackage, outputStream);
+
+
+
+ return outputStream.toByteArray();
+
+
+ } catch (IOException | TemplateException | Docx4JException e){
+ e.printStackTrace();
+ throw new HandlingErrorsException(e.getMessage());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private synchronized Mapper generateFrontMapper() throws Exception {
+ if (this.fontMapper == null) {
+// URL simSumUrl = ResourceUtils.getURL("classpath:fonts/simsun.ttc");
+ PhysicalFonts.discoverPhysicalFonts();
+ URL simSumUrl = ResourceUtils.getURL("./fonts/simsun.ttc");
+ PhysicalFonts.addPhysicalFont("SimSun", simSumUrl);
+
+ this.fontMapper = new IdentityPlusMapper();
+ fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
+ fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
+ fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
+ fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
+ fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
+ fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
+ fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
+ fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
+ fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
+ fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
+ fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
+ fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
+ fontMapper.put("等线", PhysicalFonts.get("SimSun"));
+ fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
+ fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
+ fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
+ fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
+ fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
+ fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
+ fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
+ fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
+ fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
+ fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
+ fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
+ //解决宋体(正文)和宋体(标题)的乱码问题
+ PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));
+ PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
+
+ return this.fontMapper;
+ }
+ return this.fontMapper;
+ }
+
+ public void saveByFile(String path, byte[] bytes) throws IOException {
+ FileOutputStream outputStream = new FileOutputStream(path);
+ outputStream.write(bytes);
+ outputStream.close();
+ }
+
+ public Integer saveByFileService(byte[] byteArray){
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray);
+ return fileService.saveFile(UUID.randomUUID().toString() + ".pdf", "pdf", inputStream);
+ }
+
+ private Map processDataMap(Task task){
+
+ File file = task.getFile();
+ if(file == null) throw new InnerDataTransmissionException(task.toString());
+
+ JsonableTaskResult taskResult = taskResultGenerator.getJsonableTaskResult(task.getId());
+
+ Map dataMap = new HashMap<>();
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm");
+ dataMap.put("articleTitle", file.getName());
+ dataMap.put("datetime", sdf.format(new Date()));
+
+ if(taskResult.getScore() > 90)
+ dataMap.put("grade", "A");
+ else if(taskResult.getScore() > 80)
+ dataMap.put("grade", "B");
+ else if(taskResult.getScore() > 60)
+ dataMap.put("grade", "C");
+ else
+ dataMap.put("grade", "D");
+
+ dataMap.put("wdScore", taskResult.getCorrectionScore());
+ dataMap.put("fqScore", taskResult.getDnnScore());
+ dataMap.put("flScore", taskResult.getEmotionScore());
+
+
+ List sentences = getSentenceFromArticle.get(task.getArticle());
+
+ int totalLen = 0;
+ for(Sentence sentence : sentences)
+ totalLen += sentence.getText().length();
+
+ dataMap.put("svgLen", totalLen / sentences.size());
+ dataMap.put("stnNum", sentences.size());
+ dataMap.put("userId", task.getUser().getId());
+ dataMap.put("errStatus", "正态分布");
+ dataMap.put("wEum", taskResult.getWrongTextCount());
+ dataMap.put("pcsTime", task.getEndDate().getTime() - task.getCreateDate().getTime());
+ dataMap.put("fqRum", taskResult.getBrokenSentencesCount());
+ dataMap.put("flEum", taskResult.getOralCount());
+ dataMap.put("status", "完成");
+
+ Map stnResults = new HashMap<>();
+
+ Map sentenceMap = new HashMap<>();
+
+ for(Sentence sentence : sentences){
+ sentenceMap.put(sentence.getId(), sentence);
+ }
+
+ for(JsonableSTNResult jsonableSTNResult : taskResult.getStnResults()){
+ if(jsonableSTNResult.getErrorList().size() == 0) continue;
+ STNResult stnResult = new STNResult();
+ stnResult.setId(jsonableSTNResult.getStnId());
+ stnResult.setText(sentenceMap.get(jsonableSTNResult.getStnId()).getText());
+ stnResults.put(stnResult.getId(), stnResult);
+ stnResult.setStnResultList(jsonableSTNResult.getErrorList());
+ stnResults.put(stnResult.getId(), stnResult);
+ }
+
+ List stnResultList = new ArrayList<>(stnResults.values());
+
+ dataMap.put("errorStnList", stnResultList);
+
+ return dataMap;
+
+ }
+
+ private Template getTemplate(String name){
+ try {
+ Configuration conf = new Configuration();
+
+// conf.setDirectoryForTemplateLoading(ResourceUtils.getFile("classpath:templates"));
+ conf.setDirectoryForTemplateLoading(new java.io.File("./templates/"));
+ return conf.getTemplate(name);
+
+ } catch (IOException e){
+ throw new RuntimeIOException(e.getMessage());
+ }
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/SHA256Encoder.java b/src/main/java/org/codedream/epaper/component/datamanager/SHA256Encoder.java
new file mode 100644
index 0000000..af5e40a
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/SHA256Encoder.java
@@ -0,0 +1,29 @@
+package org.codedream.epaper.component.datamanager;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.stereotype.Component;
+
+/**
+ * SHA256编码器
+ */
+@Component
+public class SHA256Encoder {
+ /**
+ * 编码
+ * @param charSequence 字符队列
+ * @return 密文
+ */
+ public String encode(CharSequence charSequence){
+ return DigestUtils.sha256Hex(charSequence.toString());
+ }
+
+ /**
+ * 验证
+ * @param charSequence 字符队列
+ * @param s 密文
+ * @return 布尔值
+ */
+ public boolean match (CharSequence charSequence, String s){
+ return s.equals(encode(charSequence));
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/SHA512Encoder.java b/src/main/java/org/codedream/epaper/component/datamanager/SHA512Encoder.java
new file mode 100644
index 0000000..7b06b0d
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/SHA512Encoder.java
@@ -0,0 +1,29 @@
+package org.codedream.epaper.component.datamanager;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.stereotype.Component;
+
+/**
+ * SHA256编码器
+ */
+@Component
+public class SHA512Encoder {
+ /**
+ * 编码
+ * @param charSequence 字符队列
+ * @return 密文
+ */
+ public String encode(CharSequence charSequence){
+ return DigestUtils.sha512Hex(charSequence.toString());
+ }
+
+ /**
+ * 验证
+ * @param charSequence 字符队列
+ * @param s 密文
+ * @return 布尔值
+ */
+ public boolean match (CharSequence charSequence, String s){
+ return s.equals(encode(charSequence));
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/STNResult.java b/src/main/java/org/codedream/epaper/component/datamanager/STNResult.java
new file mode 100644
index 0000000..c51f2c1
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/STNResult.java
@@ -0,0 +1,15 @@
+package org.codedream.epaper.component.datamanager;
+
+import lombok.Data;
+import org.codedream.epaper.component.json.model.JsonableSTNError;
+import org.codedream.epaper.component.json.model.JsonableSTNResult;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class STNResult {
+ private Integer id;
+ private String text;
+ private List stnResultList = new ArrayList<>();
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/SentenceDivider.java b/src/main/java/org/codedream/epaper/component/datamanager/SentenceDivider.java
new file mode 100644
index 0000000..3c08e8e
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/SentenceDivider.java
@@ -0,0 +1,122 @@
+package org.codedream.epaper.component.datamanager;
+
+import com.baidu.aip.nlp.AipNlp;
+import com.sun.org.apache.xpath.internal.operations.Bool;
+import javafx.util.Pair;
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.configure.SingletonAipNlp;
+import org.codedream.epaper.model.article.Phrase;
+import org.codedream.epaper.model.article.Sentence;
+import org.codedream.epaper.service.ArticleService;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+/**
+ * 提供句子分词方法,并通过{@link ArticleService}进行相应持久化操作
+ */
+@Slf4j
+@Component
+public class SentenceDivider {
+
+ @Resource
+ ArticleService articleService;
+
+ /**
+ * 将句子分词
+ *
+ * @param text 要进行分词操作的句子
+ * @return 一个已经持久化的、分完词的句子
+ */
+ public List divideSentence(String text) {
+
+ if (text.isEmpty()) return null;
+ AipNlp client = SingletonAipNlp.getInstance();
+ HashMap hashMap = new HashMap<>();
+ List phrases = new ArrayList<>();
+ String f = "";
+ try {
+ JSONObject res = client.lexer(text, hashMap);
+ f = "1";
+ boolean lexerFlag = true;
+ Iterator iterator = res.keys();
+ while (iterator.hasNext()) {
+ if (iterator.next().equals("error_msg")) {
+ lexerFlag = false;
+ break;
+ }
+ }
+
+ if (lexerFlag) {
+ f = "2";
+ JSONArray items = (JSONArray) res.get("items");
+ f = "After lexer";
+ for (int i = 0; i < items.length(); i++) {
+ JSONObject item = (JSONObject) items.get(i);
+ List basic = new ArrayList<>();
+ f = "Before basicArray";
+ JSONArray basicArray = (JSONArray) item.get("basic_words");
+
+ for (int j = 0; j < basicArray.length(); j++) {
+
+ f = "In basicArray";
+ Pair phrasePair = articleService.savePhrase(basicArray.get(j).toString());
+
+ basic.add(phrasePair.getValue());
+ }
+
+ f = "Before vec";
+ JSONObject vecRes = new JSONObject();
+
+
+ Pair phrasePair = articleService.savePhrase(item.get("item").toString());
+ Phrase phrase = phrasePair.getValue();
+
+ if(!phrasePair.getKey()){
+ phrase.setText(item.get("item").toString());
+ phrase.getBasicPhrase().addAll(basic);
+ phrase.setPos(item.get("pos").toString());
+ phrase = articleService.save(phrase);
+ }
+
+
+ Iterator keys = vecRes.keys();
+ boolean flag = true;
+ while (keys.hasNext()) {
+ if (keys.next().equals("error_msg")) {
+ flag = false;
+ break;
+ }
+ }
+ flag = false;
+ if (flag) {
+ f = "Before vecArray";
+ JSONArray vec = vecRes.getJSONArray("vec");
+ List floatList = new ArrayList<>();
+ for (int j = 0; j < vec.length(); j++) {
+ f = "In vecArray";
+ String str = vec.getString(i);
+ f = "Before parsing to float";
+ float vecUnit = Float.parseFloat(str);
+ floatList.add(vecUnit);
+ }
+ phrase.setVec(floatList);
+ }
+
+ phrases.add(phrase);
+
+ }
+ }
+ } catch (Exception e) {
+ log.error(e.toString() + ": f");
+ }
+
+ return phrases;
+ }
+
+
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/SentenceSmoothnessGetter.java b/src/main/java/org/codedream/epaper/component/datamanager/SentenceSmoothnessGetter.java
new file mode 100644
index 0000000..45aa627
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/SentenceSmoothnessGetter.java
@@ -0,0 +1,38 @@
+package org.codedream.epaper.component.datamanager;
+
+import com.baidu.aip.nlp.AipNlp;
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.configure.SingletonAipNlp;
+import org.json.JSONObject;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+
+/**
+ * 提供句子通顺度获取方法
+ */
+@Slf4j
+@Component
+public class SentenceSmoothnessGetter {
+
+ /**
+ * 通过调用百度接口获取句子通顺度
+ *
+ * @param text 待处理的文本
+ * @return 句子通顺度的值
+ */
+ public float getSentenceSmoothness(String text) {
+ AipNlp client = SingletonAipNlp.getInstance();
+ HashMap options = new HashMap<>();
+ JSONObject jsonObject = client.dnnlmCn(text, options);
+
+ try {
+ Thread.sleep(500);
+ float dnn = Float.parseFloat(jsonObject.get("ppl").toString());
+ return dnn;
+ } catch (Exception e) {
+ log.error(e.toString() + "In dnn: " + jsonObject.toString());
+ return (float) 0;
+ }
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/StringFile.java b/src/main/java/org/codedream/epaper/component/datamanager/StringFile.java
new file mode 100644
index 0000000..82a1fc3
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/StringFile.java
@@ -0,0 +1,25 @@
+package org.codedream.epaper.component.datamanager;
+
+import lombok.Data;
+
+/**
+ * 储存字符串标识的文件(可以将文件直接转换为Json进行传输)
+ */
+@Data
+public class StringFile {
+ // 文件内容(Base64编码,Gzip算法压缩)
+ private String strData = null;
+
+ // 散列值
+ private String sha256 = null;
+
+ // 文件大小
+ private Integer size = null;
+
+ // 文件类型
+ private String type = "none";
+
+ // 文件名
+ private String name = null;
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/StringFileGenerator.java b/src/main/java/org/codedream/epaper/component/datamanager/StringFileGenerator.java
new file mode 100644
index 0000000..9255006
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/StringFileGenerator.java
@@ -0,0 +1,131 @@
+package org.codedream.epaper.component.datamanager;
+
+import org.codedream.epaper.exception.innerservererror.StringFileConvertException;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Base64;
+import java.util.Optional;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * 字符串文件生成器
+ */
+@Component
+public class StringFileGenerator {
+
+ @Resource
+ private SHA256Encoder encoder;
+
+ /**
+ * 用过读入流创建一个字符串文件
+ * @param name 文件名
+ * @param type 文件类型
+ * @param stream 输入流
+ * @return 字符串文件对象
+ */
+ public Optional generateStringFile(String name, String type,InputStream stream){
+ StringFile file = new StringFile();
+ file.setName(name);
+ file.setType(type);
+
+ // 字符串内容计算
+ file.setStrData(generateFile2String(stream));
+ if(file.getStrData() == null) return Optional.empty();
+
+ // 相关校验值计算
+ file.setSha256(generateSHA256Checker(file.getStrData()));
+ file.setSize(file.getStrData().length());
+ return Optional.of(file);
+ }
+
+ /**
+ * 一次性读取输入流中的文件数据
+ * @param stream 输入流
+ * @return 文件数据
+ */
+ private byte[] readSteamAll(InputStream stream) {
+ try {
+ byte[] bytes = new byte[stream.available()];
+
+ //检查文件书否完全读取
+ if (stream.read(bytes) != bytes.length) return null;
+ else return bytes;
+ } catch (IOException e){
+ return null;
+ }
+ }
+
+ /**
+ * 将文件数据压缩(Gzip),然后用Base64编码为字符串
+ * @param stream 输入流
+ * @return 文件数据编码
+ */
+ private String generateFile2String(InputStream stream){
+ ByteArrayOutputStream zipDataStream = new ByteArrayOutputStream();
+ try {
+
+ // 解压缩
+ GZIPOutputStream gzipOutputStream = new GZIPOutputStream(zipDataStream);
+ byte[] bytes = readSteamAll(stream);
+ if(bytes == null) return null;
+ gzipOutputStream.write(bytes);
+ gzipOutputStream.close();
+
+ // 编码转换
+ return Base64.getEncoder().encodeToString(zipDataStream.toByteArray());
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 生成字符串文件的校验码
+ * @param str 文件散列值检查
+ * @return
+ */
+ private String generateSHA256Checker(String str){
+ return encoder.encode(str);
+ }
+
+ /**
+ * 检查文件内容是否正确,包括大小与校验码
+ * @param file 字符串文件对象
+ * @return 布尔值
+ */
+ public boolean checkStringFile(StringFile file){
+ return file.getStrData().length() == file.getSize()
+ && encoder.match(file.getStrData(), file.getSha256());
+ }
+
+ /**
+ * 从字符串文件中读取真实的文件数据
+ * @param file 字符串文件对象
+ * @return 输入流
+ */
+ public InputStream readFileString(StringFile file){
+ try {
+ // 字符串转换为二进制数据
+ byte[] bytes = Base64.getDecoder().decode(file.getStrData());
+ GZIPInputStream stream = new GZIPInputStream(new ByteArrayInputStream(bytes), bytes.length);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ // 数据解压缩
+ int readBits = 0;
+ byte[] rawBytes = new byte[1024];
+ while ((readBits = stream.read(rawBytes)) != -1) {
+ outputStream.write(rawBytes, 0, readBits);
+ }
+
+ stream.close();
+ return new ByteArrayInputStream(outputStream.toByteArray());
+ } catch (IOException e) {
+ throw new StringFileConvertException("Read FileString Failed");
+ }
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/TextCorrector.java b/src/main/java/org/codedream/epaper/component/datamanager/TextCorrector.java
new file mode 100644
index 0000000..6ecd7b7
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/TextCorrector.java
@@ -0,0 +1,70 @@
+package org.codedream.epaper.component.datamanager;
+
+import com.baidu.aip.nlp.AipNlp;
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.configure.SingletonAipNlp;
+import org.codedream.epaper.model.task.CorrectionResult;
+import org.json.JSONObject;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+
+/**
+ * 用于文本纠错
+ */
+@Slf4j
+@Component
+public class TextCorrector {
+
+ /**
+ * 文本纠错
+ *
+ * @param text 需要纠错的文本
+ * @return 一个纠正类的列表,保存句子需要修改的信息
+ * 值的位置;corr_text,存放纠错后文本;org_text:存放原文本。
+ */
+ public List correctText(String text) {
+
+ if (text.isEmpty()) return new ArrayList<>();
+ AipNlp client = SingletonAipNlp.getInstance();
+ HashMap options = new HashMap<>();
+ List correctionResults = new ArrayList<>();
+ String correction;
+ try {
+ JSONObject res = (JSONObject) client.ecnet(text, options).get("item");
+ if (res.get("vec_fragment") == null) {
+ return correctionResults;
+ }
+ correction = (String) res.get("correct_query");
+ DiffMatchPatch dmp = new DiffMatchPatch();
+ List diffs = dmp.diff_main(text, correction);
+ int p = 0;
+ int size = diffs.size();
+ for (int i = 0; i < size; i++) {
+ DiffMatchPatch.Diff diff = diffs.get(i);
+ if (diff.operation.equals(DiffMatchPatch.Operation.EQUAL) ||
+ diff.operation.equals(DiffMatchPatch.Operation.INSERT)) {
+ p += diff.text.length();
+ continue;
+ }
+ CorrectionResult correctionResult = new CorrectionResult();
+ correctionResult.setStartPos(p);
+ correctionResult.setLength(diff.text.length());
+ if (i == size - 1 || !diffs.get(i + 1).operation.equals(DiffMatchPatch.Operation.INSERT)) {
+ correctionResult.setCorrectionText("");
+ } else {
+ DiffMatchPatch.Diff nextDiff = diffs.get(i + 1);
+ correctionResult.setCorrectionText(nextDiff.text);
+ }
+ correctionResults.add(correctionResult);
+ }
+ } catch (Exception e) {
+ log.error(e.toString() + ": Failed to analyze \"" + text + "\"");
+ }
+
+ return correctionResults;
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/TextParser.java b/src/main/java/org/codedream/epaper/component/datamanager/TextParser.java
new file mode 100644
index 0000000..6e776ac
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/TextParser.java
@@ -0,0 +1,82 @@
+package org.codedream.epaper.component.datamanager;
+
+import javassist.bytecode.ByteArray;
+import org.codedream.epaper.exception.innerservererror.RuntimeIOException;
+import org.codedream.epaper.model.article.Article;
+import org.codedream.epaper.model.article.Paragraph;
+import org.codedream.epaper.model.file.File;
+import org.codedream.epaper.repository.article.ParagraphRepository;
+import org.codedream.epaper.service.IArticleService;
+import org.codedream.epaper.service.IFileService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Optional;
+
+@Component
+public class TextParser {
+ @Resource
+ private IFileService fileService;
+
+ @Resource
+ private IArticleService articleService;
+
+ @Resource
+ private ParagraphRepository paragraphRepository;
+
+ @Resource
+ private SHA512Encoder encoder;
+
+ public Integer parse(Integer fileId) {
+ File file = fileService.getFileInfo(fileId);
+ if(file.getType().equals("plain")){
+ Article article = articleService.createArticle(null);
+ InputStream stream = fileService.getFile(fileId);
+
+ ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
+
+ try {
+
+ // 数据内存写出
+ int readBits = 0;
+ byte[] rawBytes = new byte[1024];
+ while ((readBits = stream.read(rawBytes)) != -1) {
+ arrayOutputStream.write(rawBytes, 0, readBits);
+ }
+
+ saveParagraph(article, new String(arrayOutputStream.toByteArray()));
+ article.setFileId(fileId);
+ article = articleService.save(article);
+
+ return article.getId();
+ }
+ catch (IOException e){
+ throw new RuntimeIOException(e.getMessage());
+ }
+
+ }
+
+ return null;
+ }
+
+ // 储存段结构,并考虑缓存情况
+ private void saveParagraph(Article article, String text){
+ String hash = encoder.encode(text);
+ Paragraph paragraph;
+ Optional paragraphOptional = paragraphRepository.findBySha512Hash(hash);
+ if(!paragraphOptional.isPresent()){
+ paragraph = articleService.createParagraph(text);
+ paragraph.setSha512Hash(hash);
+ paragraph = articleService.save(paragraph);
+ }
+ else{
+ paragraph = paragraphOptional.get();
+ }
+
+ articleService.addParagraph(article, paragraph);
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/datamanager/WordParser.java b/src/main/java/org/codedream/epaper/component/datamanager/WordParser.java
new file mode 100644
index 0000000..aca9d5f
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/datamanager/WordParser.java
@@ -0,0 +1,132 @@
+package org.codedream.epaper.component.datamanager;
+
+import org.apache.poi.hwpf.extractor.WordExtractor;
+import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.codedream.epaper.configure.AppConfigure;
+import org.codedream.epaper.exception.innerservererror.HandlingErrorsException;
+import org.codedream.epaper.exception.innerservererror.RuntimeIOException;
+import org.codedream.epaper.model.article.Article;
+import org.codedream.epaper.model.article.Paragraph;
+import org.codedream.epaper.model.file.File;
+import org.codedream.epaper.repository.article.ParagraphRepository;
+import org.codedream.epaper.service.IArticleService;
+import org.codedream.epaper.service.IFileService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Optional;
+
+/**
+ * Word文档解析
+ */
+@Component
+public class WordParser {
+ @Resource
+ private IFileService fileService;
+
+ @Resource
+ private IArticleService articleService;
+
+ @Resource
+ private ParagraphRepository paragraphRepository;
+
+ @Resource
+ private SHA512Encoder encoder;
+
+ @Resource
+ private AppConfigure configure;
+
+ public Article parse(InputStream stream, String type){
+ if(type.equals("doc")){
+ return parseDoc(stream);
+ }
+ else{
+ return parseDocx(stream);
+ }
+ }
+
+ public Integer parse(Integer fileId){
+ File file = fileService.getFileInfo(fileId);
+ if(file.getType().equals("doc") || file.getType().equals("docx")){
+ Article article = parse(fileService.getFile(fileId), file.getType());
+ article.setFileId(fileId);
+ article = articleService.save(article);
+ return article.getId();
+ }
+ else throw new HandlingErrorsException(file.getType());
+
+ }
+
+ public Article parseDoc(InputStream stream) {
+ try {
+ WordExtractor extractor = new WordExtractor(stream);
+
+ Article article = articleService.createArticle(null);
+
+ for (String text : extractor.getParagraphText()) {
+ if (text.length() > configure.getParagraphMinSize()
+ && text.length() < configure.getParagraphMaxSize()) {
+ // 储存段结构
+ saveParagraph(article ,text);
+ }
+ }
+
+ // 保存章
+ return articleService.save(article);
+
+ } catch (IOException e){
+ throw new RuntimeIOException("Doc Parse Error");
+ }
+ }
+
+ public Article parseDocx(InputStream stream){
+ try{
+ XWPFDocument doc = new XWPFDocument(stream);
+ XWPFWordExtractor extractor = new XWPFWordExtractor(doc);
+
+ Article article = articleService.createArticle(null);
+
+ // 遍历段落
+ Iterator iterator = doc.getParagraphsIterator();
+ while (iterator.hasNext()){
+ XWPFParagraph para = iterator.next();
+ String text = para.getText();
+ if(text.length() > configure.getParagraphMinSize()
+ && text.length() < configure.getParagraphMaxSize()){
+ System.out.println("Paragraph: " + text);
+ // 储存段结构
+ saveParagraph(article ,text);
+ }
+ }
+
+ // 保存章
+ return articleService.save(article);
+
+ } catch (IOException e){
+ throw new RuntimeIOException("Docx Parse Error");
+ }
+ }
+
+ // 储存段结构,并考虑缓存情况
+ private void saveParagraph(Article article, String text){
+ String hash = encoder.encode(text);
+ Paragraph paragraph;
+ Optional paragraphOptional = paragraphRepository.findBySha512Hash(hash);
+ if(!paragraphOptional.isPresent()){
+ paragraph = articleService.createParagraph(text);
+ paragraph.setSha512Hash(hash);
+ paragraph = articleService.save(paragraph);
+ }
+ else{
+ paragraph = paragraphOptional.get();
+ }
+
+ articleService.addParagraph(article, paragraph);
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/JSONBaseObject.java b/src/main/java/org/codedream/epaper/component/json/JSONBaseObject.java
new file mode 100644
index 0000000..5ad626a
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/JSONBaseObject.java
@@ -0,0 +1,14 @@
+package org.codedream.epaper.component.json;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+
+/**
+ * 所有有效的JSON对象模板
+ */
+@Slf4j
+public class JSONBaseObject {
+ Date time = new Date();
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableBPT.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableBPT.java
new file mode 100644
index 0000000..6312764
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableBPT.java
@@ -0,0 +1,43 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.models.auth.In;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.codedream.epaper.model.article.Article;
+import org.codedream.epaper.model.article.Paragraph;
+import org.codedream.epaper.model.article.Sentence;
+import org.codedream.epaper.model.task.BatchProcessingTask;
+import org.codedream.epaper.model.task.Task;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@ApiModel("批处理任务结构")
+@NoArgsConstructor
+public class JsonableBPT {
+ private Integer id;
+ private Integer stnNumber;
+ private List stns = new ArrayList<>();
+
+ public JsonableBPT(BatchProcessingTask bpt){
+ this.id = bpt.getId();
+ this.stnNumber = bpt.getSentencesNumber();
+
+ for (Task task : bpt.getTasks()){
+ for(Paragraph paragraph : task.getArticle().getParagraphs()){
+ for(Sentence sentence : paragraph.getSentences()){
+ // 检查是否已经深处理完毕
+ if(sentence.isDeepProcess()) continue;
+
+ JsonableSTN stn = new JsonableSTN();
+ stn.setStnId(sentence.getId());
+ stn.setText(sentence.getText());
+ this.stns.add(stn);
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableBPTResult.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableBPTResult.java
new file mode 100644
index 0000000..d7bc5ff
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableBPTResult.java
@@ -0,0 +1,18 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@ApiModel("批处理任务结果返回结构")
+@NoArgsConstructor
+public class JsonableBPTResult {
+ // 句子Id
+ private Integer stnid;
+ // 标签的预测值
+ private List tagPossible = new ArrayList<>();
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableCSP.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableCSP.java
new file mode 100644
index 0000000..a7ae75b
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableCSP.java
@@ -0,0 +1,29 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.codedream.epaper.model.server.ChildServerPassport;
+
+import java.util.Date;
+
+@Data
+@ApiModel("子服务器护照")
+@NoArgsConstructor
+public class JsonableCSP {
+
+ // 身份认证码
+ private String identityCode;
+
+ // 最后一次签证日期
+ private Date lastUpdateTime;
+
+ // 护照是否过期
+ private boolean expired;
+
+ public JsonableCSP(ChildServerPassport csp){
+ this.identityCode = csp.getIdentityCode();
+ this.lastUpdateTime = csp.getLastUpdateTime();
+ this.expired = csp.isExpired();
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableFile.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableFile.java
new file mode 100644
index 0000000..c62a82d
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableFile.java
@@ -0,0 +1,20 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@ApiModel("文件信息结构")
+@NoArgsConstructor
+public class JsonableFile {
+
+ // 文件ID号
+ private Integer fileId;
+
+ // 文件名
+ private String filename;
+
+ // 文件类型
+ private String type;
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableSTN.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTN.java
new file mode 100644
index 0000000..116182a
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTN.java
@@ -0,0 +1,17 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@ApiModel("句原文列表结构")
+@NoArgsConstructor
+public class JsonableSTN {
+
+ // 句子ID号
+ private Integer stnId;
+
+ // 句子文本内容
+ private String text;
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNError.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNError.java
new file mode 100644
index 0000000..08b8def
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNError.java
@@ -0,0 +1,23 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@ApiModel("句错误结构")
+@NoArgsConstructor
+public class JsonableSTNError {
+
+ // 序列号(index)
+ private Integer wordIdx;
+
+ // 词长
+ private Integer wordLen;
+
+ // 错误类型
+ private Integer type;
+
+ // 错误内容
+ private String content;
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNPage.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNPage.java
new file mode 100644
index 0000000..6a3bbe4
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNPage.java
@@ -0,0 +1,21 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@ApiModel("句分页结构")
+@NoArgsConstructor
+public class JsonableSTNPage {
+ // 页序号
+ Integer page;
+
+ // 页数
+ Integer all;
+
+ // 句列表
+ List stns;
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNResult.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNResult.java
new file mode 100644
index 0000000..1bb0be7
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNResult.java
@@ -0,0 +1,28 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@ApiModel("句处理结果")
+@NoArgsConstructor
+public class JsonableSTNResult {
+ // 句ID号
+ Integer stnId;
+
+ // 错误类型
+ Integer appear;
+
+ // 分数
+ Float score;
+
+ // 是否为书面文本
+ boolean isNeutral;
+
+ // 错误列表
+ List errorList = new ArrayList<>();
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableTask.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableTask.java
new file mode 100644
index 0000000..1ed79fa
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableTask.java
@@ -0,0 +1,42 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.codedream.epaper.model.task.Task;
+
+@Data
+@ApiModel("子任务结果")
+@NoArgsConstructor
+public class JsonableTask {
+
+ // 任务ID号
+ private Integer taskId;
+
+ // 用户openid
+ private String openid;
+
+ // 文件ID号
+ private Integer fileId;
+
+ // 文本
+ private String text;
+
+ // 任务是否完成
+ private boolean finished;
+
+ // 任务进度
+ private Float progress;
+
+ // 描述
+ private String description;
+
+ public JsonableTask(Task task){
+ this.taskId = task.getId();
+ this.openid = task.getUser().getUsername();
+ this.fileId = task.getFile().getId();
+ this.finished = task.isFinished();
+ this.progress = task.getProgressRate() / 5.0f;
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableTaskResult.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableTaskResult.java
new file mode 100644
index 0000000..1b9e84f
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableTaskResult.java
@@ -0,0 +1,43 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@ApiModel("任务处理结果")
+@NoArgsConstructor
+public class JsonableTaskResult {
+
+ // 任务ID号
+ private Integer taskId;
+
+ // 任务是否成功
+ private boolean success;
+
+ // 文章错误位置数量
+ private Integer wrongTextCount = 0;
+
+ // 不通顺的句子数量
+ private Integer brokenSentencesCount = 0;
+
+ // 口语化的句子数量
+ private Integer oralCount = 0;
+
+ // 文章得分
+ private Double score = (double) 0;
+
+ // 通顺度得分
+ private Double dnnScore = (double) 0;
+
+ // 感情倾向得分
+ private Double emotionScore = (double) 0;
+
+ // 文本纠错得分
+ private Double correctionScore = (double) 0;
+
+ private List stnResults = new ArrayList<>();
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableUser.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableUser.java
new file mode 100644
index 0000000..f6c5840
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableUser.java
@@ -0,0 +1,33 @@
+package org.codedream.epaper.component.json.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.codedream.epaper.model.user.User;
+
+@Data
+@ApiModel("用户验证信息")
+@NoArgsConstructor
+public class JsonableUser {
+
+ // 用户的ID号(数据库)
+ private Integer id;
+
+ // 用户openid
+ private String openid;
+
+ // 密码(哈希值)
+ private String password;
+
+ public JsonableUser(User user){
+ this.openid = user.getUsername();
+ this.password = user.getPassword();
+ this.id = user.getId();
+ }
+
+ public User parseObject(User user){
+ user.setUsername(this.getOpenid());
+ user.setPassword(this.getPassword());
+ return user;
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/request/UserLoginChecker.java b/src/main/java/org/codedream/epaper/component/json/request/UserLoginChecker.java
new file mode 100644
index 0000000..80c809d
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/request/UserLoginChecker.java
@@ -0,0 +1,22 @@
+package org.codedream.epaper.component.json.request;
+
+import lombok.Data;
+
+/**
+ * 用户登录请求对象
+ */
+@Data
+public class UserLoginChecker {
+
+ // 请求类型
+ private String checkType;
+
+ // openid
+ private String openid;
+
+ // 密码
+ private String password;
+
+ // 客户端代码
+ private String clientCode;
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/respond/EmptyDataObjectRespond.java b/src/main/java/org/codedream/epaper/component/json/respond/EmptyDataObjectRespond.java
new file mode 100644
index 0000000..b01a0fa
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/respond/EmptyDataObjectRespond.java
@@ -0,0 +1,8 @@
+package org.codedream.epaper.component.json.respond;
+
+/**
+ * 空应答
+ */
+public class EmptyDataObjectRespond {
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/respond/ErrorInfoJSONRespond.java b/src/main/java/org/codedream/epaper/component/json/respond/ErrorInfoJSONRespond.java
new file mode 100644
index 0000000..19f3041
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/respond/ErrorInfoJSONRespond.java
@@ -0,0 +1,15 @@
+package org.codedream.epaper.component.json.respond;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 错误信息对象
+ */
+@Data
+public class ErrorInfoJSONRespond {
+ String exception = null;
+ String exceptionMessage = null;
+ Date date = null;
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/respond/JSONBaseRespondObject.java b/src/main/java/org/codedream/epaper/component/json/respond/JSONBaseRespondObject.java
new file mode 100644
index 0000000..dc7881d
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/respond/JSONBaseRespondObject.java
@@ -0,0 +1,61 @@
+package org.codedream.epaper.component.json.respond;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.codedream.epaper.component.json.JSONBaseObject;
+
+/**
+ * 服务端返回的JSON对象标准模板
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class JSONBaseRespondObject extends JSONBaseObject {
+
+ // 存放返回内容
+ private Object data = new EmptyDataObjectRespond();
+
+ // 存放响应信息提示
+ private String msg = "";
+
+ // 额外信息
+ private String info = null;
+
+ // 状态
+ private Integer status = 200;
+
+ public JSONBaseRespondObject(String msg){
+ super();
+ this.status = 200;
+ this.msg = msg;
+ }
+
+ public JSONBaseRespondObject(Integer status, String msg){
+ super();
+ this.status = status;
+ this.msg = msg;
+ }
+
+ public void setStatusNotFound(){
+ this.status = 404;
+ }
+
+ public void setStatusBadRequest(){
+ this.status = 400;
+ }
+
+ public void setStatusUnauthorized(){
+ this.status = 401;
+ }
+
+ public void setStatusForbidden(){
+ this.status = 403;
+ }
+
+ public void setStatusInternalServerError(){
+ this.status = 500;
+ }
+
+ public void setStatusOK(){
+ this.status = 200;
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/json/respond/UserLoginCheckerJSONRespond.java b/src/main/java/org/codedream/epaper/component/json/respond/UserLoginCheckerJSONRespond.java
new file mode 100644
index 0000000..65c8a5c
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/json/respond/UserLoginCheckerJSONRespond.java
@@ -0,0 +1,32 @@
+package org.codedream.epaper.component.json.respond;
+
+import lombok.Data;
+
+/**
+ * 用户登录请求应答
+ */
+@Data
+public class UserLoginCheckerJSONRespond {
+
+ // 用户是否存在
+ Boolean userExist = null;
+
+ // 用户是否被封禁
+ Boolean userBanned = null;
+
+ // 登录状态
+ Boolean loginStatus = null;
+
+ // 返回附加信息
+ String respondInformation = null;
+
+ // Token
+ String token = null;
+
+ // 用户ID号(数据库)
+ String uid = null;
+
+ // 预验证码
+ String pvc = null;
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/server/CSPUpdater.java b/src/main/java/org/codedream/epaper/component/server/CSPUpdater.java
new file mode 100644
index 0000000..236b51f
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/server/CSPUpdater.java
@@ -0,0 +1,69 @@
+package org.codedream.epaper.component.server;
+
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.auth.TimestampExpiredChecker;
+import org.codedream.epaper.configure.AppConfigure;
+import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException;
+import org.codedream.epaper.model.server.ChildServerPassport;
+import org.codedream.epaper.model.task.BatchProcessingTask;
+import org.codedream.epaper.repository.server.ChildServerPassportRepository;
+import org.codedream.epaper.repository.task.BatchProcessingTaskRepository;
+import org.codedream.epaper.service.INeuralNetworkModelService;
+import org.codedream.epaper.service.ITaskService;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Optional;
+
+/**
+ * 子服务器护照管理器
+ */
+@Slf4j
+@Component
+public class CSPUpdater {
+
+ @Resource
+ private ChildServerPassportRepository cspRepository;
+
+ @Resource
+ private BatchProcessingTaskRepository bptRepository;
+
+ @Resource
+ private INeuralNetworkModelService neuralNetworkModelService;
+
+ @Resource
+ private TimestampExpiredChecker expiredChecker;
+
+ @Resource
+ private AppConfigure configure;
+
+ /**
+ * 检查护照状态
+ */
+ @Scheduled(cron = "0/60 * * * * ?")
+ public void update(){
+ log.info("CSP Updater Started");
+ Iterable childServerPassports = cspRepository.findByExpired(false);
+ for(ChildServerPassport csp : childServerPassports){
+ if(expiredChecker
+ .checkDateBeforeDeterminedTime(csp.getLastUpdateTime(), configure.gerChildServerRegisterTimeout())){
+ if(csp.getBptId() != null){
+
+ // 释放其占用的批处理任务
+ Optional bpt = bptRepository.findById(csp.getBptId());
+ if(!bpt.isPresent()) throw new InnerDataTransmissionException();
+ neuralNetworkModelService.markBPTFailed(bpt.get());
+
+ log.info(String.format("Unlock BPT: bptId %s", csp.getBptId()));
+ csp.setBptId(null);
+ }
+ csp.setExpired(true);
+ csp = cspRepository.save(csp);
+ log.info(String.format("CSP Expired: idcode %s", csp.getIdentityCode()));
+
+ }
+
+ }
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/ArticlePreprocessor.java b/src/main/java/org/codedream/epaper/component/task/ArticlePreprocessor.java
new file mode 100644
index 0000000..d8a041b
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/ArticlePreprocessor.java
@@ -0,0 +1,89 @@
+package org.codedream.epaper.component.task;
+
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.datamanager.PdfParser;
+import org.codedream.epaper.component.datamanager.TextParser;
+import org.codedream.epaper.component.datamanager.WordParser;
+import org.codedream.epaper.exception.innerservererror.HandlingErrorsException;
+import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException;
+import org.codedream.epaper.model.article.Article;
+import org.codedream.epaper.model.file.File;
+import org.codedream.epaper.model.task.Task;
+import org.codedream.epaper.repository.article.ArticleRepository;
+import org.codedream.epaper.repository.task.TaskRepository;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Optional;
+
+/**
+ * 用于对文章的预处理。
+ * 包括对word文档的解析({@link WordParser})
+ */
+@Slf4j
+@Component
+public class ArticlePreprocessor {
+
+ @Resource
+ private TaskRepository taskRepository;
+
+ @Resource
+ private ArticleRepository articleRepository;
+
+ @Resource
+ private WordParser wordParser;
+
+ @Resource
+ private TextParser textParser;
+
+ @Resource
+ private PdfParser pdfParser;
+
+
+ /**
+ * 预处理任务中存储的文章并将其持久化
+ *
+ * @param taskId 任务id
+ */
+ public void parse(Integer taskId) {
+
+ // 查找子任务
+ Optional taskOptional = taskRepository.findById(taskId);
+ if (!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString());
+
+ Task task = taskOptional.get();
+ File file = task.getFile();
+ if (file == null) throw new InnerDataTransmissionException();
+
+ task.setFile(file);
+
+ Integer articleId = null;
+
+ // 章分段处理
+ switch (file.getType()) {
+ case "doc":
+ case "docx":
+ articleId = wordParser.parse(file.getId());
+ break;
+ case "plain":
+ articleId = textParser.parse(file.getId());
+ break;
+ case "pdf":
+ articleId = pdfParser.parse(file.getId());
+ break;
+ default:
+ throw new HandlingErrorsException(file.getType());
+ }
+
+ if(articleId == null) throw new HandlingErrorsException(file.getId().toString());
+
+ Optional optionalArticle = articleRepository.findById(articleId);
+ if (!optionalArticle.isPresent()) throw new InnerDataTransmissionException();
+
+ task.setArticle(optionalArticle.get());
+ task.setProgressRate(task.getProgressRate() + 1);
+ log.info(String.format("Article preprocess finished, task progress for now is: %d", task.getProgressRate()));
+ taskRepository.save(task);
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/BPTDivider.java b/src/main/java/org/codedream/epaper/component/task/BPTDivider.java
new file mode 100644
index 0000000..b0ade5e
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/BPTDivider.java
@@ -0,0 +1,63 @@
+package org.codedream.epaper.component.task;
+
+import org.codedream.epaper.component.article.GetSentenceFromArticle;
+import org.codedream.epaper.model.article.Sentence;
+import org.codedream.epaper.model.task.BatchProcessingTask;
+import org.codedream.epaper.model.task.Task;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 把一个超过limit的BPT按照task数量等分
+ */
+@Component
+public class BPTDivider {
+
+ @Resource
+ private GetSentenceFromArticle getSentenceFromArticle;
+
+ /**
+ * 将一个批处理任务等分
+ *
+ * 此方法会将原有的已经持久化的批处理任务从数据库中删除,并换成两个
+ * 已经等分了的批处理任务
+ *
+ * @param bpt 一个批处理任务
+ * @return 两个封装在列表中的批处理任务
+ */
+ public List divideBPT(BatchProcessingTask bpt) {
+
+ List batchProcessingTasks = new ArrayList<>();
+ BatchProcessingTask bpt1 = new BatchProcessingTask(bpt);
+ BatchProcessingTask bpt2 = new BatchProcessingTask(bpt);
+ List tasks = bpt.getTasks();
+ List sentenceList = new ArrayList<>();
+ bpt1.setTasks(tasks.subList(0, tasks.size() / 2));
+ bpt2.setTasks(tasks.subList(tasks.size() / 2, tasks.size()));
+ Integer sentenceNum = 0;
+
+ divide(batchProcessingTasks, bpt1, sentenceList, sentenceNum);
+ sentenceList.clear();
+ sentenceNum = 0;
+
+ divide(batchProcessingTasks, bpt2, sentenceList, sentenceNum);
+ return batchProcessingTasks;
+ }
+
+ private void divide(List batchProcessingTasks, BatchProcessingTask bpt, List sentenceList, Integer sentenceNum) {
+ for (int i = 0; i < bpt.getTasks().size(); i++) {
+ Task task = bpt.getTasks().get(i);
+ sentenceList.addAll(getSentenceFromArticle.get(task.getArticle()));
+ sentenceNum += task.getArticle().getSentencesNumber();
+ }
+ bpt.setSentences(sentenceList);
+ bpt.setSentencesNumber(sentenceNum);
+ bpt.setPriority(sentenceNum);
+ bpt.setCreateDate(new Date());
+ batchProcessingTasks.add(bpt);
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/BPTMonitor.java b/src/main/java/org/codedream/epaper/component/task/BPTMonitor.java
new file mode 100644
index 0000000..f997fce
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/BPTMonitor.java
@@ -0,0 +1,83 @@
+package org.codedream.epaper.component.task;
+
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.auth.TimestampExpiredChecker;
+import org.codedream.epaper.configure.AppConfigure;
+import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException;
+import org.codedream.epaper.model.task.BatchProcessingTask;
+import org.codedream.epaper.repository.task.BatchProcessingTaskRepository;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Iterator;
+import java.util.Optional;
+
+/**
+ * 用于提供对批处理任务的监控方法。
+ *
+ * 批处理任务在处理时可能会出现分配的GPU服务端突发故障、网络通信故障等问题,从而造成
+ * 当前批处理任务在结果等待队列中的无限制等待。为了避免这种问题的发生,我们设计了一个
+ * 批处理任务监听器用于监控其计算等待时间,超时后会放入原就绪队列进行重新分配
+ */
+@Slf4j
+@Component
+public class BPTMonitor {
+
+ @Resource
+ private LockedBPTs lockedBPTs;
+
+ @Resource
+ private BPTQueue bptQueue;
+
+ @Resource
+ private BatchProcessingTaskRepository bptRepository;
+
+ @Resource
+ private TimestampExpiredChecker timestampExpiredChecker;
+
+ @Resource
+ private AppConfigure configure;
+
+
+ private boolean initStatus = true;
+
+ /**
+ * 一个定时任务,每15秒检测一次批处理任务的等待情况
+ */
+ @Scheduled(cron = "0/5 * * ? * *")
+ public void monitorBPTs() {
+ // 启动自检
+ if (initStatus) {
+ // 查找未完成的批处理任务
+ Iterable batchProcessingTasks = bptRepository.findAllByFinished(false);
+
+ for (BatchProcessingTask bpt : batchProcessingTasks) {
+ bptQueue.addBPT(bpt.getId());
+ }
+ initStatus = false;
+ }
+
+ log.info("BPT Monitor Started");
+ log.info(String.format("Lined BPTs Number For Now: %s", bptQueue.size()));
+ log.info(String.format("Locked BPTs Number For Now: %s", lockedBPTs.size()));
+ if (lockedBPTs.isEmpty()) {
+ return;
+ }
+ Iterator bptIterator = lockedBPTs.iterator();
+ while (bptIterator.hasNext()) {
+ Integer bptId = bptIterator.next();
+ Optional oBpt = bptRepository.findById(bptId);
+ if (!oBpt.isPresent()) throw new InnerDataTransmissionException();
+
+ BatchProcessingTask bpt = oBpt.get();
+ if (timestampExpiredChecker.checkDateBeforeDeterminedTime(
+ bpt.getJoinDate(), configure.gerMaxBPTProcessDelayTime())) {
+
+ bptQueue.addBPT(bpt.getId());
+ bptIterator.remove();
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/BPTQueue.java b/src/main/java/org/codedream/epaper/component/task/BPTQueue.java
new file mode 100644
index 0000000..be578b8
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/BPTQueue.java
@@ -0,0 +1,59 @@
+package org.codedream.epaper.component.task;
+
+import org.codedream.epaper.model.task.BatchProcessingTask;
+import org.codedream.epaper.repository.task.BatchProcessingTaskRepository;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Comparator;
+import java.util.concurrent.PriorityBlockingQueue;
+
+/**
+ * 批处理任务优先级队列
+ *
+ * 根据批处理任务待处理的句子数量设置优先级
+ * (参见{@link org.codedream.epaper.model.task.BatchProcessingTask#compareTo(BatchProcessingTask)})
+ */
+@Component
+public class BPTQueue {
+
+ @Resource
+ private BatchProcessingTaskRepository bptRepository;
+
+ // 优先阻塞队列
+ private PriorityBlockingQueue batchProcessingTasks = new PriorityBlockingQueue<>(1,
+ new Comparator() {
+ @Override
+ public int compare(Integer o1, Integer o2) {
+ BatchProcessingTask bpt1 = bptRepository.findById(o1).get();
+ BatchProcessingTask bpt2 = bptRepository.findById(o2).get();
+ return bpt1.getPriority() - bpt2.getPriority();
+ }
+ });
+
+ // 添加批处理任务
+ public void addBPT(Integer bptId) {
+ batchProcessingTasks.offer(bptId);
+ }
+
+ //移除批处理任务
+ public void removeBPT(Integer bptId) {
+ batchProcessingTasks.remove(bptId);
+ }
+
+ // 检查队列是否为空
+ public boolean checkEmpty(){
+ return batchProcessingTasks.isEmpty();
+ }
+
+ // 从队列中获得一个批处理任务
+ public Integer getBptId() throws InterruptedException {
+ return batchProcessingTasks.take();
+ }
+
+ public Integer size(){
+ return batchProcessingTasks.size();
+ }
+
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/JsonableTaskResultGenerator.java b/src/main/java/org/codedream/epaper/component/task/JsonableTaskResultGenerator.java
new file mode 100644
index 0000000..840eae0
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/JsonableTaskResultGenerator.java
@@ -0,0 +1,180 @@
+package org.codedream.epaper.component.task;
+
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.json.model.JsonableSTNError;
+import org.codedream.epaper.component.json.model.JsonableSTNResult;
+import org.codedream.epaper.component.json.model.JsonableTaskResult;
+import org.codedream.epaper.exception.notfound.NotFoundException;
+import org.codedream.epaper.model.task.CorrectionResult;
+import org.codedream.epaper.model.task.SentenceResult;
+import org.codedream.epaper.model.task.TaskResult;
+import org.codedream.epaper.repository.task.TaskResultRepository;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 提供用于返回前端的结果构造方法
+ *
+ * 原生的结果数据比较混杂,不适合前端后端JSON格式的数据通信,设立此类以整合结果
+ */
+@Slf4j
+@Component
+public class JsonableTaskResultGenerator {
+
+ @Resource
+ TaskResultRepository taskResultRepository;
+
+ /**
+ * 根据任务结果的原生数据构造用于返回前端的任务结果
+ * @param taskId 任务id
+ * @return 一个JsonableTaskResult对象,包含构建好的任务结果
+ * @see JsonableTaskResult
+ */
+ public JsonableTaskResult getJsonableTaskResult(Integer taskId) {
+
+ log.info(String.format("Generating jsonable result of task %d", taskId));
+ Optional OTaskResult = taskResultRepository.findByTaskId(taskId);
+ double dnnScore = 0;
+ double correctScore = 0;
+ double emotionScore = 0;
+ double e = Math.E;
+ int dnnCnt = 0;
+ int wrongCnt = 0;
+ int oralCnt = 0;
+ if (OTaskResult.isPresent()) {
+ TaskResult taskResult = OTaskResult.get();
+ JsonableTaskResult jsonableTaskResult = new JsonableTaskResult();
+ List jsonableSTNResults = new ArrayList<>();
+ jsonableTaskResult.setTaskId(taskResult.getTaskId());
+ taskResult.setSuccess(true);
+ taskResult = taskResultRepository.save(taskResult);
+ jsonableTaskResult.setSuccess(true);
+ Map sentenceResultMap = taskResult.getSentenceResultMap();
+ for (SentenceResult result : taskResult.getSentenceResultMap().values()) {
+ JsonableSTNResult jsonableSTNResult = new JsonableSTNResult();
+ jsonableSTNResult.setStnId(result.getSentenceId());
+ jsonableSTNResult.setAppear(0);
+ SentenceResult sentenceResult = sentenceResultMap.get(result.getSentenceId());
+
+ // 中立值设置
+ jsonableSTNResult.setNeutral(sentenceResult.isNeutral());
+
+ List jsonableSTNErrorList = new ArrayList<>();
+
+ List correctionResultList = result.getCorrectionResults();
+ correctScore += correctionResultList.size();
+
+ log.info("Generating correction result……");
+ log.info(String.format("Correction result of sentence %d: %s", sentenceResult.getSentenceId(),
+ correctionResultList.toString()));
+ // 寻找该sentence被修改了的位置
+ for (CorrectionResult correctionResult : correctionResultList) {
+ JsonableSTNError jsonableSTNError = new JsonableSTNError();
+ jsonableSTNError.setWordIdx(correctionResult.getStartPos());
+ jsonableSTNError.setWordLen(correctionResult.getLength());
+ jsonableSTNError.setType(1);
+ wrongCnt++;
+ if (correctionResult.getCorrectionText().isEmpty()) {
+ jsonableSTNError.setContent("文本存在错误,建议删除");
+ } else {
+ jsonableSTNError.setContent(String.format("文本存在错误,建议修改为:%s",
+ correctionResult.getCorrectionText()));
+ }
+ jsonableSTNErrorList.add(jsonableSTNError);
+ }
+
+ // 判断句子情感倾向
+ String content;
+ JsonableSTNError stnError = new JsonableSTNError();
+ if (sentenceResult.isNegative() || sentenceResult.isPositive()) {
+ stnError.setType(2);
+ stnError.setWordIdx(0);
+ stnError.setWordLen(0x7fffffff);
+ if (sentenceResult.isNegative()) {
+ taskResult.getNegativeEmotionsCount().incrementAndGet();
+ float possibility = sentenceResult.getPossibilities().get(0);
+ emotionScore += Math.pow(e, possibility);
+ if (possibility < 0.9) {
+ content = "文本语言较强烈口语化特征,建议修改为书面语。";
+ } else {
+ content = "文本语言极为强烈的口语化特征,建议修改为书面语。";
+ }
+ } else {
+ taskResult.getPositiveEmotionsCount().incrementAndGet();
+ float possibility = sentenceResult.getPossibilities().get(1);
+ emotionScore += Math.pow(2 * e, possibility);
+ if (possibility < 0.99) {
+ content = "文本语言较强烈的口语化特征,建议修改为书面语。";
+ } else {
+ content = "文本语言有极为强烈的口语化特征,建议修改为书面语。";
+ }
+ }
+ oralCnt++;
+ stnError.setContent(content);
+ jsonableSTNErrorList.add(stnError);
+ } else if (sentenceResult.getPossibilities().get(1) < 0.99) {
+ stnError.setType(2);
+ stnError.setWordIdx(0);
+ stnError.setWordLen(0x7fffffff);
+ content = "文本疑似存在口语化问题,请注意审查";
+ stnError.setContent(content);
+ jsonableSTNErrorList.add(stnError);
+ oralCnt++;
+ }
+
+ if (sentenceResult.getDnn() > 4000) {
+ dnnScore += sentenceResult.getDnn();
+ taskResult.getBrokenSentencesCount().incrementAndGet();
+ JsonableSTNError jsonableSTNError = new JsonableSTNError();
+ jsonableSTNError.setType(3);
+ jsonableSTNError.setContent("句子通顺度存在问题,建议修改");
+ jsonableSTNError.setWordIdx(0);
+ jsonableSTNError.setWordLen(0x7fffffff);
+ jsonableSTNErrorList.add(jsonableSTNError);
+ dnnCnt++;
+ } else if (sentenceResult.getDnn() > 2000) {
+ dnnScore += sentenceResult.getDnn();
+ dnnCnt++;
+ }
+ jsonableSTNResult.setErrorList(jsonableSTNErrorList);
+ jsonableSTNResults.add(jsonableSTNResult);
+ }
+ taskResultRepository.save(taskResult);
+
+ jsonableTaskResult.setWrongTextCount(wrongCnt);
+ jsonableTaskResult.setBrokenSentencesCount(dnnCnt);
+ jsonableTaskResult.setOralCount(oralCnt);
+ jsonableTaskResult.setStnResults(jsonableSTNResults);
+
+ wrongCnt += 4;
+ correctScore = Math.PI * Math.log(Math.atan(1.1 * wrongCnt - 10) + 2) / Math.log(e) + 10;
+ correctScore = correctScore * 6.3489125794718040652210611515526 + 12.305831203372289317158476382609;
+
+ dnnCnt += 1;
+ dnnScore = Math.PI * Math.log(-Math.atan(2 * dnnCnt - 20) + 2) / Math.log(e) + 10;
+ dnnScore = dnnScore * 5.8066270155791913101469156905197 + 19.001197895698319575318511547571;
+
+
+ oralCnt += 1;
+ emotionScore = Math.PI * Math.log(-Math.atan(0.9 * oralCnt - 12) + 2) / Math.log(e) + 10;
+ emotionScore = emotionScore * 6.4684248579558173553422908337857 + 9.969074019579274789063012724827;
+
+ jsonableTaskResult.setDnnScore(dnnScore);
+ jsonableTaskResult.setEmotionScore(emotionScore);
+ jsonableTaskResult.setCorrectionScore(correctScore);
+
+ dnnScore *= 0.34482758620689655172413793103448;
+ correctScore *= 0.17241379310344827586206896551724;
+ emotionScore *= 0.48275862068965517241379310344828;
+ jsonableTaskResult.setScore(dnnScore + emotionScore + correctScore);
+ return jsonableTaskResult;
+ } else {
+ throw new NotFoundException("This task has no result yet.");
+ }
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/LockedBPTs.java b/src/main/java/org/codedream/epaper/component/task/LockedBPTs.java
new file mode 100644
index 0000000..85ed256
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/LockedBPTs.java
@@ -0,0 +1,35 @@
+package org.codedream.epaper.component.task;
+
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * 用于提供{@link org.codedream.epaper.model.task.BatchProcessingTask}的计算等待队列以及相关操作
+ */
+@Component
+public class LockedBPTs {
+ private List bptIdList = new ArrayList<>();
+
+ public void add(Integer bptId){
+ bptIdList.add(bptId);
+ }
+
+ public Iterator iterator(){
+ return bptIdList.iterator();
+ }
+
+ public boolean isEmpty(){
+ return bptIdList.isEmpty();
+ }
+
+ public boolean contains(Integer integer){
+ return bptIdList.contains(integer);
+ }
+
+ public Integer size(){
+ return bptIdList.size();
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/ParagraphProcessor.java b/src/main/java/org/codedream/epaper/component/task/ParagraphProcessor.java
new file mode 100644
index 0000000..1b9985a
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/ParagraphProcessor.java
@@ -0,0 +1,116 @@
+package org.codedream.epaper.component.task;
+
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.datamanager.ParagraphDivider;
+import org.codedream.epaper.component.datamanager.SHA512Encoder;
+import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException;
+import org.codedream.epaper.model.article.Article;
+import org.codedream.epaper.model.article.Paragraph;
+import org.codedream.epaper.model.article.Sentence;
+import org.codedream.epaper.model.task.Task;
+import org.codedream.epaper.repository.article.ArticleRepository;
+import org.codedream.epaper.repository.article.ParagraphRepository;
+import org.codedream.epaper.repository.article.SentenceRepository;
+import org.codedream.epaper.repository.task.TaskRepository;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+/**
+ * 提供对段落的预处理方法
+ */
+@Slf4j
+@Component
+public class ParagraphProcessor {
+
+ @Resource
+ private TaskRepository taskRepository;
+
+ @Resource
+ private ParagraphRepository paragraphRepository;
+
+ @Resource
+ private ParagraphDivider paragraphDivider;
+
+ @Resource
+ private SentenceRepository sentenceRepository;
+
+ @Resource
+ private ArticleRepository articleRepository;
+
+ @Resource
+ private SHA512Encoder encoder;
+
+ /**
+ * 对段落进行分句处理并进行SHA512编码,以便缓存识别
+ *
+ * @param taskId 任务id,用于获取待处理的段落
+ * @see ParagraphDivider
+ * @see SHA512Encoder
+ */
+ public void parse(Integer taskId) {
+ // 查找子任务
+ Optional taskOptional = taskRepository.findById(taskId);
+ if (!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString());
+
+ Article article = taskOptional.get().getArticle();
+ if (article == null) throw new InnerDataTransmissionException(taskId.toString());
+
+ int stnNum = 0;
+
+ // 段分句及段结构更新
+ for (Paragraph paragraph : article.getParagraphs()) {
+ // 跳过预处理过的段落
+ if (paragraph.isPreprocess()) continue;
+
+ // 段分句处理
+ List sentenceTexts = paragraphDivider.divideParagraph(paragraph.getText());
+ List sentences = new ArrayList<>();
+ for (String text : sentenceTexts) {
+ Sentence sentence;
+ String hash = encoder.encode(text);
+
+ // 查找句数据库缓存
+ Optional sentenceOptional = sentenceRepository.findBySha512Hash(hash);
+ if (!sentenceOptional.isPresent()) {
+ // 创建新的句
+ sentence = new Sentence();
+ sentence.setText(text);
+ sentence.setSha512Hash(hash);
+ sentence = sentenceRepository.save(sentence);
+ } else {
+ sentence = sentenceOptional.get();
+ }
+ sentences.add(sentence);
+ }
+
+ stnNum += sentences.size();
+
+ // 设置句集合
+ Set sentenceSet = new HashSet<>(sentences);
+ paragraph.setSentences(sentenceSet);
+
+ // 设置句列表
+ for (Sentence sentence : sentences) {
+ paragraph.getSentenceList().add(sentence.getId());
+ }
+
+ // 设置预处理状态
+ paragraph.setPreprocess(true);
+
+ // 更新段落信息
+ paragraphRepository.save(paragraph);
+ }
+
+ // 更新文章总句数
+ taskOptional.get().getArticle().setSentencesNumber(stnNum);
+ taskOptional.get().setProgressRate(taskOptional.get().getProgressRate() + 1);
+ log.info(String.format("Paragraph preprocess finished, task progress for now is: %d",
+ taskOptional.get().getProgressRate()));
+ articleRepository.save(taskOptional.get().getArticle());
+ taskRepository.save(taskOptional.get());
+
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/SentenceAnalyser.java b/src/main/java/org/codedream/epaper/component/task/SentenceAnalyser.java
new file mode 100644
index 0000000..0922acf
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/SentenceAnalyser.java
@@ -0,0 +1,62 @@
+package org.codedream.epaper.component.task;
+
+import org.codedream.epaper.component.datamanager.SentenceSmoothnessGetter;
+import org.codedream.epaper.component.datamanager.TextCorrector;
+import org.codedream.epaper.model.article.Sentence;
+import org.codedream.epaper.model.task.CorrectionResult;
+import org.codedream.epaper.model.task.SentenceResult;
+import org.codedream.epaper.repository.task.SentenceResultRepository;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 句分析器,提供句子分析相关方法以及持久化方法
+ *
+ * @see TextCorrector
+ * @see SentenceSmoothnessGetter
+ */
+@Component
+public class SentenceAnalyser {
+
+ @Resource
+ private TextCorrector textCorrector;
+
+ @Resource
+ private SentenceSmoothnessGetter dnnGetter;
+
+ @Resource
+ private SentenceResultRepository sentenceResultRepository;
+
+ public SentenceResult analyse(Sentence sentence) {
+ // 创建与预填写句子与处理结果结构
+ SentenceResult sentenceResult = new SentenceResult();
+ sentenceResult.setSentenceId(sentence.getId());
+ sentenceResult.setDnn(dnnParse(sentence));
+ correct(sentenceResult, sentence);
+ // 储存句分析结果
+ return sentenceResultRepository.save(sentenceResult);
+ }
+
+ /**
+ * 获取句子的文本纠错结果
+ *
+ * @param sentence 句子
+ */
+ private void correct(SentenceResult sentenceResult, Sentence sentence) {
+ List correctionResultList = textCorrector.correctText(sentence.getText());
+ sentenceResult.setCorrectionResults(correctionResultList);
+ }
+
+ /**
+ * 获取DNN处理结果
+ *
+ * @param sentence 待处理句子
+ * @return DNN处理结果
+ * @see SentenceSmoothnessGetter#getSentenceSmoothness(String)
+ */
+ private synchronized float dnnParse(Sentence sentence) {
+ return dnnGetter.getSentenceSmoothness(sentence.getText());
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/SentencePreprocessor.java b/src/main/java/org/codedream/epaper/component/task/SentencePreprocessor.java
new file mode 100644
index 0000000..3a27bb3
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/SentencePreprocessor.java
@@ -0,0 +1,78 @@
+package org.codedream.epaper.component.task;
+
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.article.GetSentenceFromArticle;
+import org.codedream.epaper.component.datamanager.SentenceDivider;
+import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException;
+import org.codedream.epaper.model.article.Article;
+import org.codedream.epaper.model.article.Phrase;
+import org.codedream.epaper.model.article.Sentence;
+import org.codedream.epaper.model.task.Task;
+import org.codedream.epaper.repository.article.SentenceRepository;
+import org.codedream.epaper.repository.task.TaskRepository;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 用于提供句子预处理相关方法,以及对应的持久化方法
+ */
+@Slf4j
+@Component
+public class SentencePreprocessor {
+
+ @Resource
+ private TaskRepository taskRepository;
+
+ @Resource
+ private SentenceDivider sentenceDivider;
+
+ @Resource
+ private SentenceRepository sentenceRepository;
+
+ @Resource
+ private GetSentenceFromArticle getSentenceFromArticle;
+
+
+ /**
+ * 取出任务中的句子并进行预处理
+ *
+ * @param taskId 任务id
+ * @see SentenceDivider#divideSentence(String)
+ * @see GetSentenceFromArticle#get(Article)
+ */
+ public void parse(Integer taskId) {
+ // 查找子任务
+ Optional taskOptional = taskRepository.findById(taskId);
+ if (!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString());
+
+ Task task = taskOptional.get();
+ Article article = task.getArticle();
+ if (article == null) throw new InnerDataTransmissionException(taskId.toString());
+
+ List sentences = getSentenceFromArticle.get(article);
+
+ for (Sentence sentence : sentences) {
+ // 跳过缓存句
+ if (sentence.isPreprocess()) continue;
+
+ List phrases = sentenceDivider.divideSentence(sentence.getText());
+
+ sentence.getPhrases().addAll(phrases);
+ sentence.setPreprocess(true);
+
+ for (Phrase phrase : phrases) {
+ sentence.getPhraseList().add(phrase.getId());
+ }
+
+ sentenceRepository.save(sentence);
+ }
+ task.setProgressRate(task.getProgressRate() + 1);
+ log.info(String.format("Sentence preprocess finished, task progress for now is: %d", task.getProgressRate()));
+ taskRepository.save(task);
+ }
+
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/TaskAnalyser.java b/src/main/java/org/codedream/epaper/component/task/TaskAnalyser.java
new file mode 100644
index 0000000..e32cdc7
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/TaskAnalyser.java
@@ -0,0 +1,110 @@
+package org.codedream.epaper.component.task;
+
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.article.GetSentenceFromArticle;
+import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException;
+import org.codedream.epaper.model.article.Sentence;
+import org.codedream.epaper.model.task.SentenceResult;
+import org.codedream.epaper.model.task.Task;
+import org.codedream.epaper.model.task.TaskResult;
+import org.codedream.epaper.repository.task.SentenceResultRepository;
+import org.codedream.epaper.repository.task.TaskRepository;
+import org.codedream.epaper.repository.task.TaskResultRepository;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+
+/**
+ * 子任务预分析器,用于精确到句子的任务分析以及持久化子任务结果
+ *
+ * @see SentenceAnalyser
+ */
+@Slf4j
+@Component
+public class TaskAnalyser {
+
+ @Resource
+ private TaskResultRepository taskResultRepository;
+
+ @Resource
+ private TaskRepository taskRepository;
+
+ @Resource
+ private GetSentenceFromArticle getSentenceFromArticle;
+
+ @Resource
+ private SentenceAnalyser sentenceAnalyser;
+
+ @Resource
+ private SentenceResultRepository sentenceResultRepository;
+
+ /**
+ * 分析一个task,并持久化其结果
+ * 持久化结果的时候,会先查找缓存
+ *
+ * @param taskId 任务id
+ * @return 一个TaskResult对象
+ */
+ public TaskResult analyse(Integer taskId) {
+
+ log.info(String.format("Analysing task: %d", taskId));
+
+ // 查找子任务
+ Optional taskOptional = taskRepository.findById(taskId);
+ if (!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString());
+
+ Task task = taskOptional.get();
+
+ // 构建预处理的任务结果存储结构
+ TaskResult taskResult = new TaskResult();
+ taskResult.setTaskId(task.getId());
+
+ Map dnnRes = new HashMap<>();
+ Map sentenceMap = new HashMap<>();
+ // 句分析
+ for(Sentence sentence : getSentenceFromArticle.get(task.getArticle())){
+ SentenceResult sentenceResult;
+ log.info(String.format("Analysing sentence: %d", sentence.getId()));
+ // 查找缓存
+ Optional sentenceResultOptional =
+ sentenceResultRepository.findBySentenceId(sentence.getId());
+
+ if (sentenceResultOptional.isPresent()) {
+ sentenceResult = sentenceResultOptional.get();
+
+ dnnRes.put(sentence.getId(), sentenceResult.getDnn());
+ sentenceMap.put(sentence.getId(), sentenceResult);
+ log.info(String.format("DNN result of sentence %d: %f", sentence.getId(), sentenceResult.getDnn()));
+ log.info(String.format("Correction result of sentence %d: %s", sentence.getId(),
+ sentenceResult.getCorrectionResults().toString()));
+ continue;
+ } else {
+ sentenceResult = sentenceAnalyser.analyse(sentence);
+ }
+ log.info(String.format("DNN result of sentence %d: %f", sentence.getId(), sentenceResult.getDnn()));
+ log.info(String.format("Correction result of sentence %d: %s", sentence.getId(),
+ sentenceResult.getCorrectionResults().toString()));
+ dnnRes.put(sentence.getId(), sentenceResult.getDnn());
+ sentenceMap.put(sentence.getId(), sentenceResult);
+ }
+
+ taskResult.setDnnMap(dnnRes);
+ taskResult.setSentenceResultMap(sentenceMap);
+
+ taskResult = taskResultRepository.save(taskResult);
+
+
+ // 更新任务
+ taskOptional.get().setProgressRate(taskOptional.get().getProgressRate()+1);
+ task.setResult(taskResult);
+ taskRepository.save(task);
+ log.info(String.format("Analyse preprocess finished, task progress for now is: %d", task.getProgressRate()));
+
+ log.info(String.format("Analysing result of task %d: %s", taskId, task.getResult()));
+ return taskResult;
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/TaskMonitor.java b/src/main/java/org/codedream/epaper/component/task/TaskMonitor.java
new file mode 100644
index 0000000..fe6d38a
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/TaskMonitor.java
@@ -0,0 +1,83 @@
+package org.codedream.epaper.component.task;
+
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.auth.TimestampExpiredChecker;
+import org.codedream.epaper.configure.AppConfigure;
+import org.codedream.epaper.model.task.BatchProcessingTask;
+import org.codedream.epaper.model.task.Task;
+import org.codedream.epaper.repository.task.BatchProcessingTaskRepository;
+import org.codedream.epaper.service.TaskService;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * 子任务监视器
+ */
+@Slf4j
+@Component
+public class TaskMonitor {
+
+ @Resource
+ private TaskQueue taskQueue;
+
+ @Resource
+ private TaskService taskService;
+
+ @Resource
+ private TimestampExpiredChecker timestampExpiredChecker;
+
+ @Resource
+ private BatchProcessingTaskRepository bptRepository;
+
+ @Resource
+ private AppConfigure configure;
+
+ @Scheduled(cron = "0/3 * * ? * *")
+ public void monitorTasks() {
+ log.info("Tasks Monitor Started.");
+ log.info(String.format("Tasks Number For Now: %s", taskQueue.size()));
+ if (taskQueue.checkEmpty()) {
+ return;
+ }
+
+ List tasks = new ArrayList<>();
+ Integer sentenceNum = 0;
+ Iterator iterator = taskQueue.getIterator();
+ while (iterator.hasNext()) {
+ Task task = iterator.next();
+
+ if (null == task) {
+ log.error("Task null value.");
+ continue;
+ }
+ //超时MaxTaskDelayTime秒即添加到一个批处理队列中
+ if (timestampExpiredChecker.checkDateBeforeDeterminedTime(
+ task.getCreateDate(), configure.gerMaxTaskDelayTime())) {
+
+ tasks.add(task);
+ iterator.remove();
+ sentenceNum += task.getArticle().getSentencesNumber();
+ }
+ }
+
+ if (tasks.isEmpty()) return;
+
+ // 对等待超时的task立即新建一个bpt,将之放入就绪队列,等待调用
+ BatchProcessingTask bpt = new BatchProcessingTask();
+ bpt.setTasks(tasks);
+ bpt.setCreateDate(new Date());
+ bpt.setPriority(sentenceNum);
+ bpt.setSentencesNumber(sentenceNum);
+ bpt = bptRepository.save(bpt);
+
+ // 注册批处理任务
+ taskService.registerBPTTask(bpt);
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/component/task/TaskQueue.java b/src/main/java/org/codedream/epaper/component/task/TaskQueue.java
new file mode 100644
index 0000000..f14da83
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/component/task/TaskQueue.java
@@ -0,0 +1,55 @@
+package org.codedream.epaper.component.task;
+
+import lombok.Data;
+import org.codedream.epaper.model.task.Task;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+/**
+ * 任务优先队列,用于给BPT中的任务进行优先级排序
+ */
+@Data
+@Component
+public class TaskQueue {
+
+ Comparator comparator = Comparator.comparingInt(Task::getPriority);
+
+ private Queue tasks = new PriorityQueue<>(comparator);
+
+ private Integer sentenceNumber = 0;
+
+ public boolean addTask(Task task) {
+ if (this.tasks.offer(task)) {
+ sentenceNumber += task.getArticle().getSentencesNumber();
+ return true;
+ } else return false;
+ }
+
+ public Integer size(){
+ return tasks.size();
+ }
+
+ public void removeTask(Task task){
+ tasks.remove(task);
+ }
+
+ public Iterator getIterator(){
+ return tasks.iterator();
+ }
+
+ public List getTaskAsList() {
+ List taskList = new ArrayList<>(this.tasks);
+ return taskList;
+ }
+
+ public void clear() {
+ this.tasks.clear();
+ this.setSentenceNumber(0);
+ }
+
+ public boolean checkEmpty() {
+ return this.tasks.isEmpty();
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/configure/AppConfigure.java b/src/main/java/org/codedream/epaper/configure/AppConfigure.java
new file mode 100644
index 0000000..1c82333
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/AppConfigure.java
@@ -0,0 +1,125 @@
+package org.codedream.epaper.configure;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * 应用程序常用配置信息
+ * 用于常见的应用程序本身的相关信息的引用
+ */
+@Component
+public class AppConfigure {
+ /**
+ * 获得应用程序的中文名
+ * @return 返回包含完整内容的字符串
+ */
+ public String getName() {
+ return "智慧学术论文行文指导服务端";
+ }
+
+ /**
+ * 获得应用程序的版本号
+ * @return 返回版本号内容的字符串
+ */
+ public String getVersion() {
+ return "0.0.1_200204";
+ }
+
+ /**
+ * 获得应用程序的英文名
+ * @return 返回包含完整内容的字符串
+ */
+ public String getEnglishName() {
+ return "ePaper";
+ }
+
+ /**
+ * 获得开发小组的名称
+ * @return 包含完整内容的字符串
+ */
+ public String getOrganization() {
+ return "码梦工坊";
+ }
+
+ /**
+ * 文件服务储存路径
+ * @return 字符串
+ */
+ public String getFilePath(){
+ return "./FILES/";
+ }
+
+ /**
+ * 上传的文件的大小限制(字节)
+ * 预设值:16MB
+ * @return 数值
+ */
+ public Integer getFileMaxSize(){
+ return 16000000;
+ }
+
+ /**
+ * 单段的最小字数阈值
+ * @return 数值
+ */
+ public Integer getParagraphMinSize(){
+ return 16;
+ }
+
+ /**
+ * 单段的最大字数阈值
+ * @return 数值
+ */
+ public Integer getParagraphMaxSize(){
+ return 2048;
+ }
+
+ /**
+ * 批处理任务最大句数目阈值
+ * @return 数值
+ */
+ public Long getBPTMaxSentenceNumber() {
+ return 32768L;
+ }
+
+ /**
+ * 批处理任务最少句数目阈值
+ * @return 数值
+ */
+ public Long getBPTMinSentenceNumber(){
+ return 1024L;
+ }
+
+ /**
+ * 子服务器失联等待时间
+ * @return 数值
+ */
+ public Integer gerChildServerRegisterTimeout(){
+ return 300;
+ }
+
+ /**
+ * 子任务等待被加入批处理任务最长时间
+ * @return 数值
+ */
+ public Integer gerMaxTaskDelayTime(){
+ return 30;
+ }
+
+ /**
+ * 批处理任务处理最长时间
+ * @return 数值
+ */
+ public Integer gerMaxBPTProcessDelayTime(){
+ return 300;
+ }
+
+
+ /**
+ * 单页句数
+ * @return 数值
+ */
+ public Integer getSentencePrePage(){
+ return 10;
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/configure/BatchTaskConfiguration.java b/src/main/java/org/codedream/epaper/configure/BatchTaskConfiguration.java
new file mode 100644
index 0000000..8eedd7b
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/BatchTaskConfiguration.java
@@ -0,0 +1,46 @@
+package org.codedream.epaper.configure;
+
+import lombok.Data;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 批处理任务配置,用于配置线程相关参数
+ */
+@Configuration
+@Data
+public class BatchTaskConfiguration {
+
+ //保留的线程池大小
+ private static int corePoolSize = 15;
+
+ //线程池最大大小
+ private static int maxPoolSize = 30;
+
+ //线程最大空闲时间
+ private static int keepAliveTime = 1000;
+
+ //阻塞队列大小
+ private static int workQueueSize = 200;
+
+ private static Long limit = 20000L;
+
+ public static int getCorePoolSize() {
+ return corePoolSize;
+ }
+
+ public static int getMaxPoolSize() {
+ return maxPoolSize;
+ }
+
+ public static int getKeepAliveTime() {
+ return keepAliveTime;
+ }
+
+ public static int getWorkQueueSize() {
+ return workQueueSize;
+ }
+
+ public static Long getLimit() {
+ return limit;
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/configure/ComponentsConfigure.java b/src/main/java/org/codedream/epaper/configure/ComponentsConfigure.java
new file mode 100644
index 0000000..5a99379
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/ComponentsConfigure.java
@@ -0,0 +1,9 @@
+package org.codedream.epaper.configure;
+
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ComponentsConfigure {
+
+
+}
diff --git a/src/main/java/org/codedream/epaper/configure/CustomWebSecurityConfig.java b/src/main/java/org/codedream/epaper/configure/CustomWebSecurityConfig.java
new file mode 100644
index 0000000..9556d6c
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/CustomWebSecurityConfig.java
@@ -0,0 +1,162 @@
+package org.codedream.epaper.configure;
+
+import org.codedream.epaper.component.auth.*;
+import org.codedream.epaper.service.EPUserDetailsService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.session.SessionRegistry;
+import org.springframework.security.core.session.SessionRegistryImpl;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import javax.annotation.Resource;
+
+/**
+ * Spring Security 配置类
+ * 用于Spring Security相关参数的配置
+ */
+@Configuration
+@EnableWebSecurity
+public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Resource
+ EPUserDetailsService aseUserDetailService;
+
+ @Resource
+ EPPasswordEncoder EPPasswordEncoder;
+
+ @Resource
+ EPSecurityAuthenticationProvider EPSecurityAuthenticationProvider;
+
+ @Resource
+ EPAuthenticationSuccessHandler successHandler;
+
+ @Resource
+ EPAuthenticationFailureHandler failureHandler;
+
+ @Resource
+ EPAuthenticationEntryPoint authenticationEntryPoint;
+
+ @Resource
+ EPAccessDeniedHandler accessDeniedHandler;
+
+ /**
+ * HTTP服务器安全配置
+ * @param http HTTP服务器安全对象
+ * @throws Exception 异常
+ */
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .authorizeRequests()
+ .anyRequest().authenticated()
+ .and()
+ .csrf().disable()
+ .logout().permitAll();
+
+ http.exceptionHandling()
+ .authenticationEntryPoint(authenticationEntryPoint)
+ .accessDeniedHandler(accessDeniedHandler);
+
+ // 替换掉原有的UsernamePasswordAuthenticationFilter
+ http.addFilterAt(epUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
+ .addFilterBefore(jsonTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
+
+ http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+
+ }
+
+ /**
+ * 认证器设置
+ * @param auth 认证对象
+ * @throws Exception 异常
+ */
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.authenticationProvider(EPSecurityAuthenticationProvider)
+ .userDetailsService(aseUserDetailService)
+ .passwordEncoder(EPPasswordEncoder);
+ }
+
+ /**
+ * 安全配置
+ * @param web web对象
+ * @throws Exception 异常
+ */
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web
+ .ignoring()
+ .antMatchers(
+ "/assets/**",
+ "/forget/**",
+ "/not_found/**",
+ "/error/**",
+ "/user/**",
+ "/swagger-ui.html",
+ "/webjars/**",
+ "/swagger-resources/**",
+ "/v2/api-docs",
+ "/configuration/ui",
+ "/configuration/security");
+ }
+
+ /**
+ * 注册自定义的UsernamePasswordAuthenticationFilter
+ * @return UsernamePasswordAuthenticationFilter
+ * @throws Exception 异常
+ */
+ @Bean
+ EPJSONTokenAuthenticationFilter jsonTokenAuthenticationFilter() throws Exception {
+ return new EPJSONTokenAuthenticationFilter();
+ }
+
+ /**
+ * 注册自定义的UsernamePasswordAuthenticationFilter
+ * @return UsernamePasswordAuthenticationFilter
+ * @throws Exception 异常
+ */
+ @Bean
+ EPUsernamePasswordAuthenticationFilter epUsernamePasswordAuthenticationFilter() throws Exception {
+ EPUsernamePasswordAuthenticationFilter filter = new EPUsernamePasswordAuthenticationFilter();
+ filter.setAuthenticationSuccessHandler(successHandler);
+ filter.setAuthenticationFailureHandler(failureHandler);
+ filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy(sessionRegistry()));
+ filter.setAllowSessionCreation(true);
+ filter.setRequiresAuthenticationRequestMatcher(
+ new AntPathRequestMatcher("/user/login", "POST"));
+
+ filter.setAuthenticationManager(authenticationManagerBean());
+ return filter;
+ }
+
+ /**
+ * 注册Session会话储存器
+ * @return SessionRegistry
+ */
+ @Bean
+ public SessionRegistry sessionRegistry() {
+ return new SessionRegistryImpl();
+ }
+
+ /**
+ * 登记sessionAuthenticationStrategy
+ * @param sessionRegistry sessionRegistry
+ * @return SessionAuthenticationStrategy
+ */
+ @Bean
+ public SessionAuthenticationStrategy sessionAuthenticationStrategy(SessionRegistry sessionRegistry){
+ return new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry){{
+ setMaximumSessions(1);
+ }};
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/configure/EPApplicationContextInitializer.java b/src/main/java/org/codedream/epaper/configure/EPApplicationContextInitializer.java
new file mode 100644
index 0000000..3348863
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/EPApplicationContextInitializer.java
@@ -0,0 +1,24 @@
+package org.codedream.epaper.configure;
+
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.task.BPTQueue;
+import org.codedream.epaper.model.task.BatchProcessingTask;
+import org.codedream.epaper.repository.task.BatchProcessingTaskRepository;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+
+/**
+ * 服务端程序初始化检查
+ */
+@Slf4j
+public class EPApplicationContextInitializer implements ApplicationContextInitializer {
+
+ @Override
+ public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
+ log.info("EPApplicationContextInitializer Started");
+
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/configure/EPExecutorConfigure.java b/src/main/java/org/codedream/epaper/configure/EPExecutorConfigure.java
new file mode 100644
index 0000000..53c17d1
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/EPExecutorConfigure.java
@@ -0,0 +1,39 @@
+package org.codedream.epaper.configure;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置
+ */
+@Slf4j
+@Configuration
+public class EPExecutorConfigure {
+
+ @Bean(value = "PaPoolExecutor")
+ public Executor asyncServiceExecutor() {
+ ThreadPoolTaskExecutor executor =new ThreadPoolTaskExecutor();
+ // 核心线程数
+ executor.setCorePoolSize(4);
+ // 最大线程数
+ executor.setMaxPoolSize(8);
+ // 队列大小
+ executor.setQueueCapacity(100);
+ // 空闲线程等待工作的超时时间
+ executor.setKeepAliveSeconds(60);
+ // 线程池中的线程的名称前缀
+ executor.setThreadNamePrefix("pa-pool");
+
+ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+ // 线程池初始化
+ executor.initialize();
+
+ return executor;
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/configure/EPSwaggerConfigure.java b/src/main/java/org/codedream/epaper/configure/EPSwaggerConfigure.java
new file mode 100644
index 0000000..9910eff
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/EPSwaggerConfigure.java
@@ -0,0 +1,59 @@
+package org.codedream.epaper.configure;
+
+import com.google.common.collect.Sets;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.ParameterBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.schema.ModelRef;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Parameter;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Swagger2配置类
+ */
+@Configuration
+@EnableSwagger2
+public class EPSwaggerConfigure {
+ @Bean
+ public Docket createRestApi() {
+
+ List pars = new ArrayList();
+
+ pars.add(new ParameterBuilder().name("openid").description("账号openid").hidden(true).order(1)
+ .modelRef(new ModelRef("string")).parameterType("header")
+ .required(false).defaultValue("24310760d8bb8b6542e5a3f16a0d67253214e01ee7ab0e96a1").build());
+ pars.add(new ParameterBuilder().name("signed").description("客户端签名").hidden(true).order(2)
+ .modelRef(new ModelRef("string")).parameterType("header")
+ .required(false).defaultValue("6d4923fca4dcb51f67b85e54a23a8d763d9e02af").build());
+ pars.add(new ParameterBuilder().name("timestamp").description("时间戳").hidden(true).order(3)
+ .modelRef(new ModelRef("string")).parameterType("header")
+ .required(false).defaultValue(Long.toString(new Date().getTime())).build());
+
+ return new Docket(DocumentationType.SWAGGER_2)
+ .protocols(Sets.newHashSet("http"))
+ .apiInfo(apiInfo())
+ .select()
+ .apis(RequestHandlerSelectors.basePackage("org.codedream.epaper.controller"))
+ .paths(PathSelectors.any())
+ .build()
+ .globalOperationParameters(pars);
+ }
+
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title("智慧学术论文行文指导服务端接口定义")
+ .version("0.0.1")
+ .description("用于对服务端接口进行说明")
+ .build();
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/configure/GlobalConfigure.java b/src/main/java/org/codedream/epaper/configure/GlobalConfigure.java
new file mode 100644
index 0000000..29ffd29
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/GlobalConfigure.java
@@ -0,0 +1,15 @@
+package org.codedream.epaper.configure;
+
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Spring 框架全局配置类
+ * 主要用于注册或者管理Bean
+ */
+@Configuration
+public class GlobalConfigure {
+
+
+
+}
diff --git a/src/main/java/org/codedream/epaper/configure/NLPConfigure.java b/src/main/java/org/codedream/epaper/configure/NLPConfigure.java
new file mode 100644
index 0000000..a6b0264
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/NLPConfigure.java
@@ -0,0 +1,26 @@
+package org.codedream.epaper.configure;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 百度API接口所需信息
+ */
+@Configuration
+public class NLPConfigure {
+
+ private static final String APP_ID = "18006539";
+ private static final String APP_KEY = "5sdgAnjElUhfuzH1eHFDnWpz";
+ private static final String SECRET_KEY = "7DYgD3j0KEO3h0Lxrwq16QUWWShvsvKV";
+
+ public static String getAPPId() {
+ return APP_ID;
+ }
+
+ public static String getAppKey() {
+ return APP_KEY;
+ }
+
+ public static String getSecretKey() {
+ return SECRET_KEY;
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/configure/PunctuationConfiguration.java b/src/main/java/org/codedream/epaper/configure/PunctuationConfiguration.java
new file mode 100644
index 0000000..dffcdd0
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/PunctuationConfiguration.java
@@ -0,0 +1,75 @@
+package org.codedream.epaper.configure;
+
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+/**
+ * 句末标点、引用标点等配置
+ */
+@Configuration
+public class PunctuationConfiguration {
+
+ private static final List expPunctuations = new ArrayList() {{
+ add("(");
+ add(")");
+ add("(");
+ add(")");
+ }};
+
+ private static final List endOfSentencePunctuations = new ArrayList() {{
+ add("。");
+ add("?");
+ add("!");
+ }};
+
+ private static final List warningQuotes = new ArrayList() {{
+ add("\"");
+ add("\'");
+ }};
+
+ private static final List frontPunctuations = new ArrayList() {{
+ add("“");
+ add("《");
+ }};
+
+ private static final List backPunctuations = new ArrayList() {{
+ add("”");
+ add("》");
+ add(")");
+ add(")");
+ }};
+
+ private static final List quotesPunctuations = new ArrayList() {{
+ add("“");
+ add("《");
+ add("”");
+ add("》");
+ }};
+
+
+ static public List getExpPunctuations() {
+ return expPunctuations;
+ }
+
+ public static List getEndOfSentencePunctuations() {
+ return endOfSentencePunctuations;
+ }
+
+ public static List getWarningQuotes() {
+ return warningQuotes;
+ }
+
+ public static List getFrontPunctuations() {
+ return frontPunctuations;
+ }
+
+ public static List getBackPunctuations() {
+ return backPunctuations;
+ }
+
+ public static List getQuotesPunctuations() {
+ return quotesPunctuations;
+ }
+
+}
diff --git a/src/main/java/org/codedream/epaper/configure/SingletonAipNlp.java b/src/main/java/org/codedream/epaper/configure/SingletonAipNlp.java
new file mode 100644
index 0000000..5dd2f9c
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/configure/SingletonAipNlp.java
@@ -0,0 +1,32 @@
+package org.codedream.epaper.configure;
+
+import com.baidu.aip.nlp.AipNlp;
+import org.codedream.epaper.configure.NLPConfigure;
+import org.springframework.stereotype.Component;
+
+/**
+ * 百度{@link AipNlp}的接口单例设计,提供线程安全的单例接口调用
+ */
+@Component
+public class SingletonAipNlp {
+ private static AipNlp aipNlp;
+
+ public SingletonAipNlp() {
+ }
+
+ /**
+ * 调用一个单例{@link AipNlp}
+ *
+ * @return 一个单例的AipNlp对象
+ */
+ public static AipNlp getInstance() {
+ if (null == aipNlp) {
+ synchronized (AipNlp.class) {
+ if (null == aipNlp) {
+ aipNlp = new AipNlp(NLPConfigure.getAPPId(), NLPConfigure.getAppKey(), NLPConfigure.getSecretKey());
+ }
+ }
+ }
+ return aipNlp;
+ }
+}
diff --git a/src/main/java/org/codedream/epaper/controller/EPControllerAdvice.java b/src/main/java/org/codedream/epaper/controller/EPControllerAdvice.java
new file mode 100644
index 0000000..5c62519
--- /dev/null
+++ b/src/main/java/org/codedream/epaper/controller/EPControllerAdvice.java
@@ -0,0 +1,116 @@
+package org.codedream.epaper.controller;
+
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+import org.codedream.epaper.component.api.QuickJSONRespond;
+import org.codedream.epaper.component.json.respond.ErrorInfoJSONRespond;
+import org.codedream.epaper.exception.badrequest.AlreadyExistException;
+import org.codedream.epaper.exception.badrequest.IllegalException;
+import org.codedream.epaper.exception.conflict.RelatedObjectsExistException;
+import org.codedream.epaper.exception.innerservererror.FormatException;
+import org.codedream.epaper.exception.innerservererror.HandlingErrorsException;
+import org.codedream.epaper.exception.innerservererror.RuntimeIOException;
+import org.codedream.epaper.exception.notfound.NotFoundException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.annotation.Resource;
+import java.util.Date;
+
+/**
+ * 错误/异常管理机制控制
+ */
+@Slf4j
+@RestControllerAdvice
+@Api(hidden = true)
+public class EPControllerAdvice {
+
+ @Resource
+ private QuickJSONRespond quickJSONRespond;
+
+ /**
+ * 非法请求类异常处理
+ * @param ex 异常
+ * @return 返回对象
+ */
+ @ExceptionHandler(value = {
+ NullPointerException.class,
+ AlreadyExistException.class,
+ IllegalException.class
+ })
+ public ResponseEntity