Bladeren bron

feat: "完善学生写作的功能和接口"

Leonezhurui 2 jaren geleden
bovenliggende
commit
4d3f89354d

+ 10 - 0
src/main/java/com/njuzr/eaibackend/controller/AIController.java

@@ -75,4 +75,14 @@ public class AIController {
         AIEntry entry = aiDialogueService.requestAI(dialogueId, aidto);
         return MyResponse.success(entry);
     }
+
+    @PostMapping("/rewrite")
+    public MyResponse rewrite(
+            @RequestParam Long assignmentId,
+            @RequestParam Long studentId
+    ) {
+        AIEntry entry = aiDialogueService.rewrite(assignmentId, studentId);
+        return MyResponse.success(entry);
+    }
+
 }

+ 6 - 1
src/main/java/com/njuzr/eaibackend/controller/AssignmentController.java

@@ -7,6 +7,7 @@ import com.njuzr.eaibackend.dto.AssignmentUpdateDTO;
 import com.njuzr.eaibackend.enums.AssignmentCompletionStatus;
 import com.njuzr.eaibackend.po.Assignment;
 import com.njuzr.eaibackend.po.MyUserDetails;
+import com.njuzr.eaibackend.service.AIDialogueService;
 import com.njuzr.eaibackend.service.AssignmentService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -26,10 +27,13 @@ public class AssignmentController {
 
     private final AssignmentService assignmentService;
 
+    private final AIDialogueService aiDialogueService;
+
 
     @Autowired
-    public AssignmentController(AssignmentService assignmentService) {
+    public AssignmentController(AssignmentService assignmentService, AIDialogueService aiDialogueService) {
         this.assignmentService = assignmentService;
+        this.aiDialogueService = aiDialogueService;
     }
 
     /**
@@ -119,6 +123,7 @@ public class AssignmentController {
             @RequestParam Long assignmentId
     ) {
         assignmentService.engageAssignment(studentId, assignmentId);
+        aiDialogueService.createAIDialogue(assignmentId, studentId);
         return MyResponse.success("参加作业成功");
     }
 

+ 16 - 0
src/main/java/com/njuzr/eaibackend/controller/TranslateController.java

@@ -0,0 +1,16 @@
+package com.njuzr.eaibackend.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/3/18 - 23:44
+ * @Package: EAI-Backend
+ */
+
+@RestController
+@RequestMapping("/api/translate")
+public class TranslateController {
+
+}

+ 58 - 0
src/main/java/com/njuzr/eaibackend/controller/aggregated/EditorController.java

@@ -0,0 +1,58 @@
+package com.njuzr.eaibackend.controller.aggregated;
+
+import com.njuzr.eaibackend.controller.MyResponse;
+import com.njuzr.eaibackend.service.AIDialogueService;
+import com.njuzr.eaibackend.service.AssignmentService;
+import com.njuzr.eaibackend.vo.AIDialogueVO;
+import com.njuzr.eaibackend.vo.AssignmentVO;
+import com.njuzr.eaibackend.vo.EngagementVO;
+import com.njuzr.eaibackend.vo.aggregated.EditorDataVO;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/3/19 - 19:57
+ * @Package: EAI-Backend
+ */
+
+@RestController
+@RequestMapping("/api/aggregated/editor")
+public class EditorController {
+    private final AssignmentService assignmentService;
+
+    private final AIDialogueService aiDialogueService;
+
+    public EditorController(AssignmentService assignmentService, AIDialogueService aiDialogueService) {
+        this.assignmentService = assignmentService;
+        this.aiDialogueService = aiDialogueService;
+    }
+
+    @GetMapping
+    public MyResponse getEditorData(
+            @RequestParam Long studentId,
+            @RequestParam Long assignmentId
+    ) {
+
+        // 1 作业的详情信息 getAssignmentById,获取作业的基本信息,重点是作业描述、作业附件、截止时间(剩余时间)
+        AssignmentVO assignmentVO = assignmentService.findAssignmentById(assignmentId);
+
+        // 2 学生参与作业的详细信息 getEngagement,获取文件链接和文件key
+        EngagementVO  engagementVO = assignmentService.getEngagement(studentId, assignmentId);
+
+        // 3 学生AI对话的历史记录(存储在mongodb中)
+        AIDialogueVO aiDialogueVO = aiDialogueService.getAIDialogue(assignmentId, studentId, 0, 10);
+
+        // 4 学生查询字典的历史记录(MySQL中)
+
+        EditorDataVO editorDataVO = new EditorDataVO();
+
+        editorDataVO.setAssignmentVO(assignmentVO);
+        editorDataVO.setEngagementVO(engagementVO);
+        editorDataVO.setAiDialogueVO(aiDialogueVO);
+
+        return MyResponse.success(editorDataVO);
+    }
+}

+ 1 - 1
src/main/java/com/njuzr/eaibackend/mapper/AIDialogueMapper.java

@@ -15,7 +15,7 @@ import java.util.List;
  * @Package: EAI-Backend
  */
 
-public interface AIDialogueMapper extends MongoRepository<AIDialogue, Long> {
+public interface AIDialogueMapper extends MongoRepository<AIDialogue, String> {
     Page<AIDialogue> findByAssignmentIdAndUserId(Long assignmentId, Long userId, Pageable pageable);
 
 }

+ 17 - 0
src/main/java/com/njuzr/eaibackend/mapper/AIRewriteRecordMapper.java

@@ -0,0 +1,17 @@
+package com.njuzr.eaibackend.mapper;
+
+import com.njuzr.eaibackend.po.AIDialogue;
+import com.njuzr.eaibackend.po.AIRewriteRecord;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/3/20 - 19:37
+ * @Package: EAI-Backend
+ */
+
+public interface AIRewriteRecordMapper extends MongoRepository<AIRewriteRecord, String> {
+    Page<AIRewriteRecord> findByAssignmentIdAndUserId(Long assignmentId, Long userId, Pageable pageable);
+}

+ 39 - 0
src/main/java/com/njuzr/eaibackend/po/AIRewriteRecord.java

@@ -0,0 +1,39 @@
+package com.njuzr.eaibackend.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.redis.core.index.Indexed;
+
+import java.util.List;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/3/20 - 19:32
+ * @Package: EAI-Backend
+ */
+
+@Data
+@Document(collection = "aiRewriteRecords")
+public class AIRewriteRecord {
+    @Id
+    private String id;
+
+    @Indexed
+    private Long assignmentId; // 外键
+
+    @Indexed
+    private Long userId; // 外键,需要建立(assignmentId, userId)外键索引
+
+    private List<RecordEntry> records;
+
+
+    @Data
+    @AllArgsConstructor
+    public static class RecordEntry {
+        private String role;
+        private String content;
+        private Long timestamp;
+    }
+}

+ 126 - 3
src/main/java/com/njuzr/eaibackend/service/AIDialogueService.java

@@ -3,12 +3,17 @@ package com.njuzr.eaibackend.service;
 import com.njuzr.eaibackend.dto.AIDTO;
 import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.mapper.AIDialogueMapper;
-import com.njuzr.eaibackend.po.AIDialogue;
-import com.njuzr.eaibackend.po.AIEntry;
+import com.njuzr.eaibackend.mapper.AIRewriteRecordMapper;
+import com.njuzr.eaibackend.mapper.AssignmentMapper;
+import com.njuzr.eaibackend.mapper.StudentAssignmentMapper;
+import com.njuzr.eaibackend.po.*;
+import com.njuzr.eaibackend.utils.FileUtil;
 import com.njuzr.eaibackend.utils.ModelMapperUtil;
 import com.njuzr.eaibackend.vo.AIDialogueVO;
+import com.njuzr.eaibackend.vo.AssignmentVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
 import org.springframework.data.domain.*;
 import org.springframework.data.mongodb.core.MongoTemplate;
 import org.springframework.data.mongodb.core.aggregation.*;
@@ -18,7 +23,10 @@ import org.springframework.data.mongodb.core.query.Update;
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
@@ -36,13 +44,24 @@ public class AIDialogueService {
 
     private final AIDialogueMapper aiDialogueMapper;
 
+    private final AIRewriteRecordMapper aiRewriteRecordMapper;
+
     private final AIRequestService aiRequestService;
 
+    private final StudentAssignmentMapper studentAssignmentMapper;
+
+    private final AssignmentMapper assignmentMapper;
+
+    private final FileUtil fileUtil = new FileUtil();
+
     @Autowired
-    public AIDialogueService(MongoTemplate mongoTemplate, AIDialogueMapper aiDialogueMapper, AIRequestService aiRequestService) {
+    public AIDialogueService(MongoTemplate mongoTemplate, AIDialogueMapper aiDialogueMapper, AIRewriteRecordMapper aiRewriteRecordMapper, AIRequestService aiRequestService, StudentAssignmentMapper studentAssignmentMapper, AssignmentMapper assignmentMapper) {
         this.mongoTemplate = mongoTemplate;
         this.aiDialogueMapper = aiDialogueMapper;
+        this.aiRewriteRecordMapper = aiRewriteRecordMapper;
         this.aiRequestService = aiRequestService;
+        this.studentAssignmentMapper = studentAssignmentMapper;
+        this.assignmentMapper = assignmentMapper;
     }
 
     /**
@@ -61,6 +80,21 @@ public class AIDialogueService {
         aiDialogueMapper.save(aiDialogue);
     }
 
+    public void createAIRewriteRecords(Long assignmentId, Long userId) {
+        if (aiRewriteRecordMapper.findByAssignmentIdAndUserId(assignmentId, userId, PageRequest.of(0,1)).hasContent()) {
+            throw MyException.create(HttpStatus.BAD_REQUEST, "AI改写记录已存在");
+        }
+
+        AIRewriteRecord aiRewriteRecord = new AIRewriteRecord();
+        aiRewriteRecord.setAssignmentId(assignmentId);
+        aiRewriteRecord.setUserId(userId);
+        aiRewriteRecord.setRecords(new ArrayList<>());
+
+        aiRewriteRecordMapper.save(aiRewriteRecord);
+    }
+
+
+
     /**
      * 分页返回查找内容
      * @param assignmentId
@@ -93,6 +127,7 @@ public class AIDialogueService {
         }
 
         List<AIDialogue.DialogueEntry> pagedDialogues = sortedDialogues.subList(start, end);
+        pagedDialogues.sort(Comparator.comparing(AIDialogue.DialogueEntry::getTimestamp));
 
         List<AIEntry> entries = pagedDialogues.stream().map(entry -> ModelMapperUtil.map(entry, AIEntry.class)).collect(Collectors.toList());
 
@@ -146,6 +181,84 @@ public class AIDialogueService {
     }
 
 
+    public AIEntry rewrite(Long assignmentId, Long studentId) {
+        // 1 通过assignmentId和studentId找到engagement,获取当前文件链接
+        Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
+        if(engagement == null)
+            throw MyException.create(HttpStatus.BAD_REQUEST, "作业参与不存在");
+
+        // 通过assignmentId找到作业,获取作业描述
+        Assignment assignment = assignmentMapper.selectById(assignmentId);
+        if (assignment == null)
+            throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
+
+        String assignmentDescription = assignment.getDescription();
+
+        // 2 下载文件,并转为String类型
+        String fileUrl = engagement.getFileUrl();
+
+        String content;
+        Resource fileResource = fileUtil.downloadFile(fileUrl);
+        try (InputStream inputStream = fileResource.getInputStream()) {
+            content = fileUtil.readDocxFile(inputStream);
+            log.info("文件内容转换成String为:"+content);
+        } catch (Exception e) {
+            throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "文件转码出错");
+        }
+
+        // 3 构造提示词
+        String promptTemplate = "%s\n"
+                + "假设你现在是一位英文专业的教师,正在批阅一位同学的写作作业,作业描述是:%s"
+                + "请做如下工作:\n"
+                + "1. 满分100分,请综合英文用词、句式等方面,给出评分和评分原因\n"
+                + "2. 针对文章,给出改进的内容,格式为“xxx”可以修改为“xxx”\n"
+                + "3. 给出修改后的完整文章内容\n"
+                + "其余的任何描述都不需要,也不需要任何交互!";
+
+        String prompt = String.format(promptTemplate, content, assignmentDescription);
+        log.info("内置提示词为:"+ prompt);
+
+        // 4 请求AI
+        List<AIEntry> entry = new ArrayList<>();
+        entry.add(new AIEntry("user", prompt));
+        AIRequestService.AIResponse response = aiRequestService.requestChatGLM(entry);
+
+        // 5 获取返回数据
+        String retContent;
+        String role;
+        Long timestamp;
+        try {
+            retContent = response.getChoices().get(0).getMessage().getContent();
+            role = response.getChoices().get(0).getMessage().getRole();
+            timestamp = response.getCreated();
+            log.info("AI返回的数据为:"+retContent);
+        } catch (Exception e) {
+            log.error("AI请求返回的数据内容有误,数据为{}", response);
+            throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"请求失败");
+        }
+
+        // 6 存储数据
+        // 如果没有,则创建一条记录
+        if (!aiRewriteRecordMapper.findByAssignmentIdAndUserId(assignmentId, studentId, PageRequest.of(0,1)).hasContent()) {
+            createAIRewriteRecords(assignmentId, studentId);
+        }
+
+        // 找到这条记录
+        Page<AIRewriteRecord> records = aiRewriteRecordMapper.findByAssignmentIdAndUserId(assignmentId, studentId, PageRequest.of(0,1));
+
+        // 获取记录的Id
+        String recordId = records.getContent().get(0).getId();
+
+        // 更新mongodb数据库
+        AIRewriteRecord.RecordEntry userEntry = new AIRewriteRecord.RecordEntry("user", content, System.currentTimeMillis() / 1000);
+        AIRewriteRecord.RecordEntry recordEntry = new AIRewriteRecord.RecordEntry(role, retContent, timestamp);
+        addRecordEntry(recordId, userEntry, recordEntry);
+
+        return new AIEntry(role, retContent);
+
+    }
+
+
     /**
      * 更新MongoDB数据;通过$push的方式,减少更新的开销;请求成功则会一次性更新两条数据,如果不成功,则两条都不插入。
      * @param dialogueId
@@ -161,6 +274,16 @@ public class AIDialogueService {
             log.error("MongoDB数据库更新出错");
             throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"数据库更新失败");
         }
+    }
 
+    private void addRecordEntry(String recordId, AIRewriteRecord.RecordEntry userEntry, AIRewriteRecord.RecordEntry aiEntry) {
+        try {
+            Query query =  new Query(Criteria.where("id").is(recordId));
+            Update update = new Update().push("dialogues").each(userEntry, aiEntry);
+            mongoTemplate.updateFirst(query, update, AIRewriteRecord.class);
+        } catch (Exception e) {
+            log.error("MongoDB数据库更新出错");
+            throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"数据库更新失败");
+        }
     }
 }

+ 2 - 0
src/main/java/com/njuzr/eaibackend/service/AssignmentService.java

@@ -21,6 +21,8 @@ public interface AssignmentService {
 
     AssignmentVO createAssignment(MyUserDetails user, Long courseId, AssignmentDTO assignmentDTO);
 
+    AssignmentVO findAssignmentById(Long assignmentId);
+
     IPage<AssignmentVO> findAssignmentPage(Page<Assignment> page, AssignmentQueryDTO assignmentQueryDTO);
 
     AssignmentVO updateAssignment(MyUserDetails user, Long assignmentId, AssignmentUpdateDTO assignmentUpdateDTO);

+ 10 - 0
src/main/java/com/njuzr/eaibackend/service/impl/AssignmentServiceImpl.java

@@ -83,12 +83,22 @@ public class AssignmentServiceImpl implements AssignmentService {
         return convertToVO(target);
     }
 
+
+    @Override
+    public AssignmentVO findAssignmentById(Long assignmentId) {
+        QueryWrapper<Assignment> wrapper = new QueryWrapper<>();
+        wrapper.eq("assignment_id", assignmentId);
+        Assignment res = assignmentMapper.selectOne(wrapper);
+        return convertToVO(res);
+    }
+
     @Override
     public IPage<AssignmentVO> findAssignmentPage(Page<Assignment> page, AssignmentQueryDTO assignmentQueryDTO) {
 
         QueryWrapper<Assignment> wrapper = new QueryWrapper<>();
 
         if (assignmentQueryDTO.getAssignmentId() != null) { // assignmentId直接判断是否相等
+            // TODO 检测assignmentId是否正确
             wrapper.eq("assignmentId", assignmentQueryDTO.getAssignmentId());
         }
         if (assignmentQueryDTO.getTeacherId() != null) { // teacherId直接匹配

+ 28 - 0
src/main/java/com/njuzr/eaibackend/utils/FileUtil.java

@@ -3,6 +3,7 @@ package com.njuzr.eaibackend.utils;
 import com.njuzr.eaibackend.exception.MyException;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
 import org.springframework.core.io.Resource;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.client.RestTemplate;
@@ -10,6 +11,8 @@ import org.springframework.web.client.RestTemplate;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
 
 /**
  * @author: Leonezhurui
@@ -20,6 +23,11 @@ import java.io.IOException;
 @Slf4j
 public class FileUtil {
 
+    /**
+     * 通过文件链接下载文件
+     * @param fileUrl
+     * @return
+     */
     public Resource downloadFile(String fileUrl) {
         try {
             RestTemplate restTemplate = new RestTemplate();
@@ -30,6 +38,10 @@ public class FileUtil {
         return null;
     }
 
+    /**
+     * 创建空的docx文件
+     * @return
+     */
     public ByteArrayInputStream createEmptyDocx() {
         try (XWPFDocument document = new XWPFDocument()) {
             // 这里不添加任何内容,保持docx文件为空;同时,将XWPFDocument转换为byte数组以便上传
@@ -42,4 +54,20 @@ public class FileUtil {
             throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "创建空Docx文件失败");
         }
     }
+
+    /**
+     * 从Docx中读取,并转成String
+     * @param inputStream
+     * @return
+     * @throws Exception
+     */
+    public String readDocxFile(InputStream inputStream) throws Exception {
+        XWPFDocument document = new XWPFDocument(inputStream);
+        List<XWPFParagraph> paragraphs = document.getParagraphs();
+        StringBuilder sb = new StringBuilder();
+        for (XWPFParagraph para : paragraphs) {
+            sb.append(para.getText()).append("\n");
+        }
+        return sb.toString();
+    }
 }

+ 19 - 0
src/main/java/com/njuzr/eaibackend/vo/aggregated/EditorDataVO.java

@@ -0,0 +1,19 @@
+package com.njuzr.eaibackend.vo.aggregated;
+
+import com.njuzr.eaibackend.vo.AIDialogueVO;
+import com.njuzr.eaibackend.vo.AssignmentVO;
+import com.njuzr.eaibackend.vo.EngagementVO;
+import lombok.Data;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/3/19 - 20:07
+ * @Package: EAI-Backend
+ */
+
+@Data
+public class EditorDataVO {
+    private AssignmentVO assignmentVO;
+    private EngagementVO engagementVO;
+    private AIDialogueVO aiDialogueVO;
+}