From 7f410361930376a5f240a1ec3540bfde1af18187 Mon Sep 17 00:00:00 2001 From: lbw <1192299468@qq.com> Date: Tue, 16 Dec 2025 19:08:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(lessonplan):=20=E5=AE=9E=E7=8E=B0=E5=9F=BA?= =?UTF-8?q?=E4=BA=8EAI=E7=9A=84=E5=AD=A6=E6=A1=88=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=94=9F=E6=88=90=E4=B8=8E=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增DifyArticleClient工具类,实现基于Dify API的对话与文本生成功能 - 创建LessonPlansService接口及其实现,实现学案按天生成及存储 - 设计LessonPlansDO和StudentLessonPlansDO数据对象及对应MyBatis映射和数据库操作 - 扩展VocabularyBankDO实体及Mapper,支持查询单元词汇和学生未掌握词汇 - 利用deepoove-poi模板技术生成Word格式的学习计划文档,包含词汇、复习和练习 - 开发StringToPlanMapUtil工具类,解析AI返回结果为结构化学案内容 - 新增JUnit测试用例验证AI对话功能及学案生成逻辑正确性 - 更新Spring Boot配置,添加AI接口地址及密钥等参数 - 在前端Vue项目中新建学案页面,路由配置及导航菜单支持学案访问 --- .../service/constant/LessonPlanConstant.java | 10 + .../domain/dataobject/LessonPlansDO.java | 27 ++ .../dataobject/StudentLessonPlansDO.java | 32 ++ .../domain/dataobject/VocabularyBankDO.java | 3 + .../domain/mapper/GradeUnitDOMapper.java | 3 + .../domain/mapper/LessonPlansDOMapper.java | 10 + .../mapper/StudentLessonPlansDOMapper.java | 8 + .../domain/mapper/VocabularyBankDOMapper.java | 6 + .../service/service/LessonPlansService.java | 5 + .../service/plan/LessonPlansServiceImpl.java | 291 ++++++++++++++++++ .../service/utils/DifyArticleClient.java | 118 +++++++ .../service/utils/StringToPlanMapUtil.java | 73 +++++ .../main/resources/config/application-dev.yml | 10 +- .../src/main/resources/generatorConfig.xml | 2 +- .../resources/mapper/GradeUnitDOMapper.xml | 6 + .../resources/mapper/LessonPlansDOMapper.xml | 25 ++ .../mapper/StudentLessonPlansDOMapper.xml | 25 ++ .../mapper/VocabularyBankDOMapper.xml | 49 +++ .../templates/study_plan_review_v1.docx | Bin 0 -> 12006 bytes .../templates/tem_study_plan_v1.docx | Bin 0 -> 15906 bytes .../yinlihupo/enlish/service/ai/AITest.java | 50 +++ .../enlish/service/mapper/PlanTest.java | 28 ++ .../enlish/service/service/plan/PlanTest.java | 18 ++ enlish-vue/src/layouts/components/Header.vue | 7 +- enlish-vue/src/pages/LearningPlan.vue | 22 ++ enlish-vue/src/router/index.js | 8 + 26 files changed, 831 insertions(+), 5 deletions(-) create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/constant/LessonPlanConstant.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/LessonPlansDO.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/StudentLessonPlansDO.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/LessonPlansDOMapper.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/StudentLessonPlansDOMapper.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/service/LessonPlansService.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/service/plan/LessonPlansServiceImpl.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/DifyArticleClient.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/StringToPlanMapUtil.java create mode 100644 enlish-service/src/main/resources/mapper/LessonPlansDOMapper.xml create mode 100644 enlish-service/src/main/resources/mapper/StudentLessonPlansDOMapper.xml create mode 100644 enlish-service/src/main/resources/templates/study_plan_review_v1.docx create mode 100644 enlish-service/src/main/resources/templates/tem_study_plan_v1.docx create mode 100644 enlish-service/src/test/java/com/yinlihupo/enlish/service/ai/AITest.java create mode 100644 enlish-service/src/test/java/com/yinlihupo/enlish/service/mapper/PlanTest.java create mode 100644 enlish-service/src/test/java/com/yinlihupo/enlish/service/service/plan/PlanTest.java create mode 100644 enlish-vue/src/pages/LearningPlan.vue 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 0000000000000000000000000000000000000000..3c8f0438d7b9847fed5ab49d7d224018f7d20a88 GIT binary patch literal 12006 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 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: '学案' // 页面标题 + } } ]