diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/constant/LessonPlanConstant.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/constant/LessonPlanConstant.java new file mode 100644 index 0000000..c0e491e --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/constant/LessonPlanConstant.java @@ -0,0 +1,10 @@ +package com.yinlihupo.enlish.service.constant; + +public interface LessonPlanConstant { + + String TITLE = "Title"; + String PASSAGE = "The Passage"; + String QUIZ = "Quiz"; + String ANSWER_KEY_EXPLANATION = "Answer Key & Explanation"; + String FULL_TRANSLATION = "Full Translation"; +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/LessonPlansDO.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/LessonPlansDO.java new file mode 100644 index 0000000..18cbbac --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/LessonPlansDO.java @@ -0,0 +1,27 @@ +package com.yinlihupo.enlish.service.domain.dataobject; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class LessonPlansDO { + private Integer id; + + private String title; + + private String gradeId; + + private Integer unitId; + + private Date createdAt; + + private String contentDetails; + +} \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/StudentLessonPlansDO.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/StudentLessonPlansDO.java new file mode 100644 index 0000000..29bec11 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/StudentLessonPlansDO.java @@ -0,0 +1,32 @@ +package com.yinlihupo.enlish.service.domain.dataobject; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.Date; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class StudentLessonPlansDO { + private Integer id; + + private Integer studentId; + + private Integer planId; + + private Date startTime; + + private BigDecimal masteryRat; + + private Integer totalCount; + + private String memorizedWordsJson; + + private String unmemorizedWordsJson; + +} \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/VocabularyBankDO.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/VocabularyBankDO.java index f07a287..80744a3 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/VocabularyBankDO.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/VocabularyBankDO.java @@ -10,6 +10,7 @@ import lombok.NoArgsConstructor; @Data @Builder public class VocabularyBankDO { + private Integer id; private String word; @@ -18,6 +19,8 @@ public class VocabularyBankDO { private String pronunciation; + private String pos; + private Integer unitId; } \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/GradeUnitDOMapper.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/GradeUnitDOMapper.java index 6ed5767..5d215e5 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/GradeUnitDOMapper.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/GradeUnitDOMapper.java @@ -1,5 +1,6 @@ package com.yinlihupo.enlish.service.domain.mapper; +import com.yinlihupo.enlish.service.domain.dataobject.GradeUnitDO; import org.apache.ibatis.annotations.Param; import java.util.List; @@ -7,4 +8,6 @@ import java.util.List; public interface GradeUnitDOMapper { List selectUnitIdsByGradeId(@Param("gradeId") Integer gradeId); + + GradeUnitDO selectByUnitId(@Param("unitId") Integer unitId); } \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/LessonPlansDOMapper.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/LessonPlansDOMapper.java new file mode 100644 index 0000000..d45a096 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/LessonPlansDOMapper.java @@ -0,0 +1,10 @@ +package com.yinlihupo.enlish.service.domain.mapper; + +import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO; + +public interface LessonPlansDOMapper { + + void insert(LessonPlansDO lessonPlansDO); + + LessonPlansDO selectById(Integer id); +} \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/StudentLessonPlansDOMapper.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/StudentLessonPlansDOMapper.java new file mode 100644 index 0000000..b706aeb --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/StudentLessonPlansDOMapper.java @@ -0,0 +1,8 @@ +package com.yinlihupo.enlish.service.domain.mapper; + +import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO; + +public interface StudentLessonPlansDOMapper { + + void insert(StudentLessonPlansDO studentLessonPlansDO); +} \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/VocabularyBankDOMapper.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/VocabularyBankDOMapper.java index 13b5e3c..e70c69e 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/VocabularyBankDOMapper.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/VocabularyBankDOMapper.java @@ -14,4 +14,10 @@ public interface VocabularyBankDOMapper { List selectVocabularyBankDOListByIds(@Param("ids") List ids); List selectAllIds(); + + List selectVocabularyBankDOAllByUnitId(@Param("unitId") Integer unitId); + + List selectVocabularyBankListStudentNotMaster(@Param("gradeId") Integer gradeId, @Param("studentId") Integer studentId); + + List selectVocabularyBankListSelfCheck(@Param("gradeId") Integer gradeId, @Param("studentId") Integer studentId, @Param("ids") List ids, @Param("wordCount") Integer wordCount); } \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/LessonPlansService.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/LessonPlansService.java new file mode 100644 index 0000000..bf42577 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/LessonPlansService.java @@ -0,0 +1,5 @@ +package com.yinlihupo.enlish.service.service; + +public interface LessonPlansService { + void generateLessonPlans(Integer studentId, Integer unitId); +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/plan/LessonPlansServiceImpl.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/plan/LessonPlansServiceImpl.java new file mode 100644 index 0000000..79120af --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/plan/LessonPlansServiceImpl.java @@ -0,0 +1,291 @@ +package com.yinlihupo.enlish.service.service.plan; + +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.config.Configure; +import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; +import com.yinlihupo.enlish.service.constant.LessonPlanConstant; +import com.yinlihupo.enlish.service.domain.dataobject.*; +import com.yinlihupo.enlish.service.domain.mapper.*; +import com.yinlihupo.enlish.service.service.LessonPlansService; +import com.yinlihupo.enlish.service.utils.DifyArticleClient; +import com.yinlihupo.enlish.service.utils.StringToPlanMapUtil; +import com.yinlihupo.framework.common.util.JsonUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.*; + +@Service +@Slf4j +public class LessonPlansServiceImpl implements LessonPlansService { + + @Resource + private LessonPlansDOMapper lessonPlansDOMapper; + @Resource + private StudentLessonPlansDOMapper studentLessonPlansDOMapper; + @Resource + private VocabularyBankDOMapper vocabularyBankDOMapper; + @Resource + private UnitDOMapper unitDOMapper; + @Resource + private GradeUnitDOMapper gradeUnitDOMapper; + @Resource + private GradeDOMapper gradeDOMapper; + @Resource + private DifyArticleClient difyArticleClient; + + @Value("${templates.plan.weekday}") + private String planWeekday; + @Value("${templates.plan.weekend}") + private String planWeekend; + + + @Override + public void generateLessonPlans(Integer studentId, Integer unitId) { + List vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOAllByUnitId(unitId); + UnitDO unitDO = unitDOMapper.selectByPrimaryKey(unitId); + GradeUnitDO gradeUnitDO = gradeUnitDOMapper.selectByUnitId(unitId); + GradeDO gradeDO = gradeDOMapper.selectById(gradeUnitDO.getGradeId()); + + // 补差词汇所用词汇的 + List vocabularyBankListStudentNotMaster = getVocabListRandom(vocabularyBankDOMapper + .selectVocabularyBankListStudentNotMaster(gradeUnitDO.getGradeId(), studentId), 50); + int gapSize = vocabularyBankListStudentNotMaster.size(); + int countGap = gapSize / 5; + + int syncSize = vocabularyBankDOS.size(); + int countSync = syncSize / 5; + int checkTotal = 50; + List> weeksSync = new ArrayList<>(); + List> weeksGap = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + List syncVocabList = vocabularyBankDOS.subList(i * countSync, Math.min((i + 1) * countSync, syncSize)); + List gapVocabList = vocabularyBankListStudentNotMaster.subList(i * countGap, Math.min(i * countGap + countGap, gapSize)); + weeksSync.add(syncVocabList); + weeksGap.add(gapVocabList); + List reviewVocabList = new ArrayList<>(); + List checkList = new ArrayList<>(); + // 艾宾浩斯遗忘曲线 + switch (i) { + case 1 -> reviewVocabList.addAll(syncVocabList); + case 2 -> { + reviewVocabList.addAll(weeksSync.get(0)); + reviewVocabList.addAll(weeksSync.get(1)); + + checkList.addAll(weeksSync.get(1)); + checkList.addAll(weeksSync.get(1)); + } + case 3 -> { + reviewVocabList.addAll(weeksSync.get(1)); + reviewVocabList.addAll(weeksGap.get(2)); + + checkList.addAll(weeksSync.get(1)); + checkList.addAll(weeksSync.get(2)); + } + case 4 -> { + reviewVocabList.addAll(weeksSync.get(2)); + reviewVocabList.addAll(weeksGap.get(3)); + + checkList.addAll(weeksSync.get(2)); + checkList.addAll(weeksSync.get(3)); + } + } + List checkWords = vocabularyBankDOMapper + .selectVocabularyBankListSelfCheck(gradeUnitDO.getGradeId(), studentId, checkList.stream().map(VocabularyBankDO::getId).toList(), Math.max(checkTotal - checkList.size(), 0)); + checkList.addAll(checkWords); + + Map lessonPlanMap = null; + try { + lessonPlanMap = generateWeekdayPlans(syncVocabList, gapVocabList, reviewVocabList, checkList, i + 1, gradeDO, unitDO, studentId); + LessonPlansDO lessonPlansDO = LessonPlansDO.builder() + .title(lessonPlanMap.get("title").toString()) + .gradeId(gradeDO.getId().toString()) + .unitId(unitDO.getId()) + .createdAt(new Date()) + .contentDetails(JsonUtils.toJsonString(lessonPlanMap)) + .build(); + lessonPlansDOMapper.insert(lessonPlansDO); + + StudentLessonPlansDO studentLessonPlansDO = StudentLessonPlansDO.builder() + .studentId(studentId) + .planId(lessonPlansDO.getId()) + .build(); + studentLessonPlansDOMapper.insert(studentLessonPlansDO); + + } catch (Exception e) { + throw new RuntimeException(e); + } + log.info("生成第{}天计划成功", i + 1); + } + + + try { + int syncWeekender = syncSize / 2; + for (int i = 0; i < 2; i++) { + List checkList = vocabularyBankDOS.subList(i * syncWeekender, Math.min((i + 1) * syncWeekender, syncSize)); + Map map = generateWeekendPlans(checkList, i + 6, gradeDO, unitDO, studentId); + + LessonPlansDO lessonPlansDO = LessonPlansDO.builder() + .title(map.get("title").toString()) + .gradeId(gradeDO.getId().toString()) + .unitId(unitDO.getId()) + .createdAt(new Date()) + .contentDetails(JsonUtils.toJsonString(map)) + .build(); + lessonPlansDOMapper.insert(lessonPlansDO); + + StudentLessonPlansDO studentLessonPlansDO = StudentLessonPlansDO.builder() + .studentId(studentId) + .planId(lessonPlansDO.getId()) + .build(); + studentLessonPlansDOMapper.insert(studentLessonPlansDO); + log.info("生成第{}天计划成功", i + 6); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Map generateWeekendPlans(List checkList, + int day, + GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws IOException { + + Map data = new HashMap<>(); + data.put("title", "第" + day + "天" + "复习" + gradeDO.getTitle() + unitDO.getTitle() + studentId); + data.put("checkList", checkList); + LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); + Configure config = Configure.builder() + .bind("checkList", policy) + .build(); + + XWPFTemplate template = XWPFTemplate.compile(planWeekend, config); + template.render(data); + template.writeAndClose(new FileOutputStream("C:\\project\\java\\enlish_edu\\enlish\\enlish-service\\src\\main\\resources\\tmp\\word" + "复习" + day + ".docx")); + + return data; + } + + private Map generateWeekdayPlans(List syncVocabList, + List gapVocabList, + List reviewVocabList, + List checkList, + int day, + GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws Exception { + String title = gradeDO.getTitle() + " " + unitDO.getTitle() + " " + "第" + day + "天" + studentId; + Map data = new HashMap<>(); + data.put("title", title); + data.put("syncVocabList", syncVocabList); + data.put("gapVocabList", gapVocabList); + data.put("reviewVocabList", reviewVocabList); + data.put("checkList", checkList); + data.put("checkListAns", checkList); + + // 中译英 + List drillRound1 = new ArrayList<>(syncVocabList); + Collections.shuffle(drillRound1); + data.put("drillRound1", drillRound1); + List drillRound2 = new ArrayList<>(syncVocabList); + Collections.shuffle(drillRound2); + data.put("drillRound2", drillRound2); + List drillRound3 = new ArrayList<>(syncVocabList); + Collections.shuffle(drillRound3); + data.put("drillRound3", drillRound3); + + // 英译中 + List mixedDrill = new ArrayList<>(); + mixedDrill.addAll(syncVocabList); + mixedDrill.addAll(gapVocabList); + mixedDrill.addAll(reviewVocabList); + Collections.shuffle(mixedDrill); + data.put("mixedDrill", mixedDrill); + + // 文章 A + log.info("生成文章 A 中文开始"); + Map mapA = getArticleStringMap(gradeDO, unitDO, studentId, syncVocabList); + log.info("生成文章 A 成功 {}", mapA); + data.put("articleATitle", mapA.get(LessonPlanConstant.TITLE)); + data.put("articleApassage", mapA.get(LessonPlanConstant.PASSAGE)); + data.put("articleAquiz", mapA.get(LessonPlanConstant.QUIZ)); + data.put("articleAans", mapA.get(LessonPlanConstant.ANSWER_KEY_EXPLANATION)); + data.put("articleAtran", mapA.get(LessonPlanConstant.FULL_TRANSLATION)); + + // 文章 B + log.info("生成文章 B 中文开始"); + Map mapB; + List wordsArticleB = new ArrayList<>(); + wordsArticleB.addAll(syncVocabList); + wordsArticleB.addAll(gapVocabList); + wordsArticleB.addAll(reviewVocabList); + mapB = getArticleStringMap(gradeDO, unitDO, studentId, wordsArticleB); + log.info("生成文章 B 成功 {}", mapB); + data.put("articleBTitle", mapB.get(LessonPlanConstant.TITLE)); + data.put("articleBpassage", mapB.get(LessonPlanConstant.PASSAGE)); + data.put("articleBquiz", mapB.get(LessonPlanConstant.QUIZ)); + data.put("articleBans", mapB.get(LessonPlanConstant.ANSWER_KEY_EXPLANATION)); + data.put("articleBtran", mapB.get(LessonPlanConstant.FULL_TRANSLATION)); + + LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); + Configure config = Configure.builder() + .bind("syncVocabList", policy) + .bind("gapVocabList", policy) + .bind("reviewVocabList", policy) + .bind("drillRound1", policy) + .bind("drillRound2", policy) + .bind("drillRound3", policy) + .bind("mixedDrill", policy) + .bind("checkList", policy) + .bind("checkListAns", policy) + .build(); + + XWPFTemplate template = XWPFTemplate.compile(planWeekday, config); + template.render(data); + template.writeAndClose(new FileOutputStream("C:\\project\\java\\enlish_edu\\enlish\\enlish-service\\src\\main\\resources\\tmp\\word" + title + ".docx")); + + return data; + } + + @NonNull + private Map getArticleStringMap(GradeDO gradeDO, UnitDO unitDO, Integer studentId, List words) throws Exception { + Map map; + StringBuilder sb = new StringBuilder(); + words.forEach(word -> sb.append(word.getWord()).append(",")); + sb.deleteCharAt(sb.length() - 1); + String string = sb.toString(); + int i = 0; + do { + log.info("第{}次生成文章中文开始", ++i); + String answer = difyArticleClient.sendChat(string, String.valueOf(studentId) + UUID.randomUUID(), null).getAnswer(); + map = StringToPlanMapUtil.parseTextToMap(answer); + } while (map.get(LessonPlanConstant.TITLE) == null + || map.get(LessonPlanConstant.PASSAGE) == null + || map.get(LessonPlanConstant.QUIZ) == null + || map.get(LessonPlanConstant.ANSWER_KEY_EXPLANATION) == null + || map.get(LessonPlanConstant.FULL_TRANSLATION) == null + ); + return map; + } + + + public List getVocabListRandom(List list, int count) { + + List randomResultList; + + int listSize = list.size(); + if (listSize <= count) { + randomResultList = new ArrayList<>(list); + } else { + List tempList = new ArrayList<>(list); + Collections.shuffle(tempList); // 随机打乱列表顺序 + randomResultList = new ArrayList<>(tempList.subList(0, count)); + + } + + return randomResultList; + } + +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/DifyArticleClient.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/DifyArticleClient.java new file mode 100644 index 0000000..160fa94 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/DifyArticleClient.java @@ -0,0 +1,118 @@ +package com.yinlihupo.enlish.service.utils; // 修改为你的包名 + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import lombok.ToString; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +@Component +public class DifyArticleClient { + + @Value("${ai.key}") + private String apiKey; + @Value("${ai.url}") + private String baseUrl; + private final HttpClient httpClient; + private final ObjectMapper objectMapper; + + // 构造函数 + public DifyArticleClient() { + + this.httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(10)) // 连接超时 + .build(); + this.objectMapper = new ObjectMapper(); + } + + /** + * 发送对话请求 (阻塞模式) + * + * @param query 用户的问题 + * @param userId 用户唯一标识 + * @param conversationId 会话ID (首次传 null 或 空字符串) + * @return DifyResponse 包含回复内容和新的 conversationId + */ + public DifyResponse sendChat(String query, String userId, String conversationId) throws Exception { + String endpoint = this.baseUrl; + + // 1. 构建请求体对象 + ChatRequest payload = new ChatRequest(); + payload.setQuery(query); + payload.setUser(userId); + payload.setResponseMode("blocking"); // 使用阻塞模式,一次性返回 + // 如果有 conversationId,带上它以保持上下文 + if (conversationId != null && !conversationId.isEmpty()) { + payload.setConversationId(conversationId); + } + // 如果你的 Dify 应用没有定义变量,inputs 传空 Map 即可,但字段必须存在 + payload.setInputs(new HashMap<>()); + + // 2. 序列化为 JSON 字符串 + String jsonBody = objectMapper.writeValueAsString(payload); + + // 3. 构建 HTTP 请求 + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(endpoint)) + .header("Authorization", "Bearer " + this.apiKey) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .timeout(Duration.ofSeconds(30)) // 读取超时 + .build(); + + // 4. 发送请求 + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + // 5. 检查状态码 + if (response.statusCode() != 200) { + throw new RuntimeException("Dify 请求失败: HTTP " + response.statusCode() + " | Body: " + response.body()); + } + + // 6. 反序列化响应体 + return objectMapper.readValue(response.body(), DifyResponse.class); + } + + // ================= 内部类:DTO (数据传输对象) ================= + + // 请求体结构 + @Data + @ToString + public static class ChatRequest { + private Map inputs; + private String query; + @JsonProperty("response_mode") + private String responseMode; + @JsonProperty("conversation_id") + private String conversationId; + private String user; + + } + + // 响应体结构 + // ignoreUnknown = true 非常重要:Dify 返回很多元数据,我们只映射需要的字段,防止报错 + @JsonIgnoreProperties(ignoreUnknown = true) + @Data + @ToString + public static class DifyResponse { + private String answer; // 核心回复内容 + + @JsonProperty("conversation_id") + private String conversationId; // 会话 ID + + @JsonProperty("message_id") + private String messageId; + + private long created_at; + + } +} \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/StringToPlanMapUtil.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/StringToPlanMapUtil.java new file mode 100644 index 0000000..a877baf --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/StringToPlanMapUtil.java @@ -0,0 +1,73 @@ +package com.yinlihupo.enlish.service.utils; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class StringToPlanMapUtil { + + public static Map parseTextToMap(String text) { + // 使用 LinkedHashMap 保持插入顺序 + Map map = new LinkedHashMap<>(); + + // 正则表达式:匹配行首的 **Key:** 格式 + Pattern headerPattern = Pattern.compile("^\\s*\\*\\*(.+?):\\*\\*\\s*(.*)"); + + String currentKey = null; + StringBuilder currentValue = new StringBuilder(); + + try (Scanner scanner = new Scanner(text)) { + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + Matcher matcher = headerPattern.matcher(line); + + if (matcher.matches()) { + // 如果发现新的 Header + + // 1. 保存上一个 Key-Value 对(如果存在) + if (currentKey != null) { + // 修改处:在存入 Map 前,调用 cleanMdBold 去除粗体语法 + map.put(currentKey, cleanMdBold(currentValue.toString())); + } + + // 2. 更新当前的 Key + currentKey = matcher.group(1).trim(); + + // 3. 重置 StringBuilder + currentValue = new StringBuilder(); + String remainingText = matcher.group(2); + if (!remainingText.isEmpty()) { + currentValue.append(remainingText).append("\n"); + } + } else { + // 如果不是 Header 行,追加到当前 Value 中 + if (currentKey != null) { + currentValue.append(line).append("\n"); + } + } + } + + // 循环结束后,保存最后一个 Key-Value 对 + if (currentKey != null) { + // 修改处:同样在最后存入时去除粗体语法 + map.put(currentKey, cleanMdBold(currentValue.toString())); + } + } + + return map; + } + + /** + * 辅助方法:去除字符串两端的空白以及内部的 Markdown 粗体符号 (**) + */ + private static String cleanMdBold(String text) { + if (text == null) { + return ""; + } + // 1. 替换掉所有的 ** 符号 + // 2. 去除首尾空白 + return text.replace("**", "").trim(); + } +} \ No newline at end of file diff --git a/enlish-service/src/main/resources/config/application-dev.yml b/enlish-service/src/main/resources/config/application-dev.yml index 644948d..a69c256 100644 --- a/enlish-service/src/main/resources/config/application-dev.yml +++ b/enlish-service/src/main/resources/config/application-dev.yml @@ -25,5 +25,13 @@ templates: word: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\assessment_v5.docx count: 100 data: C:\project\tess + plan: + weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v1.docx + weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v1.docx + plan_day: 7 tmp: - png: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\tmp\png\ \ No newline at end of file + png: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\tmp\png\ + +ai: + key: app-loC6IrJpj4cS54MAYp73QtGl + url: https://chat.cosonggle.com/v1/chat-messages \ No newline at end of file diff --git a/enlish-service/src/main/resources/generatorConfig.xml b/enlish-service/src/main/resources/generatorConfig.xml index 58211a3..0cea3ea 100644 --- a/enlish-service/src/main/resources/generatorConfig.xml +++ b/enlish-service/src/main/resources/generatorConfig.xml @@ -45,7 +45,7 @@ targetProject="src/main/java"/> - + + \ No newline at end of file diff --git a/enlish-service/src/main/resources/mapper/LessonPlansDOMapper.xml b/enlish-service/src/main/resources/mapper/LessonPlansDOMapper.xml new file mode 100644 index 0000000..1ff8f9c --- /dev/null +++ b/enlish-service/src/main/resources/mapper/LessonPlansDOMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + insert into lesson_plans (title, grade_id, unit_id, content_details, created_at) + values (#{title}, #{gradeId}, #{unitId}, #{contentDetails}, now()) + + + + \ No newline at end of file diff --git a/enlish-service/src/main/resources/mapper/StudentLessonPlansDOMapper.xml b/enlish-service/src/main/resources/mapper/StudentLessonPlansDOMapper.xml new file mode 100644 index 0000000..73def9a --- /dev/null +++ b/enlish-service/src/main/resources/mapper/StudentLessonPlansDOMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + insert into student_lesson_plans (student_id, plan_id, start_time, mastery_rat, total_count) + values (#{studentId,jdbcType=INTEGER}, #{planId,jdbcType=INTEGER}, + now(), 0.0, 0 + ) + + + \ No newline at end of file diff --git a/enlish-service/src/main/resources/mapper/VocabularyBankDOMapper.xml b/enlish-service/src/main/resources/mapper/VocabularyBankDOMapper.xml index 13d1285..d2b8403 100644 --- a/enlish-service/src/main/resources/mapper/VocabularyBankDOMapper.xml +++ b/enlish-service/src/main/resources/mapper/VocabularyBankDOMapper.xml @@ -7,6 +7,7 @@ + @@ -28,6 +29,9 @@ unit_id, + + pos, + @@ -45,6 +49,9 @@ #{unitId,jdbcType=INTEGER}, + + #{pos,jdbcType=VARCHAR}, + @@ -69,4 +76,46 @@ from vocabulary_bank + + + + + + \ No newline at end of file diff --git a/enlish-service/src/main/resources/templates/study_plan_review_v1.docx b/enlish-service/src/main/resources/templates/study_plan_review_v1.docx new file mode 100644 index 0000000..3c8f043 Binary files /dev/null and b/enlish-service/src/main/resources/templates/study_plan_review_v1.docx differ diff --git a/enlish-service/src/main/resources/templates/tem_study_plan_v1.docx b/enlish-service/src/main/resources/templates/tem_study_plan_v1.docx new file mode 100644 index 0000000..060d38f Binary files /dev/null and b/enlish-service/src/main/resources/templates/tem_study_plan_v1.docx differ diff --git a/enlish-service/src/test/java/com/yinlihupo/enlish/service/ai/AITest.java b/enlish-service/src/test/java/com/yinlihupo/enlish/service/ai/AITest.java new file mode 100644 index 0000000..b1a46b9 --- /dev/null +++ b/enlish-service/src/test/java/com/yinlihupo/enlish/service/ai/AITest.java @@ -0,0 +1,50 @@ +package com.yinlihupo.enlish.service.ai; + +import com.yinlihupo.enlish.service.utils.DifyArticleClient; +import com.yinlihupo.enlish.service.utils.StringToPlanMapUtil; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.IOException; +import java.util.Map; + +@SpringBootTest +public class AITest { + + @Resource + private DifyArticleClient client; + + @Test + public void test1() throws IOException { + + + try { + // 2. 第一轮对话 (没有 conversation_id) + System.out.println("--- Round 1 ---"); + String userId = "user-1001"; + DifyArticleClient.DifyResponse response1 = client.sendChat("ruler, pencil, eraser, crayon, bag, pen, book, red, green, yellow, blue, face, ear, eye, nose, mouth, duck, pig, cat, bear, dog, elephant, monkey, bird, tiger, panda, bread, juice, egg, milk", userId, null); + + //System.out.println("AI 回复: " + response1.getAnswer()); + System.out.println("当前会话ID: " + response1.getConversationId()); + +// // 3. 第二轮对话 (传入上一轮的 conversation_id 以保持记忆) +// System.out.println("\n--- Round 2 ---"); +// // 注意这里传入了 response1.getConversationId() +// DifyClient.DifyResponse response2 = client.sendChat("我刚才说了我叫什么?", userId, response1.getConversationId()); +// +// System.out.println("AI 回复: " + response2.getAnswer()); + + System.out.println("\n--- Round 2 ---"); + Map stringStringMap = StringToPlanMapUtil.parseTextToMap(response1.getAnswer()); + System.out.println(stringStringMap.get("Title")); + System.out.println(stringStringMap.get("The Passage")); + System.out.println(stringStringMap.get("Quiz")); + System.out.println(stringStringMap.get("Answer Key & Explanation")); + System.out.println(stringStringMap.get("Full Translation")); + + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/enlish-service/src/test/java/com/yinlihupo/enlish/service/mapper/PlanTest.java b/enlish-service/src/test/java/com/yinlihupo/enlish/service/mapper/PlanTest.java new file mode 100644 index 0000000..bf98d4a --- /dev/null +++ b/enlish-service/src/test/java/com/yinlihupo/enlish/service/mapper/PlanTest.java @@ -0,0 +1,28 @@ +package com.yinlihupo.enlish.service.mapper; + +import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO; +import com.yinlihupo.enlish.service.domain.mapper.LessonPlansDOMapper; +import com.yinlihupo.framework.common.util.JsonUtils; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Map; + +@SpringBootTest +public class PlanTest { + + @Resource + private LessonPlansDOMapper lessonPlansDOMapper; + + @Test + public void test() throws Exception { + LessonPlansDO lessonPlansDO = lessonPlansDOMapper.selectById(21); + Map stringObjectMap = JsonUtils.parseMap(lessonPlansDO.getContentDetails(), String.class, Object.class); + for (Map.Entry entry : stringObjectMap.entrySet()) { + System.out.println(entry.getKey()); + System.out.println(entry.getValue()); + System.out.println("------------------------"); + } + } +} diff --git a/enlish-service/src/test/java/com/yinlihupo/enlish/service/service/plan/PlanTest.java b/enlish-service/src/test/java/com/yinlihupo/enlish/service/service/plan/PlanTest.java new file mode 100644 index 0000000..371b8eb --- /dev/null +++ b/enlish-service/src/test/java/com/yinlihupo/enlish/service/service/plan/PlanTest.java @@ -0,0 +1,18 @@ +package com.yinlihupo.enlish.service.service.plan; + +import com.yinlihupo.enlish.service.service.LessonPlansService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class PlanTest { + + @Resource + private LessonPlansService lessonPlansService; + + @Test + public void test() { + lessonPlansService.generateLessonPlans(2, 146); + } +} diff --git a/enlish-vue/src/layouts/components/Header.vue b/enlish-vue/src/layouts/components/Header.vue index cd3c165..7b7733d 100644 --- a/enlish-vue/src/layouts/components/Header.vue +++ b/enlish-vue/src/layouts/components/Header.vue @@ -45,10 +45,11 @@
  • - - Marketplace - + 学案 +
  • +
    + + +
    +
    + + + + 学习计划 + 学习记录 + + + +
    +
    + + + diff --git a/enlish-vue/src/router/index.js b/enlish-vue/src/router/index.js index d5c524e..1215653 100644 --- a/enlish-vue/src/router/index.js +++ b/enlish-vue/src/router/index.js @@ -1,5 +1,6 @@ import Index from '@/pages/index.vue' import Uploadpng from '@/pages/uploadpng.vue' +import LearningPlan from '@/pages/LearningPlan.vue' import Class from '@/pages/class.vue' import { createRouter, createWebHashHistory } from 'vue-router' @@ -18,6 +19,13 @@ const routes = [ meta: { // meta 信息 title: '上传图片' // 页面标题 } + }, + { + path: '/learningplan', // 路由地址 + component: LearningPlan, // 对应组件 + meta: { // meta 信息 + title: '学案' // 页面标题 + } } ]