Compare commits
35 Commits
260c2c79f1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fcf381b8f1 | |||
| fb29acc145 | |||
| 7182371c92 | |||
| 09b326c07a | |||
| 49963bb49c | |||
| bf2a80917c | |||
| deabd5f7f5 | |||
| 0802f6fe70 | |||
| 679241588f | |||
| fe7128dd4e | |||
| 1184ea7895 | |||
| e468be74b7 | |||
| 57166af2f4 | |||
| fcb8ac9c22 | |||
| 7f2fda16ec | |||
| 9cd43c0e74 | |||
| b86f37443c | |||
| 36e5231c6c | |||
| 868e0bb7bd | |||
| 504dd8d964 | |||
| 6277e3ab42 | |||
| 0f5169c1d7 | |||
| 5ebf40101d | |||
| 5858bf2ecc | |||
| bddf6c0936 | |||
| 340bc5b5e3 | |||
| 2d76ed507e | |||
| 494ab77486 | |||
| d3cfa80613 | |||
| 66eb02cb08 | |||
| 0b0311d2d9 | |||
| 7b68184787 | |||
| bc9334f5ab | |||
| aff862d161 | |||
| 15e909c318 |
BIN
debug_roi.jpg
BIN
debug_roi.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
5728
docx/enlish_v2.sql
Normal file
5728
docx/enlish_v2.sql
Normal file
File diff suppressed because one or more lines are too long
11395
docx/enlish_v3.sql
Normal file
11395
docx/enlish_v3.sql
Normal file
File diff suppressed because one or more lines are too long
@@ -153,6 +153,25 @@
|
||||
<artifactId>java-jwt</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-openai</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>javase</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -24,22 +24,16 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
.back();
|
||||
|
||||
SaRouter.match("/**")
|
||||
.notMatch("/class/list")
|
||||
.notMatch("/exam/words/get")
|
||||
.notMatch("/exam/words/detail")
|
||||
.notMatch("/exam/words/student/history")
|
||||
.notMatch("grade/list")
|
||||
.notMatch("/student/list")
|
||||
.notMatch("/student/detail")
|
||||
.notMatch("/studentLessonPlans/list")
|
||||
.notMatch("/studentLessonPlans/history")
|
||||
.notMatch("/student/analyze")
|
||||
.notMatch("/unit/list")
|
||||
.notMatch("/vocabulary/list")
|
||||
.notMatch("/plan/download")
|
||||
.notMatch("/login/**")
|
||||
.notMatch("/plan/word/voice")
|
||||
.notMatch("/plan/word/voice/tts")
|
||||
.check(r -> StpUtil.checkLogin());
|
||||
|
||||
SaRouter.match("/admin/**")
|
||||
.notMatch("/plan/word/voice")
|
||||
.notMatch("/plan/word/voice/tts")
|
||||
.check(r -> StpUtil.checkRole("root"));
|
||||
|
||||
}))
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns("/error");
|
||||
|
||||
@@ -28,7 +28,11 @@ public class StpInterfaceImpl implements StpInterface {
|
||||
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
return userToRole((Long) loginId) ;
|
||||
long l = 0L;
|
||||
if (loginId instanceof String loginIdStr) {
|
||||
l = Long.parseLong(loginIdStr);
|
||||
}
|
||||
return userToRole(l);
|
||||
}
|
||||
|
||||
private List<String> userToRole(Long userId) {
|
||||
|
||||
@@ -31,7 +31,15 @@ public class ExamWordsConstant {
|
||||
public static final int ZONE_F_SIZE = 7;
|
||||
|
||||
|
||||
// 摸底
|
||||
public static final int EXAM_TYPE_BASELINE = 1;
|
||||
// 中期
|
||||
public static final int EXAM_TYPE_MIDTERM = 2;
|
||||
// 期末
|
||||
public static final int EXAM_TYPE_FINAL = 3;
|
||||
// 小测
|
||||
public static final int EXAM_TYPE_TEST = 4;
|
||||
|
||||
|
||||
public static int getZoneA(int gradeId) {
|
||||
return switch (gradeId) {
|
||||
@@ -129,4 +137,17 @@ public class ExamWordsConstant {
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
public static String day2Chinese(int day) {
|
||||
return switch (day) {
|
||||
case 1 -> "一";
|
||||
case 2 -> "二";
|
||||
case 3 -> "三";
|
||||
case 4 -> "四";
|
||||
case 5 -> "五";
|
||||
case 6 -> "六";
|
||||
case 7 -> "七";
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package com.yinlihupo.enlish.service.constant;
|
||||
|
||||
public interface LessonPlanConstant {
|
||||
public class LessonPlanConstant {
|
||||
|
||||
String TITLE = "Title";
|
||||
String PASSAGE = "The Passage";
|
||||
String QUIZ = "Quiz";
|
||||
String ANSWER_KEY_EXPLANATION = "Answer Key & Explanation";
|
||||
String FULL_TRANSLATION = "Full Translation";
|
||||
public static final String TITLE = "Title";
|
||||
public static final String PASSAGE = "ThePassage";
|
||||
public static final String QUIZ = "Quiz";
|
||||
public static final String ANSWER_KEY_EXPLANATION = "AnswerKey&Explanation";
|
||||
public static final String FULL_TRANSLATION = "FullTranslation";
|
||||
|
||||
// 正在生成学案标识
|
||||
public static final String GENERATING_PLAN = "GeneratingPlan";
|
||||
|
||||
public static String buildGeneratePlanContent(Integer studentId) {
|
||||
return GENERATING_PLAN + ":" + studentId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@ public class UserRedisConstants {
|
||||
|
||||
public static final String USER_LOGIN_CODE = "user:login:code:";
|
||||
|
||||
public static final String USER_INVITATION_CODE = "user:invitation:code:";
|
||||
|
||||
public static String buildUserLoginCode(String phone) {
|
||||
return USER_LOGIN_CODE + phone;
|
||||
}
|
||||
|
||||
public static String buildUserInvitationCode(String code) {
|
||||
return USER_INVITATION_CODE + code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.yinlihupo.enlish.service.controller;
|
||||
|
||||
import com.yinlihupo.enlish.service.constant.UserRedisConstants;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.RoleDO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||
import com.yinlihupo.enlish.service.model.vo.admin.CreateInvitationCodeReqVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.admin.CreateInvitationCodeRspVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.user.CreateUserReqVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.user.FindUserListRepVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.user.FindUserListRspVO;
|
||||
@@ -11,6 +14,7 @@ import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||
import com.yinlihupo.framework.common.response.PageResponse;
|
||||
import com.yinlihupo.framework.common.response.Response;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -19,6 +23,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RequestMapping("/admin/")
|
||||
@RestController
|
||||
@@ -30,6 +36,8 @@ public class AdminController {
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Resource
|
||||
private RoleService roleService;
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@PostMapping("user/list")
|
||||
@ApiOperationLog(description = "查询用户列表")
|
||||
@@ -66,4 +74,18 @@ public class AdminController {
|
||||
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@PostMapping("user/create/invitation/code")
|
||||
@ApiOperationLog(description = "创建邀请码")
|
||||
public Response<CreateInvitationCodeRspVO> createInvitationCode(@RequestBody CreateInvitationCodeReqVO createInvitationCodeReqVO) {
|
||||
|
||||
String code = String.valueOf(new Random().nextInt(1000000));
|
||||
Integer limit = createInvitationCodeReqVO.getLimit();
|
||||
Integer expire = createInvitationCodeReqVO.getExpire();
|
||||
|
||||
redisTemplate.opsForValue().set(UserRedisConstants.buildUserInvitationCode(code), limit);
|
||||
redisTemplate.expire(UserRedisConstants.buildUserInvitationCode(code), expire, TimeUnit.DAYS);
|
||||
|
||||
return Response.success(CreateInvitationCodeRspVO.builder().invitationCode(code).build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,25 +42,27 @@ public class ExamWordsController {
|
||||
|
||||
@PostMapping("generate")
|
||||
public void generateFeltExamWords(@RequestBody GenerateExamWordsReqVO generateExamWordsReqVO, HttpServletResponse response) {
|
||||
Integer gradeId = generateExamWordsReqVO.getGradeId();
|
||||
Integer level = generateExamWordsReqVO.getLevel();
|
||||
Integer type = generateExamWordsReqVO.getType();
|
||||
Integer studentId = generateExamWordsReqVO.getStudentId();
|
||||
if (studentId == null || gradeId == null || level == null) {
|
||||
if (studentId == null) {
|
||||
throw new RuntimeException("参数错误");
|
||||
}
|
||||
try {
|
||||
ExamWordsDO examWordsDO = examWordsService.generateExamWords(gradeId, level, studentId, type);
|
||||
ExamWordsDO examWordsDO = examWordsService.generateExamWords(studentId, type);
|
||||
if (examWordsDO == null || examWordsDO.getWordIds().isEmpty()) {
|
||||
throw new RuntimeException("没有单词");
|
||||
}
|
||||
|
||||
List<VocabularyBankDO> vocabularyBankDOS = vocabularyService.findVocabularyBankDOListById(examWordsDO.getWordIds());
|
||||
List<Word> assessmentWords = vocabularyBankDOS.stream().map(vocabularyBankDO -> Word.builder()
|
||||
.id(vocabularyBankDO.getId())
|
||||
.title(vocabularyBankDO.getWord())
|
||||
.definition(vocabularyBankDO.getDefinition())
|
||||
.definition(vocabularyBankDO.getDefinition().length() > 6 ? vocabularyBankDO.getDefinition().substring(0, 6) : vocabularyBankDO.getDefinition())
|
||||
.build()).toList();
|
||||
|
||||
// bug: 获取单词后,单词的id会乱序、 需要重新更新考试记录中的 id
|
||||
examWordsDO.setWordIds(assessmentWords.stream().map(Word::getId).toList());
|
||||
examWordsService.updateExamWordsWordIdsOrder(examWordsDO);
|
||||
log.info("生成试卷成功 {}", examWordsDO);
|
||||
List<StudentDetail> studentDetailList = studentService.getStudentDetailList(Collections.singletonList(studentId));
|
||||
List<Map<String, Object>> maps = studentDetailList.stream().map(studentDetail -> {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
@@ -70,10 +72,16 @@ public class ExamWordsController {
|
||||
data.put("examStr", examWordsDO.getTitle());
|
||||
data.put("words", assessmentWords);
|
||||
data.put("answer", assessmentWords);
|
||||
|
||||
List<Word> words1 = assessmentWords.subList(0, assessmentWords.size() / 2);
|
||||
List<Word> words2 = assessmentWords.subList(assessmentWords.size() / 2, assessmentWords.size());
|
||||
data.put("words1", words1);
|
||||
data.put("words2", words2);
|
||||
|
||||
return data;
|
||||
}).toList();
|
||||
|
||||
WordExportUtil.generateExamWords(maps, response, templateWordPath);
|
||||
WordExportUtil.generateExamWords(maps, examWordsDO, response, templateWordPath);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -105,12 +113,17 @@ public class ExamWordsController {
|
||||
PageResponse<ExamWordsResultRspVO> getExamWordsResult(@RequestBody ExamWordsResultReqVO examWordsResultReqVO) {
|
||||
Integer page = examWordsResultReqVO.getPage();
|
||||
Integer size = examWordsResultReqVO.getSize();
|
||||
Integer classId = examWordsResultReqVO.getClassId();
|
||||
Integer gradeId = examWordsResultReqVO.getGradeId();
|
||||
String studentName = examWordsResultReqVO.getStudentName();
|
||||
Integer total = examWordsJudgeService.getExamWordsJudgeResultCount();
|
||||
List<ExamWordsJudgeResultDO> examWordsJudgeResult = examWordsJudgeService.getExamWordsJudgeResult(page, size);
|
||||
List<ExamWordsJudgeResultDO> examWordsJudgeResult = examWordsJudgeService.getExamWordsJudgeResult(page, size, classId, gradeId, studentName);
|
||||
List<ExamWordsResultRspVO> list = examWordsJudgeResult.stream().map(examWordsJudgeResultDO -> ExamWordsResultRspVO
|
||||
.builder()
|
||||
.id(examWordsJudgeResultDO.getId())
|
||||
.studentId(examWordsJudgeResultDO.getStudentId())
|
||||
.studentName(examWordsJudgeResultDO.getStudentId() != null ? studentService.getStudentById(examWordsJudgeResultDO.getStudentId()).getName() : "")
|
||||
.examWordsTitle(examWordsJudgeResultDO.getExamWordsId() != null ? examWordsService.getExamWordsDOById(examWordsJudgeResultDO.getExamWordsId()).getTitle() : "")
|
||||
.examWordsId(examWordsJudgeResultDO.getExamWordsId())
|
||||
.startDate(examWordsJudgeResultDO.getStartDate())
|
||||
.correctWordCount(examWordsJudgeResultDO.getCorrectWordCount())
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
package com.yinlihupo.enlish.service.controller;
|
||||
|
||||
import com.yinlihupo.enlish.service.constant.LessonPlanConstant;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||
import com.yinlihupo.enlish.service.model.vo.plan.AddLessonPlanReqVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.plan.DownLoadLessonPlanReqVO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||
import com.yinlihupo.enlish.service.model.vo.plan.*;
|
||||
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
||||
import com.yinlihupo.enlish.service.utils.TTSUtil;
|
||||
import com.yinlihupo.enlish.service.utils.WordExportUtil;
|
||||
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||
import com.yinlihupo.framework.common.response.PageResponse;
|
||||
import com.yinlihupo.framework.common.response.Response;
|
||||
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -28,8 +33,13 @@ public class LessonPlanController {
|
||||
@Resource
|
||||
private LessonPlansService lessonPlanService;
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Resource(name = "taskExecutor")
|
||||
private Executor taskExecutor;
|
||||
@Resource
|
||||
private TTSUtil ttsUtil;
|
||||
|
||||
@Value("${templates.plan.weekday}")
|
||||
private String planWeekday;
|
||||
@@ -41,8 +51,12 @@ public class LessonPlanController {
|
||||
public Response<String> generateLessonPlan(@RequestBody AddLessonPlanReqVO addLessonPlanReqVO) {
|
||||
Integer studentId = addLessonPlanReqVO.getStudentId();
|
||||
Integer unitId = addLessonPlanReqVO.getUnitId();
|
||||
Integer wordSize = addLessonPlanReqVO.getWordSize();
|
||||
try {
|
||||
taskExecutor.execute(() -> lessonPlanService.generateLessonPlans(studentId, unitId));
|
||||
if (redisTemplate.opsForValue().get(LessonPlanConstant.buildGeneratePlanContent(studentId)) != null) {
|
||||
throw new RuntimeException("学案正在生成,请耐心等待");
|
||||
}
|
||||
taskExecutor.execute(() -> lessonPlanService.generateLessonPlans(studentId, unitId, wordSize));
|
||||
return Response.success("生成学案成功,请等待 10 分钟");
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
@@ -59,13 +73,63 @@ public class LessonPlanController {
|
||||
try {
|
||||
Map<String, Object> map = JsonUtils.parseMap(lessonPlanById.getContentDetails(), String.class, Object.class);
|
||||
if (!lessonPlanById.getTitle().contains("复习")) {
|
||||
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById.getTitle(), response, planWeekday, true);
|
||||
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById, response, planWeekday, true);
|
||||
} else {
|
||||
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById.getTitle(), response, planWeekend, false);
|
||||
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById, response, planWeekend, false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("word/voice")
|
||||
@ApiOperationLog(description = "获取单词")
|
||||
public Response<FindWordVoiceRspVO> findPlanWordVoice(@RequestBody FindWordVoiceReqVO findWordVoiceReqVO) {
|
||||
Integer id = findWordVoiceReqVO.getPlanId();
|
||||
LessonPlansDO lessonPlanById = lessonPlanService.findLessonPlanById(id);
|
||||
try {
|
||||
Map<String, Object> map = JsonUtils.parseMap(lessonPlanById.getContentDetails(), String.class, Object.class);
|
||||
Object syncVocabList = map.get("syncVocabList");
|
||||
List<VocabularyBankDO> list = JsonUtils.parseList(JsonUtils.toJsonString(syncVocabList), VocabularyBankDO.class);
|
||||
List<String> words = list.stream().map(VocabularyBankDO::getWord).toList();
|
||||
|
||||
return Response.success(FindWordVoiceRspVO.builder().words(words).build());
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
return Response.fail("获取单词失败");
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("word/voice/tts")
|
||||
public void findPlanWordVoiceTTS(@RequestBody FindWordTTSVoiceReqVO findWordVoiceReqVO, HttpServletResponse response) {
|
||||
ttsUtil.generateWordVoice(findWordVoiceReqVO.getText(), response);
|
||||
}
|
||||
|
||||
@PostMapping("check")
|
||||
@ApiOperationLog(description = "检测学案是否在生成")
|
||||
public Response<String> checkLessonPlan(@RequestBody FindIsGeneratePlanReqVO findIsGeneratePlanReqVO) {
|
||||
Integer studentId = findIsGeneratePlanReqVO.getStudentId();
|
||||
String key = LessonPlanConstant.buildGeneratePlanContent(studentId);
|
||||
if (redisTemplate.opsForValue().get(key) != null) {
|
||||
return Response.fail();
|
||||
}
|
||||
return Response.success("学案生成完成");
|
||||
}
|
||||
|
||||
@PostMapping("student/list")
|
||||
@ApiOperationLog(description = "查询学生学案")
|
||||
public Response<FindPlanStudentListRspVO> findStudentPlans(@RequestBody FindPlanStudentReqVO findPlanStudentReqVO) {
|
||||
List<LessonPlansDO> lessonPlansDOS = lessonPlanService.findLessonPlansByStudentId(findPlanStudentReqVO.getStudentId());
|
||||
List<LessonPlanItem> list = lessonPlansDOS.stream().map(lessonPlansDO -> LessonPlanItem
|
||||
.builder()
|
||||
.id(lessonPlansDO.getId())
|
||||
.isFinished(0)
|
||||
.title(lessonPlansDO.getTitle())
|
||||
.build())
|
||||
.toList();
|
||||
|
||||
return Response.success(FindPlanStudentListRspVO.builder().lessonPlanItems(list).build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class LoginController {
|
||||
@ApiOperationLog(description = "登录")
|
||||
public Response<String> login(@RequestBody LoginReqVO loginReqVO) {
|
||||
try {
|
||||
loginService.login(loginReqVO.getPhone(), loginReqVO.getName(), loginReqVO.getPassword(), loginReqVO.getCode());
|
||||
loginService.login(loginReqVO.getPhone(), loginReqVO.getName(), loginReqVO.getPassword(), loginReqVO.getCode(), loginReqVO.getInvitationCode());
|
||||
return Response.success(StpUtil.getTokenInfo().getTokenValue());
|
||||
} catch (Exception e) {
|
||||
log.error("注册或登录失败 {}", e.getMessage());
|
||||
|
||||
@@ -4,6 +4,8 @@ package com.yinlihupo.enlish.service.controller;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.ClassDO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.GradeDO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.StudentDO;
|
||||
import com.yinlihupo.enlish.service.model.bo.StudentDetail;
|
||||
import com.yinlihupo.enlish.service.model.bo.exam.WordMasteryDetail;
|
||||
import com.yinlihupo.enlish.service.model.vo.student.*;
|
||||
import com.yinlihupo.enlish.service.service.ClassService;
|
||||
import com.yinlihupo.enlish.service.service.GradeService;
|
||||
@@ -46,6 +48,8 @@ public class StudentController {
|
||||
.id(studentDO.getId())
|
||||
.name(studentDO.getName())
|
||||
.classId(studentDO.getClassId())
|
||||
.className(classService.findClassById(studentDO.getClassId()).getTitle())
|
||||
.gradeName(gradeService.findByClassId(studentDO.getGradeId()).getTitle())
|
||||
.gradeId(studentDO.getGradeId())
|
||||
.build()).toList();
|
||||
|
||||
@@ -92,4 +96,21 @@ public class StudentController {
|
||||
String analyzeStudentStudy = studentService.analyzeStudentStudy(analyzeStudentStudyReqVO.getStudentId());
|
||||
return Response.success(analyzeStudentStudy);
|
||||
}
|
||||
|
||||
@PostMapping("mastery/detail")
|
||||
@ApiOperationLog(description = "查询学生单词掌握详情")
|
||||
public Response<List<FindStudentMasteryDetailRspVO>> findStudentMasteryDetail(@RequestBody FindStudentMasteryDetailReqVO findStudentMasteryDetailReqVO) {
|
||||
Integer studentId = findStudentMasteryDetailReqVO.getStudentId();
|
||||
List<WordMasteryDetail> studentWordMasteryDetail = studentService.findStudentWordMasteryDetail(studentId);
|
||||
|
||||
List<FindStudentMasteryDetailRspVO> list = studentWordMasteryDetail.stream().map(wordMasteryDetail -> FindStudentMasteryDetailRspVO.builder()
|
||||
.word(wordMasteryDetail.getWord())
|
||||
.reviewCount(wordMasteryDetail.getReviewCount())
|
||||
.memoryStrength(wordMasteryDetail.getMemoryStrength())
|
||||
.updateTime(wordMasteryDetail.getUpdate_time())
|
||||
.build()
|
||||
).toList();
|
||||
|
||||
return Response.success(list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package com.yinlihupo.enlish.service.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||
import com.yinlihupo.enlish.service.model.vo.user.FindUserInfoRspVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.user.UpdateUserInfoReqVO;
|
||||
import com.yinlihupo.enlish.service.service.UserService;
|
||||
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||
import com.yinlihupo.framework.common.response.Response;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/user/")
|
||||
@Slf4j
|
||||
public class UserController {
|
||||
|
||||
@Resource
|
||||
@@ -27,4 +33,23 @@ public class UserController {
|
||||
return Response.success(findUserInfoRspVO);
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("update-user-info")
|
||||
@ApiOperationLog(description = "修改用户信息")
|
||||
public Response<String> updatePassword(@RequestBody UpdateUserInfoReqVO updateUserInfoReqVO) {
|
||||
try {
|
||||
String code = updateUserInfoReqVO.getCode();
|
||||
String newPassword = updateUserInfoReqVO.getNewPassword();
|
||||
String phone = updateUserInfoReqVO.getPhone();
|
||||
String name = updateUserInfoReqVO.getName();
|
||||
userService.updateUserInfo(newPassword, code, phone, name);
|
||||
|
||||
StpUtil.logout();
|
||||
|
||||
return Response.success();
|
||||
} catch (Exception e) {
|
||||
log.error("修改密码失败 {}", e.getMessage());
|
||||
return Response.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.yinlihupo.enlish.service.controller;
|
||||
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindStudentWordDetailReqVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindStudentWordDetailRspVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindWordTitleReqVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindWordTitleRspVO;
|
||||
import com.yinlihupo.enlish.service.service.VocabularyService;
|
||||
@@ -30,4 +32,11 @@ public class VocabularyController {
|
||||
.build();
|
||||
return Response.success(findWordTitleRspVO);
|
||||
}
|
||||
|
||||
@PostMapping("student/detail")
|
||||
@ApiOperationLog(description = "查询学生单词详情")
|
||||
public Response<FindStudentWordDetailRspVO> findStudentWordDetail(@RequestBody FindStudentWordDetailReqVO vo) {
|
||||
return Response.success(vocabularyService.findStudentWordDetail(vo.getId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.yinlihupo.enlish.service.domain.dataobject;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class PlanExamDO {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private Integer planId;
|
||||
|
||||
private Integer examId;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.yinlihupo.enlish.service.domain.dataobject;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class StudentStageLearningRemarkDO {
|
||||
private Integer id;
|
||||
|
||||
private Integer studentId;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
private String commentContent;
|
||||
|
||||
}
|
||||
@@ -7,4 +7,6 @@ public interface ExamWordsDOMapper {
|
||||
int insert(ExamWordsDO record);
|
||||
|
||||
ExamWordsDO selectById(Integer id);
|
||||
|
||||
void updateWordIdsOrder(ExamWordsDO examWordsDO);
|
||||
}
|
||||
@@ -19,9 +19,13 @@ public interface ExamWordsJudgeResultDOMapper {
|
||||
|
||||
Integer selectCount();
|
||||
|
||||
Integer selectUnfinishedCount();
|
||||
|
||||
ExamWordsJudgeResultDO selectDetailById(@Param("id") Integer id);
|
||||
|
||||
List<ExamWordsJudgeResultDO> selectByStudentId(@Param("studentId") Integer studentId);
|
||||
|
||||
List<ExamWordsJudgeResultDO> selectByStudentIdAndLimitTime(@Param("studentId") Integer studentId);
|
||||
|
||||
List<ExamWordsJudgeResultDO> selectByPageAndStudentIds(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize, @Param("studentIds") List<Integer> studentIds);
|
||||
}
|
||||
@@ -14,4 +14,6 @@ public interface LessonPlansDOMapper {
|
||||
List<LessonPlansDO> findLessonPlansByStudentId(@Param("ids") List<Integer> ids);
|
||||
|
||||
LessonPlansDO selectByLessonId(@Param("lessonId") Integer lessonId);
|
||||
|
||||
List<LessonPlansDO> selectByStudentId(@Param("studentId") Integer studentId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.yinlihupo.enlish.service.domain.mapper;
|
||||
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.PlanExamDO;
|
||||
|
||||
public interface PlanExamDOMapper {
|
||||
void insert(PlanExamDO record);
|
||||
|
||||
PlanExamDO selectByExamId(Integer examId);
|
||||
}
|
||||
@@ -23,4 +23,10 @@ public interface StudentDOMapper {
|
||||
int selectStudentCountByClassId(@Param("classId") Integer classId);
|
||||
|
||||
int updateStudentActualGradeId(@Param("studentId") Integer studentId, @Param("gradeId") Integer gradeId);
|
||||
|
||||
List<StudentDO> selectStudentDOListByClassId(@Param("classId") Integer classId);
|
||||
|
||||
List<StudentDO> selectStudentDOListByGradeId(@Param("gradeId") Integer gradeId);
|
||||
|
||||
List<StudentDO> selectStudentDOListByName(@Param("name") String name);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.yinlihupo.enlish.service.domain.mapper;
|
||||
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.StudentStageLearningRemarkDO;
|
||||
|
||||
public interface StudentStageLearningRemarkDOMapper {
|
||||
|
||||
void insert(StudentStageLearningRemarkDO studentStageLearningRemarkDO);
|
||||
}
|
||||
@@ -25,4 +25,6 @@ public interface UnitDOMapper {
|
||||
List<UnitDO> selectUnitDOList(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
|
||||
|
||||
Integer selectUnitDOListCount();
|
||||
|
||||
List<UnitDO> selectByUnitName(@Param("unitName") String unitName);
|
||||
}
|
||||
@@ -11,6 +11,10 @@ public interface UserDOMapper {
|
||||
|
||||
void insert(UserDO userDO);
|
||||
|
||||
void updatePassword(@Param("id") Long id, @Param("password") String password);
|
||||
|
||||
void updateUserInfo(@Param("id") Long id, @Param("name") String name, @Param("password") String password, @Param("phone") String phone);
|
||||
|
||||
UserDO selectById(Long id);
|
||||
|
||||
List<UserDO> selectUserDOList(@Param("name") String name, @Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
|
||||
|
||||
@@ -24,4 +24,8 @@ public interface VocabularyBankDOMapper {
|
||||
List<VocabularyBankDO> selectVocabularyBankListByGradeIdRandom(@Param("gradeId") Integer gradeId, @Param("wordCount") Integer wordCount);
|
||||
|
||||
Integer selectWordTotal();
|
||||
|
||||
List<VocabularyBankDO> selectByUnitIds(@Param("unitIds") List<Integer> unitIds);
|
||||
|
||||
List<VocabularyBankDO> selectByGradeIdAndNotMatchIds(@Param("gradeId") Integer gradeId, @Param("ids") List<Integer> ids, @Param("wordCount") Integer wordCount);
|
||||
}
|
||||
@@ -18,4 +18,10 @@ public interface WordMasteryLogDOMapper {
|
||||
int selectStudentStrengthCount(@Param("studentId") Integer studentId);
|
||||
|
||||
List<WordMasteryLogDO> selectByStudentIdAndLimitTime(@Param("studentId") Integer studentId);
|
||||
|
||||
List<WordMasteryLogDO> selectAllByStudentId(@Param("studentId") Integer studentId);
|
||||
|
||||
Integer selectMasteryCount(@Param("studentId") Integer studentId);
|
||||
|
||||
Integer selectNotMasteryCount(@Param("studentId") Integer studentId);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.yinlihupo.enlish.service.model.bo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class Sentence {
|
||||
|
||||
@JsonProperty("target_word")
|
||||
private String targetWord;
|
||||
@JsonProperty("grade_level")
|
||||
private String gradeLevel;
|
||||
@JsonProperty("question")
|
||||
private String question;
|
||||
@JsonProperty("chinese_clue")
|
||||
private String chineseClue;
|
||||
@JsonProperty("correct_answer")
|
||||
private String correctAnswer;
|
||||
@JsonProperty("grammar_point")
|
||||
private String grammarPoint;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.admin;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class CreateInvitationCodeReqVO {
|
||||
|
||||
// 限制人数
|
||||
private Integer limit;
|
||||
// 有效期
|
||||
private Integer expire;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.admin;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class CreateInvitationCodeRspVO {
|
||||
|
||||
private String invitationCode;
|
||||
|
||||
}
|
||||
@@ -13,4 +13,8 @@ public class ExamWordsResultReqVO {
|
||||
|
||||
private Integer page;
|
||||
private Integer size;
|
||||
|
||||
private Integer classId;
|
||||
private Integer gradeId;
|
||||
private String studentName;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.time.LocalDateTime;
|
||||
@Data
|
||||
@Builder
|
||||
public class ExamWordsResultRspVO {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String ansSheetPath;
|
||||
@@ -20,6 +21,10 @@ public class ExamWordsResultRspVO {
|
||||
|
||||
private Integer examWordsId;
|
||||
|
||||
private String studentName;
|
||||
|
||||
private String examWordsTitle;
|
||||
|
||||
private Integer correctWordCount;
|
||||
|
||||
private Integer wrongWordCount;
|
||||
|
||||
@@ -14,8 +14,6 @@ import java.util.List;
|
||||
@Builder
|
||||
public class GenerateExamWordsReqVO {
|
||||
|
||||
private Integer gradeId;
|
||||
private Integer level;
|
||||
private Integer type;
|
||||
private Integer studentId;
|
||||
}
|
||||
|
||||
@@ -15,4 +15,5 @@ public class LoginReqVO {
|
||||
private String name;
|
||||
private String password;
|
||||
private String code;
|
||||
private String invitationCode;
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ public class AddLessonPlanReqVO {
|
||||
|
||||
private Integer studentId;
|
||||
private Integer unitId;
|
||||
private Integer wordSize;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FindIsGeneratePlanReqVO {
|
||||
|
||||
private Integer studentId;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FindPlanStudentListRspVO {
|
||||
|
||||
List<LessonPlanItem> lessonPlanItems;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FindPlanStudentReqVO {
|
||||
private Integer studentId;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FindWordTTSVoiceReqVO {
|
||||
|
||||
String text;
|
||||
String voice;
|
||||
String format;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FindWordVoiceReqVO {
|
||||
|
||||
private Integer planId;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FindWordVoiceRspVO {
|
||||
|
||||
private List<String> words;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.student;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@@ -16,5 +17,6 @@ public class AddStudentReqVO {
|
||||
private String name;
|
||||
private Integer classId;
|
||||
private Integer gradeId;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.student;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FindStudentMasteryDetailReqVO {
|
||||
|
||||
private Integer studentId;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.student;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FindStudentMasteryDetailRspVO {
|
||||
private String word;
|
||||
|
||||
private Integer reviewCount;
|
||||
|
||||
private Double memoryStrength;
|
||||
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -15,4 +15,6 @@ public class StudentItemRspVO {
|
||||
private String name;
|
||||
private Integer classId;
|
||||
private Integer gradeId;
|
||||
private String className;
|
||||
private String gradeName;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.user;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Data
|
||||
public class UpdateUserInfoReqVO {
|
||||
|
||||
private String newPassword;
|
||||
private String name;
|
||||
private String phone;
|
||||
private String code;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.vocabulary;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FindStudentWordDetailReqVO {
|
||||
|
||||
private Integer id;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.vocabulary;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Data
|
||||
public class FindStudentWordDetailRspVO {
|
||||
/**
|
||||
* 已掌握单词数
|
||||
*/
|
||||
private Integer masteredWordCount;
|
||||
|
||||
/**
|
||||
* 未掌握单词数
|
||||
*/
|
||||
private Integer unmasteredWordCount;
|
||||
|
||||
/**
|
||||
* 待审查单词数(推荐使用,简洁通用)
|
||||
*/
|
||||
private Integer pendingReviewWordCount;
|
||||
}
|
||||
@@ -9,10 +9,12 @@ public interface ExamWordsJudgeService {
|
||||
|
||||
void judgeExamWords(int count);
|
||||
|
||||
List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize);
|
||||
List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize, Integer classId, Integer gradeId, String studentName);
|
||||
|
||||
Integer getExamWordsJudgeResultCount();
|
||||
|
||||
Integer getExamUnfinishedCount();
|
||||
|
||||
ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id);
|
||||
|
||||
List<ExamWordsJudgeResultDO> getStudentExamWordsResultList(Integer studentId);
|
||||
|
||||
@@ -8,7 +8,11 @@ import java.util.List;
|
||||
|
||||
public interface ExamWordsService {
|
||||
|
||||
ExamWordsDO generateExamWords(Integer gradeId, Integer level, Integer studentId, Integer type);
|
||||
ExamWordsDO generateExamWords(Integer studentId, Integer type);
|
||||
|
||||
int saveExamWordsPngToDbAndLocal(MultipartFile file);
|
||||
|
||||
void updateExamWordsWordIdsOrder(ExamWordsDO examWordsDO);
|
||||
|
||||
ExamWordsDO getExamWordsDOById(Integer id);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||
import java.util.List;
|
||||
|
||||
public interface LessonPlansService {
|
||||
void generateLessonPlans(Integer studentId, Integer unitId);
|
||||
void generateLessonPlans(Integer studentId, Integer unitId, Integer wordSize);
|
||||
|
||||
List<LessonPlansDO> findLessonPlans(List<Integer> ids);
|
||||
|
||||
LessonPlansDO findLessonPlanById(Integer id);
|
||||
|
||||
List<LessonPlansDO> findLessonPlansByStudentId(Integer studentId);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.yinlihupo.enlish.service.service;
|
||||
|
||||
public interface LoginService {
|
||||
|
||||
void login(String phone, String name, String reqPassword, String reqCode);
|
||||
void login(String phone, String name, String reqPassword, String reqCode, String invitationCode);
|
||||
|
||||
void sendVerificationCode(String phone);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.yinlihupo.enlish.service.service;
|
||||
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.StudentDO;
|
||||
import com.yinlihupo.enlish.service.model.bo.StudentDetail;
|
||||
import com.yinlihupo.enlish.service.model.bo.exam.WordMasteryDetail;
|
||||
import com.yinlihupo.enlish.service.model.vo.student.AddStudentReqVO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -22,4 +23,6 @@ public interface StudentService {
|
||||
void deleteStudent(Integer studentId);
|
||||
|
||||
String analyzeStudentStudy(Integer studentId);
|
||||
|
||||
List<WordMasteryDetail> findStudentWordMasteryDetail(Integer studentId);
|
||||
}
|
||||
|
||||
@@ -13,4 +13,6 @@ public interface UserService {
|
||||
Integer findUserTotal();
|
||||
|
||||
void createUser(UserDO userDO);
|
||||
|
||||
void updateUserInfo(String password, String reqCode, String phone, String name);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package com.yinlihupo.enlish.service.service;
|
||||
|
||||
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindStudentWordDetailRspVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface VocabularyService {
|
||||
|
||||
List<VocabularyBankDO> findVocabularyBankDOListById(List<Integer> ids);
|
||||
|
||||
FindStudentWordDetailRspVO findStudentWordDetail(Integer studentId);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
package com.yinlihupo.enlish.service.service.exam;
|
||||
|
||||
import com.yinlihupo.enlish.service.constant.ExamWordsConstant;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsDO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsJudgeResultDO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.StudentDO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.*;
|
||||
import com.yinlihupo.enlish.service.domain.mapper.*;
|
||||
import com.yinlihupo.enlish.service.service.ExamWordsService;
|
||||
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 org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ExamWordsServiceImpl implements ExamWordsService {
|
||||
|
||||
@Resource
|
||||
private GradeUnitDOMapper gradeUnitDOMapper;
|
||||
@Resource
|
||||
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
||||
@Resource
|
||||
@@ -38,6 +36,8 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
||||
private ExamWordsJudgeResultDOMapper examWordsJudgeResultDOMapper;
|
||||
@Resource
|
||||
private StudentDOMapper studentDOMapper;
|
||||
@Resource
|
||||
private UnitDOMapper unitDOMapper;
|
||||
|
||||
@Value("${templates.count}")
|
||||
private Integer wordCount;
|
||||
@@ -46,17 +46,29 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = RuntimeException.class)
|
||||
public ExamWordsDO generateExamWords(Integer gradeId, Integer level, Integer studentId, Integer type) {
|
||||
|
||||
public ExamWordsDO generateExamWords(Integer studentId, Integer type) {
|
||||
|
||||
ExamWordsDO examWordsDO;
|
||||
|
||||
if (type == ExamWordsConstant.EXAM_TYPE_BASELINE) {
|
||||
log.info("生成摸底测试");
|
||||
examWordsDO = generateBaselineExamWords(studentId);
|
||||
} else if (type == ExamWordsConstant.EXAM_TYPE_MIDTERM) {
|
||||
log.info("生成期中测试");
|
||||
examWordsDO = generateMidtermExamWords(studentId);
|
||||
} else {
|
||||
// todo 生成期中考试待实现
|
||||
examWordsDO = null;
|
||||
log.info("生成期末测试");
|
||||
examWordsDO = generateFinalExamWords(studentId);
|
||||
}
|
||||
|
||||
List<Integer> wordIds = new ArrayList<>(examWordsDO.getWordIds());
|
||||
if (wordIds.size() < wordCount) {
|
||||
log.info("单词数量不足,补充单词");
|
||||
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectByGradeIdAndNotMatchIds(studentDO.getGradeId(), wordIds, wordCount - wordIds.size());
|
||||
List<Integer> list = new ArrayList<>(vocabularyBankDOS.stream().map(VocabularyBankDO::getId).toList());
|
||||
wordIds.addAll(list);
|
||||
examWordsDO.setWordIds(wordIds);
|
||||
}
|
||||
|
||||
return examWordsDO;
|
||||
@@ -95,11 +107,54 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
||||
.gradeId(gradeId)
|
||||
.level(1)
|
||||
.type(ExamWordsConstant.EXAM_TYPE_BASELINE)
|
||||
.title("摸低测试测试" + studentDO.getName())
|
||||
.title("摸低测试" + studentDO.getName())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.wordIds(vocabularyBankDOS.stream().map(VocabularyBankDO::getId).toList())
|
||||
.build();
|
||||
|
||||
return getExamWordsDO(studentId, examWordsDO);
|
||||
}
|
||||
|
||||
private ExamWordsDO generateMidtermExamWords(Integer studentId) {
|
||||
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||
Integer gradeId = studentDO.getGradeId();
|
||||
List<UnitDO> unitDOS = unitDOMapper.selectByUnitName(ExamWordsConstant.getGradeName(gradeId) + "上");
|
||||
ExamWordsDO examWordsDO = getExamWordsDO(studentId, studentDO, gradeId, unitDOS, ExamWordsConstant.EXAM_TYPE_MIDTERM);
|
||||
examWordsDO.setTitle("期中测试" + studentDO.getName());
|
||||
return getExamWordsDO(studentId, examWordsDO);
|
||||
|
||||
}
|
||||
|
||||
private ExamWordsDO generateFinalExamWords(Integer studentId) {
|
||||
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||
Integer gradeId = studentDO.getGradeId();
|
||||
List<UnitDO> unitDOS = unitDOMapper.selectByUnitName(ExamWordsConstant.getGradeName(gradeId));
|
||||
ExamWordsDO examWordsDO = getExamWordsDO(studentId, studentDO, gradeId, unitDOS, ExamWordsConstant.EXAM_TYPE_FINAL);
|
||||
examWordsDO.setTitle("期末测试" + studentDO.getName());
|
||||
return getExamWordsDO(studentId, examWordsDO);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ExamWordsDO getExamWordsDO(Integer studentId, StudentDO studentDO, Integer gradeId, List<UnitDO> unitDOS, Integer type) {
|
||||
if (unitDOS.isEmpty()) {
|
||||
throw new RuntimeException("没有找到对应的单元");
|
||||
}
|
||||
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectByUnitIds(unitDOS.stream().map(UnitDO::getId).toList());
|
||||
|
||||
ExamWordsDO examWordsDO = ExamWordsDO.builder()
|
||||
.gradeId(gradeId)
|
||||
.level(1)
|
||||
.type(type)
|
||||
.title(studentDO.getName())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.wordIds(vocabularyBankDOS.stream().map(VocabularyBankDO::getId).toList())
|
||||
.build();
|
||||
|
||||
return getExamWordsDO(studentId, examWordsDO);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ExamWordsDO getExamWordsDO(Integer studentId, ExamWordsDO examWordsDO) {
|
||||
int insert = examWordsDOMapper.insert(examWordsDO);
|
||||
if (insert <= 0) {
|
||||
throw new RuntimeException("插入考试失败");
|
||||
@@ -116,29 +171,65 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = RuntimeException.class)
|
||||
public int saveExamWordsPngToDbAndLocal(MultipartFile file) {
|
||||
|
||||
File dir = new File(tmpPng);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
// 1. 基础校验:判空
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new RuntimeException("上传文件不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 安全校验:检查后缀名白名单
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
String suffix = "";
|
||||
if (originalFilename != null && originalFilename.contains(".")) {
|
||||
suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||
String extension = StringUtils.getFilenameExtension(originalFilename); // Spring工具类
|
||||
List<String> allowedExtensions = Arrays.asList("png", "jpg", "jpeg");
|
||||
|
||||
if (extension == null || !allowedExtensions.contains(extension.toLowerCase())) {
|
||||
throw new RuntimeException("不支持的文件格式,仅支持: " + allowedExtensions);
|
||||
}
|
||||
String newFileName = UUID.randomUUID() + suffix;
|
||||
String path = tmpPng + newFileName;
|
||||
|
||||
File dest = new File(path);
|
||||
file.transferTo(dest);
|
||||
// 3. 准备目录 (使用 NIO)
|
||||
// 假设 tmpPng 是配置好的基础路径字符串
|
||||
Path directoryPath = Paths.get(tmpPng);
|
||||
try {
|
||||
if (!Files.exists(directoryPath)) {
|
||||
Files.createDirectories(directoryPath);
|
||||
}
|
||||
|
||||
// 4. 生成文件名 (防止文件名冲突)
|
||||
String newFileName = UUID.randomUUID().toString() + "." + extension;
|
||||
|
||||
// 5. 组合最终路径 (自动处理分隔符)
|
||||
Path targetPath = directoryPath.resolve(newFileName);
|
||||
|
||||
// 6. 保存文件
|
||||
file.transferTo(targetPath.toAbsolutePath().toFile());
|
||||
String string = targetPath.toAbsolutePath().toFile().toString();
|
||||
log.info("文件上传成功路径为 {}", string);
|
||||
if (!targetPath.toFile().exists()) {
|
||||
log.error("文件上传失败: {}", newFileName);
|
||||
throw new RuntimeException("文件上传失败");
|
||||
}
|
||||
|
||||
// 7. 入库
|
||||
// 建议:存相对路径或文件名,不要存 targetPath.toString() 的绝对路径
|
||||
// 这里为了演示,假设 insert 依然接受字符串,建议存 newFileName
|
||||
int insert = examWordsJudgeResultDOMapper.insert(targetPath.toString());
|
||||
|
||||
log.info("上传文件成功: {}", newFileName);
|
||||
return insert;
|
||||
|
||||
return examWordsJudgeResultDOMapper.insert(path);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("上传失败", e);
|
||||
log.error("文件上传失败: {}", originalFilename, e);
|
||||
throw new RuntimeException("上传失败,请稍后重试", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExamWordsWordIdsOrder(ExamWordsDO examWordsDO) {
|
||||
examWordsDOMapper.updateWordIdsOrder(examWordsDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExamWordsDO getExamWordsDOById(Integer id) {
|
||||
return examWordsDOMapper.selectById(id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.yinlihupo.enlish.service.model.bo.exam.ActionType;
|
||||
import com.yinlihupo.enlish.service.model.bo.exam.DiagnosisResult;
|
||||
import com.yinlihupo.enlish.service.model.bo.exam.ZoneStats;
|
||||
import com.yinlihupo.enlish.service.service.ExamWordsJudgeService;
|
||||
import com.yinlihupo.enlish.service.service.StudentLessonPlansService;
|
||||
import com.yinlihupo.enlish.service.utils.PngUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -40,23 +41,29 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
private GradeUnitDOMapper gradeUnitDOMapper;
|
||||
@Resource
|
||||
private StudentDOMapper studentDOMapper;
|
||||
@Resource
|
||||
private PlanExamDOMapper planExamDOMapper;
|
||||
@Resource
|
||||
private StudentLessonPlansService studentLessonPlansService;
|
||||
|
||||
@Value("${templates.data}")
|
||||
private String tessdataPath;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void judgeExamWords(int count) {
|
||||
List<ExamWordsJudgeResultDO> examWordsJudgeResultDOS = examWordsJudgeResultDOMapper.selectUnfinishedExamWordsJudgeResultDOList(count);
|
||||
for (ExamWordsJudgeResultDO examWordsJudgeResultDO : examWordsJudgeResultDOS) {
|
||||
String ansSheetPath = null;
|
||||
try {
|
||||
String ansSheetPath = examWordsJudgeResultDO.getAnsSheetPath();
|
||||
ansSheetPath = examWordsJudgeResultDO.getAnsSheetPath();
|
||||
List<CoordinatesXY> coordinatesXIES = PngUtil.analysisXY(ansSheetPath);
|
||||
|
||||
// 从图片中获取学生 id 和考试 id
|
||||
StudentExamId studentExamId = PngUtil.analyzeExamWordsIdAndStudentId(ansSheetPath, tessdataPath, coordinatesXIES);
|
||||
Integer examWordsJudgeResultDOId = examWordsJudgeResultDO.getId();
|
||||
if (studentExamId == null) {
|
||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未识别学生 id 和考试 id");
|
||||
log.info("未找到学生 id 和考试 id");
|
||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未识别学生和考试");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -93,9 +100,13 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
.wrongWordCount(unmemorizedWordIds.size())
|
||||
.isFinished(1)
|
||||
.build();
|
||||
|
||||
if (examWordsDO.getType().equals(ExamWordsConstant.EXAM_TYPE_BASELINE)) {
|
||||
// 判断考试等级
|
||||
judgeExamActualGrade(wordsJudgeResultDO, examWordsDO);
|
||||
|
||||
} else {
|
||||
wordsJudgeResultDO.setMsg("此次考试" + examWordsDO.getTitle() + "答对单词数为" + memorizedWordIds.size());
|
||||
}
|
||||
int updated = examWordsJudgeResultDOMapper.updateExamWordsJudgeResultDO(wordsJudgeResultDO);
|
||||
if (updated != 1) {
|
||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新考试记录失败");
|
||||
@@ -103,6 +114,12 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
}
|
||||
log.info("更新考试记录成功");
|
||||
|
||||
PlanExamDO planExamDO = planExamDOMapper.selectByExamId(examWordsId);
|
||||
if (planExamDO != null) {
|
||||
studentLessonPlansService.finishStudentLessonPlan(studentId, planExamDO.getPlanId());
|
||||
log.info("完成学案成功, planId: {}", planExamDO.getPlanId());
|
||||
}
|
||||
|
||||
List<WordMasteryLogDO> wordMasteryLogDOS = new ArrayList<>(unmemorizedWordIds.stream().map(wordId -> WordMasteryLogDO.builder()
|
||||
.wordId(wordId)
|
||||
.studentId(studentId)
|
||||
@@ -131,9 +148,14 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
boolean delete = new File(ansSheetPath).delete();
|
||||
if (delete) {
|
||||
log.info("删除文件成功:{}", ansSheetPath);
|
||||
} else {
|
||||
log.error("删除文件失败:{}", ansSheetPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("识别考试失败 {}", e.getMessage());
|
||||
if (ansSheetPath != null) {
|
||||
new File(ansSheetPath).delete();
|
||||
}
|
||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDO.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -254,9 +276,9 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
boolean canUpgrade = zoneB.getAccuracy() >= 0.8 && (zoneA.getTotalCount() == 0 || zoneA.getAccuracy() >= 0.6);
|
||||
if (canUpgrade) {
|
||||
return new DiagnosisResult(
|
||||
anchorGrade + 1,
|
||||
Math.max(anchorGrade + 1, ExamWordsConstant.GRADE_8),
|
||||
ActionType.UPGRADE,
|
||||
"恭喜!您对" + ExamWordsConstant.getGradeName(anchorGrade) + " 的掌握非常扎实,且具备挑战" + ExamWordsConstant.getGradeName(anchorGrade + 1) + " 的潜力。系统将为您解锁更高阶词库。"
|
||||
"恭喜!您对" + ExamWordsConstant.getGradeName(anchorGrade) + " 的掌握非常扎实,且具备挑战" + ExamWordsConstant.getGradeName(Math.max(anchorGrade + 1, ExamWordsConstant.GRADE_8)) + " 的潜力。系统将为您解锁更高阶词库。"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -277,7 +299,7 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
} else if (zoneC.getAccuracy() >= 0.6) {
|
||||
// Zone C 勉强及格,Zone B 不及格 -> 降一级
|
||||
return new DiagnosisResult(
|
||||
anchorGrade - 1,
|
||||
Math.max(anchorGrade - 1, ExamWordsConstant.GRADE_1),
|
||||
ActionType.DOWNGRADE,
|
||||
"检测到" + ExamWordsConstant.getGradeName(anchorGrade) + " 学习吃力,且" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 存在模糊点。建议降级回溯,巩固 " + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + "。"
|
||||
);
|
||||
@@ -286,14 +308,14 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
// 检查 Zone D
|
||||
if (zoneD.getTotalCount() > 0 && zoneD.getAccuracy() >= 0.6) {
|
||||
return new DiagnosisResult(
|
||||
anchorGrade - 2,
|
||||
Math.max(anchorGrade - 2, ExamWordsConstant.GRADE_1),
|
||||
ActionType.DOWNGRADE,
|
||||
"您的" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 和 " + ExamWordsConstant.getGradeName(anchorGrade) + " 均存在脱节。建议从 " + ExamWordsConstant.getGradeName(zoneD.getGradeId()) + " 开始系统补漏。"
|
||||
);
|
||||
} else {
|
||||
// 彻底崩盘,可能需要熔断,或者定级到更低
|
||||
return new DiagnosisResult(
|
||||
Math.max(1, anchorGrade - 3),
|
||||
Math.max(ExamWordsConstant.GRADE_1, anchorGrade - 3),
|
||||
ActionType.DOWNGRADE,
|
||||
"基础薄弱,建议暂停当前进度,从低年级核心高频词重新开始。"
|
||||
);
|
||||
@@ -310,8 +332,24 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize) {
|
||||
public List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize, Integer classId, Integer gradeId, String studentName) {
|
||||
|
||||
List<Integer> studentIds = new ArrayList<>();
|
||||
if (classId != null) {
|
||||
studentIds.addAll(studentDOMapper.selectStudentDOListByClassId(classId).stream().map(StudentDO::getId).toList());
|
||||
}
|
||||
if (gradeId != null) {
|
||||
studentIds.addAll(studentDOMapper.selectStudentDOListByGradeId(gradeId).stream().map(StudentDO::getId).toList());
|
||||
}
|
||||
if (!studentName.isEmpty()) {
|
||||
studentIds.addAll(studentDOMapper.selectStudentDOListByName(studentName).stream().map(StudentDO::getId).toList());
|
||||
}
|
||||
|
||||
if (studentIds.isEmpty()) {
|
||||
return examWordsJudgeResultDOMapper.selectByPage((page - 1) * pageSize, pageSize);
|
||||
} else {
|
||||
return examWordsJudgeResultDOMapper.selectByPageAndStudentIds((page - 1) * pageSize, page * pageSize, studentIds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -319,6 +357,11 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
return examWordsJudgeResultDOMapper.selectCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getExamUnfinishedCount() {
|
||||
return examWordsJudgeResultDOMapper.selectUnfinishedCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id) {
|
||||
return examWordsJudgeResultDOMapper.selectDetailById(id);
|
||||
|
||||
@@ -13,11 +13,15 @@ import com.yinlihupo.enlish.service.service.LoginService;
|
||||
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -28,20 +32,32 @@ public class LoginServiceImpl implements LoginService {
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
@Resource
|
||||
private Client client;
|
||||
|
||||
@Override
|
||||
public void login(String phone, String name, String reqPassword, String reqCode) {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void login(String phone, String name, String reqPassword, String reqCode, String invitationCode) {
|
||||
UserDO userDO = userDOMapper.selectByPhone(phone);
|
||||
log.info("userDO:{}", userDO);
|
||||
|
||||
String code = stringRedisTemplate.opsForValue().get(UserRedisConstants.buildUserLoginCode(phone));
|
||||
String code = JsonUtils.toJsonString(redisTemplate.opsForValue().get(UserRedisConstants.buildUserLoginCode(phone)));
|
||||
if (userDO == null) {
|
||||
if (code == null || !code.equals(reqCode)) {
|
||||
throw new RuntimeException("验证码错误");
|
||||
}
|
||||
|
||||
Object invitationObj = redisTemplate.opsForValue().get(UserRedisConstants.buildUserInvitationCode(invitationCode));
|
||||
if (invitationObj == null) {
|
||||
throw new RuntimeException("邀请码错误");
|
||||
}
|
||||
int invitationLimit = Integer.parseInt(JsonUtils.toJsonString(invitationObj));
|
||||
if (invitationLimit <= 0) {
|
||||
throw new RuntimeException("邀请码已使用完毕");
|
||||
}
|
||||
redisTemplate.opsForValue().set(UserRedisConstants.buildUserInvitationCode(invitationCode), invitationLimit - 1);
|
||||
|
||||
userDO = UserDO.builder()
|
||||
.phone(phone)
|
||||
.name(name)
|
||||
@@ -68,7 +84,9 @@ public class LoginServiceImpl implements LoginService {
|
||||
@Override
|
||||
public void sendVerificationCode(String phone) {
|
||||
String code = RandomUtil.randomNumbers(6);
|
||||
stringRedisTemplate.opsForValue().set(UserRedisConstants.buildUserLoginCode(phone), code, Duration.ofSeconds(60));
|
||||
String key = UserRedisConstants.buildUserLoginCode(phone);
|
||||
redisTemplate.opsForValue().set(key, code);
|
||||
redisTemplate.expire(key, 5, TimeUnit.MINUTES);
|
||||
String signName = "短信测试";
|
||||
String templateCode = "SMS_154950909";
|
||||
String templateParam = String.format("{\"code\":\"%s\"}", code);
|
||||
@@ -80,10 +98,6 @@ public class LoginServiceImpl implements LoginService {
|
||||
}
|
||||
}
|
||||
|
||||
private void initUserRole(UserDO userDO) {
|
||||
// todo
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信
|
||||
*/
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
package com.yinlihupo.enlish.service.service.plan;
|
||||
|
||||
import com.yinlihupo.enlish.service.constant.ExamWordsConstant;
|
||||
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.model.bo.Sentence;
|
||||
import com.yinlihupo.enlish.service.model.bo.Word;
|
||||
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
||||
import com.yinlihupo.enlish.service.utils.DifyArticleClient;
|
||||
import com.yinlihupo.enlish.service.utils.DifyClient;
|
||||
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.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -35,22 +39,34 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
@Resource
|
||||
private GradeDOMapper gradeDOMapper;
|
||||
@Resource
|
||||
private DifyArticleClient difyArticleClient;
|
||||
|
||||
@Value("${templates.plan.weekday}")
|
||||
private String planWeekday;
|
||||
@Value("${templates.plan.weekend}")
|
||||
private String planWeekend;
|
||||
|
||||
private DifyClient difyClient;
|
||||
@Resource
|
||||
private ExamWordsDOMapper examWordsDOMapper;
|
||||
@Resource
|
||||
private StudentExamWordsDOMapper studentExamWordsDOMapper;
|
||||
@Resource
|
||||
private StudentDOMapper studentDOMapper;
|
||||
@Resource
|
||||
private PlanExamDOMapper planExamDOMapper;
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
@Resource
|
||||
private ClassDOMapper classDOMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void generateLessonPlans(Integer studentId, Integer unitId) {
|
||||
public void generateLessonPlans(Integer studentId, Integer unitId, Integer wordSize) {
|
||||
String key = LessonPlanConstant.buildGeneratePlanContent(studentId);
|
||||
redisTemplate.opsForValue().set(key, studentId);
|
||||
redisTemplate.expire(key, 7, TimeUnit.MINUTES);
|
||||
|
||||
log.info("开始生成计划");
|
||||
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOAllByUnitId(unitId);
|
||||
UnitDO unitDO = unitDOMapper.selectByPrimaryKey(unitId);
|
||||
GradeUnitDO gradeUnitDO = gradeUnitDOMapper.selectByUnitId(unitId);
|
||||
GradeDO gradeDO = gradeDOMapper.selectById(gradeUnitDO.getGradeId());
|
||||
|
||||
List<VocabularyBankDO> totalWords = new ArrayList<>();
|
||||
|
||||
// 补差词汇所用词汇的
|
||||
List<VocabularyBankDO> vocabularyBankListStudentNotMaster = getVocabListRandom(vocabularyBankDOMapper
|
||||
.selectVocabularyBankListStudentNotMaster(gradeUnitDO.getGradeId(), studentId), 50);
|
||||
@@ -58,15 +74,25 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
int countGap = gapSize / 5;
|
||||
|
||||
int syncSize = vocabularyBankDOS.size();
|
||||
int countSync = syncSize / 5;
|
||||
wordSize = wordSize <= 0 ? syncSize / 5 : wordSize;
|
||||
int checkTotal = 50;
|
||||
List<List<VocabularyBankDO>> weeksSync = new ArrayList<>();
|
||||
List<List<VocabularyBankDO>> weeksGap = new ArrayList<>();
|
||||
int j = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
List<VocabularyBankDO> syncVocabList = vocabularyBankDOS.subList(i * countSync, Math.min((i + 1) * countSync, syncSize));
|
||||
List<VocabularyBankDO> syncVocabList;
|
||||
if ((i + 1) * wordSize < syncSize) {
|
||||
syncVocabList = vocabularyBankDOS.subList(i * wordSize, (i + 1) * wordSize);
|
||||
} else {
|
||||
syncVocabList = new ArrayList<>(weeksSync.get(Math.min(j++, weeksSync.size() - 1)));
|
||||
}
|
||||
List<VocabularyBankDO> gapVocabList = vocabularyBankListStudentNotMaster.subList(i * countGap, Math.min(i * countGap + countGap, gapSize));
|
||||
weeksSync.add(syncVocabList);
|
||||
weeksGap.add(gapVocabList);
|
||||
|
||||
totalWords.addAll(syncVocabList);
|
||||
totalWords.addAll(gapVocabList);
|
||||
|
||||
List<VocabularyBankDO> reviewVocabList = new ArrayList<>();
|
||||
List<VocabularyBankDO> checkList = new ArrayList<>();
|
||||
// 艾宾浩斯遗忘曲线
|
||||
@@ -116,21 +142,29 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
.build();
|
||||
studentLessonPlansDOMapper.insert(studentLessonPlansDO);
|
||||
|
||||
Integer examId = (Integer) lessonPlanMap.get("examId");
|
||||
PlanExamDO planExamDO = PlanExamDO.builder()
|
||||
.planId(lessonPlansDO.getId())
|
||||
.examId(examId)
|
||||
.build();
|
||||
planExamDOMapper.insert(planExamDO);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
log.info("生成第{}天计划失败,失败原因 {}", i + 1, e.getMessage());
|
||||
}
|
||||
log.info("生成第{}天计划成功", i + 1);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
int syncWeekender = syncSize / 2;
|
||||
log.info("开始生成周末计划");
|
||||
int syncWeekendSize = totalWords.size() / 2;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
List<VocabularyBankDO> checkList = vocabularyBankDOS.subList(i * syncWeekender, Math.min((i + 1) * syncWeekender, syncSize));
|
||||
List<VocabularyBankDO> checkList = totalWords.subList(i * syncWeekendSize, Math.min((i + 1) * syncWeekendSize, syncWeekendSize));
|
||||
Map<String, Object> map = generateWeekendPlans(checkList, i + 6, gradeDO, unitDO, studentId);
|
||||
|
||||
LessonPlansDO lessonPlansDO = LessonPlansDO.builder()
|
||||
.title(map.get("title").toString())
|
||||
.title(map.get("examStr").toString() + "复习")
|
||||
.gradeId(gradeDO.getId().toString())
|
||||
.unitId(unitDO.getId())
|
||||
.createdAt(LocalDateTime.now())
|
||||
@@ -138,6 +172,13 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
.build();
|
||||
lessonPlansDOMapper.insert(lessonPlansDO);
|
||||
|
||||
Integer examId = (Integer) map.get("examId");
|
||||
PlanExamDO planExamDO = PlanExamDO.builder()
|
||||
.planId(lessonPlansDO.getId())
|
||||
.examId(examId)
|
||||
.build();
|
||||
planExamDOMapper.insert(planExamDO);
|
||||
|
||||
StudentLessonPlansDO studentLessonPlansDO = StudentLessonPlansDO.builder()
|
||||
.studentId(studentId)
|
||||
.planId(lessonPlansDO.getId())
|
||||
@@ -160,14 +201,43 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
return lessonPlansDOMapper.selectByLessonId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LessonPlansDO> findLessonPlansByStudentId(Integer studentId) {
|
||||
return lessonPlansDOMapper.selectByStudentId(studentId);
|
||||
}
|
||||
|
||||
private Map<String, Object> generateWeekendPlans(List<VocabularyBankDO> checkList,
|
||||
|
||||
private Map<String, Object> generateWeekendPlans(List<VocabularyBankDO> words,
|
||||
int day,
|
||||
GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws IOException {
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("title", "第" + day + "天" + "复习" + gradeDO.getTitle() + unitDO.getTitle() + studentId);
|
||||
data.put("checkList", checkList);
|
||||
words.forEach(word -> word.setDefinition(word.getDefinition().length() > 5 ? word.getDefinition().substring(0, 5) : word.getDefinition()));
|
||||
List<Integer> wordIds = words.stream().map(VocabularyBankDO::getId).toList();
|
||||
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||
|
||||
String ExamTitle = gradeDO.getTitle() + unitDO.getTitle() + "教案小测第" + ExamWordsConstant.day2Chinese(day) + "天" + studentDO.getName();
|
||||
ExamWordsDO examWordsDO = ExamWordsDO.builder()
|
||||
.gradeId(gradeDO.getId())
|
||||
.level(1)
|
||||
.wordIds(wordIds)
|
||||
.type(ExamWordsConstant.EXAM_TYPE_TEST)
|
||||
.title(ExamTitle)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
examWordsDOMapper.insert(examWordsDO);
|
||||
studentExamWordsDOMapper.insertStudentsExam(studentId, examWordsDO.getId());
|
||||
|
||||
ClassDO classDO = classDOMapper.selectClassDOById(studentDOMapper.selectStudentById(studentId).getClassId());
|
||||
data.put("examId", examWordsDO.getId());
|
||||
data.put("studentId", studentId);
|
||||
data.put("studentStr", gradeDO.getTitle() + " " + classDO.getTitle() + " " + studentDO.getName());
|
||||
data.put("examStr", ExamTitle);
|
||||
|
||||
List<VocabularyBankDO> words1 = words.subList(0, words.size() / 2);
|
||||
List<VocabularyBankDO> words2 = words.subList(words.size() / 2, words.size());
|
||||
data.put("words1", words1);
|
||||
data.put("words2", words2);
|
||||
// LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||
// Configure config = Configure.builder()
|
||||
// .bind("checkList", policy)
|
||||
@@ -186,7 +256,8 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
List<VocabularyBankDO> checkList,
|
||||
int day,
|
||||
GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws Exception {
|
||||
String title = gradeDO.getTitle() + " " + unitDO.getTitle() + " " + "第" + day + "天" + studentId;
|
||||
|
||||
String title = gradeDO.getTitle() + " " + unitDO.getTitle() + " " + "第" + day + "天";
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("title", title);
|
||||
data.put("syncVocabList", syncVocabList);
|
||||
@@ -194,15 +265,17 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
data.put("reviewVocabList", reviewVocabList);
|
||||
data.put("checkList", checkList);
|
||||
data.put("checkListAns", checkList);
|
||||
|
||||
// 中译英
|
||||
List<VocabularyBankDO> drillRound1 = new ArrayList<>(syncVocabList);
|
||||
List<Word> list = syncVocabList.stream().map(vocabularyBankDO -> Word.builder().title(vocabularyBankDO.getWord()).definition(vocabularyBankDO.getDefinition()).build()).toList();
|
||||
list.forEach(word -> word.setTitle(" "));
|
||||
|
||||
List<Word> drillRound1 = new ArrayList<>(list);
|
||||
Collections.shuffle(drillRound1);
|
||||
data.put("drillRound1", drillRound1);
|
||||
List<VocabularyBankDO> drillRound2 = new ArrayList<>(syncVocabList);
|
||||
List<Word> drillRound2 = new ArrayList<>(list);
|
||||
Collections.shuffle(drillRound2);
|
||||
data.put("drillRound2", drillRound2);
|
||||
List<VocabularyBankDO> drillRound3 = new ArrayList<>(syncVocabList);
|
||||
List<Word> drillRound3 = new ArrayList<>(list);
|
||||
Collections.shuffle(drillRound3);
|
||||
data.put("drillRound3", drillRound3);
|
||||
|
||||
@@ -211,8 +284,10 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
mixedDrill.addAll(syncVocabList);
|
||||
mixedDrill.addAll(gapVocabList);
|
||||
mixedDrill.addAll(reviewVocabList);
|
||||
Collections.shuffle(mixedDrill);
|
||||
data.put("mixedDrill", mixedDrill);
|
||||
List<Word> mixedList = new ArrayList<>(mixedDrill.stream().map(vocabularyBankDO -> Word.builder().title(vocabularyBankDO.getWord()).definition(vocabularyBankDO.getDefinition()).build()).toList());
|
||||
mixedList.forEach(word -> word.setDefinition(" "));
|
||||
Collections.shuffle(mixedList);
|
||||
data.put("mixedDrill", mixedList);
|
||||
|
||||
// 文章 A
|
||||
log.info("生成文章 A 中文开始");
|
||||
@@ -239,6 +314,50 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
data.put("articleBans", mapB.get(LessonPlanConstant.ANSWER_KEY_EXPLANATION));
|
||||
data.put("articleBtran", mapB.get(LessonPlanConstant.FULL_TRANSLATION));
|
||||
|
||||
// 连词成句
|
||||
List<Sentence> sentences = difyClient.sendSentenceAnalyze(syncVocabList.subList(0, Math.max(10, syncVocabList.size())), gradeDO.getTitle());
|
||||
data.put("sentences", sentences);
|
||||
data.put("sentencesAns", sentences);
|
||||
log.info( "生成连词成句成功");
|
||||
|
||||
|
||||
// 教案小测
|
||||
List<VocabularyBankDO> words = new ArrayList<>(syncVocabList);
|
||||
words.addAll(gapVocabList);
|
||||
words.addAll(reviewVocabList);
|
||||
if (words.size() < 100) {
|
||||
words.addAll(vocabularyBankDOMapper.selectVocabularyBankListByGradeIdRandom(gradeDO.getId(), 100 - words.size()));
|
||||
} else {
|
||||
words = words.subList(0, 100);
|
||||
}
|
||||
words.forEach(word -> word.setDefinition(word.getDefinition().length() > 5 ? word.getDefinition().substring(0, 5) : word.getDefinition()));
|
||||
List<Integer> wordIds = words.stream().map(VocabularyBankDO::getId).toList();
|
||||
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||
|
||||
String ExamTitle = gradeDO.getTitle() + unitDO.getTitle() + "教案小测 第" + ExamWordsConstant.day2Chinese(day) + "天" + studentDO.getName();
|
||||
ExamWordsDO examWordsDO = ExamWordsDO.builder()
|
||||
.gradeId(gradeDO.getId())
|
||||
.level(1)
|
||||
.wordIds(wordIds)
|
||||
.type(ExamWordsConstant.EXAM_TYPE_TEST)
|
||||
.title(ExamTitle)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
examWordsDOMapper.insert(examWordsDO);
|
||||
studentExamWordsDOMapper.insertStudentsExam(studentId, examWordsDO.getId());
|
||||
|
||||
data.put("examId", examWordsDO.getId());
|
||||
data.put("studentId", studentId);
|
||||
data.put("studentStr", studentDO.getName());
|
||||
data.put("examStr", ExamTitle);
|
||||
|
||||
List<VocabularyBankDO> words1 = words.subList(0, wordIds.size() / 2);
|
||||
List<VocabularyBankDO> words2 = words.subList(wordIds.size() / 2, wordIds.size());
|
||||
data.put("words1", words1);
|
||||
data.put("words2", words2);
|
||||
|
||||
log.info("生成教案小测成功");
|
||||
|
||||
// LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||
// Configure config = Configure.builder()
|
||||
// .bind("syncVocabList", policy)
|
||||
@@ -269,7 +388,7 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
||||
int i = 0;
|
||||
do {
|
||||
log.info("第{}次生成文章中文开始", ++i);
|
||||
String answer = difyArticleClient.sendChat(string, String.valueOf(studentId) + UUID.randomUUID(), null).getAnswer();
|
||||
String answer = difyClient.sendChat(string, String.valueOf(studentId) + UUID.randomUUID(), null).getAnswer();
|
||||
map = StringToPlanMapUtil.parseTextToMap(answer);
|
||||
} while (map.get(LessonPlanConstant.TITLE) == null
|
||||
|| map.get(LessonPlanConstant.PASSAGE) == null
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.yinlihupo.enlish.service.service.RoleService;
|
||||
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -22,7 +23,7 @@ import java.util.stream.Collectors;
|
||||
public class RoleServiceImpl implements RoleService {
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
@Resource
|
||||
private RoleDOMapper roleDOMapper;
|
||||
@Resource
|
||||
@@ -34,11 +35,12 @@ public class RoleServiceImpl implements RoleService {
|
||||
List<RoleDO> roleDOS = roleDOMapper.selectAll();
|
||||
List<String> roleKeys = roleDOS.stream().map(RoleDO::getRoleKey).toList();
|
||||
log.info("将角色同步到 redis 中, {}", roleKeys);
|
||||
stringRedisTemplate.opsForValue().set(RoleConstants.ROLE, JsonUtils.toJsonString(roleKeys), 60 * 60 * 24);
|
||||
redisTemplate.opsForValue().set(RoleConstants.ROLE, JsonUtils.toJsonString(roleKeys));
|
||||
|
||||
Map<Long, RoleDO> roleId2RoleDO = roleDOS.stream().collect(Collectors.toMap(RoleDO::getId, roleDO -> roleDO));
|
||||
|
||||
List<UserRoleRelDO> userRoleRelDOS = userRoleRelDOMapper.selectAll();
|
||||
log.info("将用户角色关系同步到 redis 中, {}", userRoleRelDOS);
|
||||
Map<Long, List<UserRoleRelDO>> userId2UserRoleRelDOs = userRoleRelDOS.stream().collect(Collectors.groupingBy(UserRoleRelDO::getUserId));
|
||||
|
||||
userId2UserRoleRelDOs.forEach((userId, userRoleRelDOs) -> {
|
||||
@@ -46,7 +48,8 @@ public class RoleServiceImpl implements RoleService {
|
||||
List<RoleDO> roleDOs = roleIds.stream().map(roleId2RoleDO::get).toList();
|
||||
List<String> user2RoleKeys = roleDOs.stream().map(RoleDO::getRoleKey).toList();
|
||||
log.info("将用户 {} 的角色同步到 redis 中, {}", userId, roleKeys);
|
||||
stringRedisTemplate.opsForValue().set(RoleConstants.buildUserRoleKey(userId), JsonUtils.toJsonString(user2RoleKeys));
|
||||
// 不要使用 JsonUtils.toJsonString(user2RoleKeys); 会造成二次序列化
|
||||
redisTemplate.opsForValue().set(RoleConstants.buildUserRoleKey(userId), user2RoleKeys);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,10 @@ import com.yinlihupo.enlish.service.model.bo.exam.ExamWordsJudgeResultDetail;
|
||||
import com.yinlihupo.enlish.service.model.bo.exam.WordMasteryDetail;
|
||||
import com.yinlihupo.enlish.service.model.vo.student.AddStudentReqVO;
|
||||
import com.yinlihupo.enlish.service.service.StudentService;
|
||||
import com.yinlihupo.enlish.service.utils.DifyArticleClient;
|
||||
import com.yinlihupo.enlish.service.utils.DifyClient;
|
||||
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -37,9 +36,11 @@ public class StudentServiceImpl implements StudentService {
|
||||
@Resource
|
||||
private ExamWordsJudgeResultDOMapper examWordsJudgeResultDOMapper;
|
||||
@Resource
|
||||
private DifyArticleClient difyArticleClient;
|
||||
private DifyClient difyClient;
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
@Resource
|
||||
private StudentStageLearningRemarkDOMapper studentStageLearningRemarkDOMapper;
|
||||
|
||||
@Override
|
||||
public List<StudentDO> getStudentsByClassIdAndGradeId(Integer classId, Integer gradeId, String name, Integer pageNo, Integer pageSize) {
|
||||
@@ -119,7 +120,10 @@ public class StudentServiceImpl implements StudentService {
|
||||
|
||||
List<Integer> wordIds = new java.util.ArrayList<>(examWordsJudgeResultDOS.stream().map(ExamWordsJudgeResultDO::getCorrectWordIds).flatMap(List::stream).toList());
|
||||
wordIds.addAll(examWordsJudgeResultDOS.stream().map(ExamWordsJudgeResultDO::getWrongWordIds).flatMap(List::stream).toList());
|
||||
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankMapper.selectVocabularyBankDOListByIds(wordIds);
|
||||
List<VocabularyBankDO> vocabularyBankDOS = new ArrayList<>();
|
||||
if (!wordIds.isEmpty()) {
|
||||
vocabularyBankDOS = vocabularyBankMapper.selectVocabularyBankDOListByIds(wordIds);
|
||||
}
|
||||
Map<Integer, VocabularyBankDO> id2Word = vocabularyBankDOS.stream().collect(Collectors.toMap(VocabularyBankDO::getId, vocabularyBankDO -> vocabularyBankDO));
|
||||
|
||||
List<ExamWordsJudgeResultDetail> examWordsJudgeResultDetails = new ArrayList<>();
|
||||
@@ -157,14 +161,39 @@ public class StudentServiceImpl implements StudentService {
|
||||
studentStudyInfo.put("单词掌握情况", wordMasteryDetails);
|
||||
|
||||
try {
|
||||
String analyze = difyArticleClient.sendStudentAnalyze(JsonUtils.toJsonString(studentStudyInfo)).getAnswer();
|
||||
String analyze = difyClient.sendStudentAnalyze(JsonUtils.toJsonString(studentStudyInfo)).getAnswer();
|
||||
// 设置过期时间 3 天
|
||||
redisTemplate.opsForValue().set(key, analyze);
|
||||
redisTemplate.expire(key, 3, TimeUnit.DAYS);
|
||||
|
||||
studentStageLearningRemarkDOMapper.insert(StudentStageLearningRemarkDO.builder()
|
||||
.studentId(studentId)
|
||||
.commentContent(analyze)
|
||||
.createTime(LocalDateTime.now())
|
||||
.build());
|
||||
|
||||
return analyze;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WordMasteryDetail> findStudentWordMasteryDetail(Integer studentId) {
|
||||
|
||||
List<WordMasteryLogDO> wordMasteryLogDOS = wordMasteryLogDOMapper.selectAllByStudentId(studentId);
|
||||
List<VocabularyBankDO> masteredWords = vocabularyBankMapper.selectVocabularyBankDOListByIds(wordMasteryLogDOS.stream().map(WordMasteryLogDO::getWordId).toList());
|
||||
Map<Integer, VocabularyBankDO> id2MasteryWord = masteredWords.stream().collect(Collectors.toMap(VocabularyBankDO::getId, vocabularyBankDO -> vocabularyBankDO));
|
||||
List<WordMasteryDetail> wordMasteryDetails = new ArrayList<>();
|
||||
for (WordMasteryLogDO wordMasteryLogDO : wordMasteryLogDOS) {
|
||||
wordMasteryDetails.add(WordMasteryDetail.builder()
|
||||
.word(id2MasteryWord.get(wordMasteryLogDO.getWordId()).getWord())
|
||||
.reviewCount(wordMasteryLogDO.getReviewCount())
|
||||
.memoryStrength(wordMasteryLogDO.getMemoryStrength())
|
||||
.update_time(wordMasteryLogDO.getUpdate_time())
|
||||
.build());
|
||||
}
|
||||
return wordMasteryDetails;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package com.yinlihupo.enlish.service.service.user;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.yinlihupo.enlish.service.constant.UserRedisConstants;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||
import com.yinlihupo.enlish.service.domain.mapper.UserDOMapper;
|
||||
import com.yinlihupo.enlish.service.service.UserService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -17,8 +21,10 @@ public class UserServiceImpl implements UserService {
|
||||
|
||||
@Resource
|
||||
private UserDOMapper userDOMapper;
|
||||
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public UserDO findUser() {
|
||||
@@ -43,4 +49,21 @@ public class UserServiceImpl implements UserService {
|
||||
userDOMapper.insert(userDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUserInfo(String password, String reqCode, String phone, String name) {
|
||||
long id = Integer.parseInt(String.valueOf(StpUtil.getLoginId()));
|
||||
UserDO userDO = userDOMapper.selectById(id);
|
||||
|
||||
String key = UserRedisConstants.buildUserLoginCode(userDO.getPhone());
|
||||
String code = Objects.requireNonNull(redisTemplate.opsForValue().get(key)).toString();
|
||||
if (code == null || !code.equals(reqCode)) {
|
||||
throw new RuntimeException("验证码错误");
|
||||
}
|
||||
|
||||
if (password != null) {
|
||||
password = passwordEncoder.encode(password);
|
||||
}
|
||||
userDOMapper.updateUserInfo(id, name, password, phone);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.yinlihupo.enlish.service.service.vocabulary;
|
||||
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||
import com.yinlihupo.enlish.service.domain.mapper.VocabularyBankDOMapper;
|
||||
import com.yinlihupo.enlish.service.domain.mapper.WordMasteryLogDOMapper;
|
||||
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindStudentWordDetailRspVO;
|
||||
import com.yinlihupo.enlish.service.service.VocabularyService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -14,9 +16,19 @@ public class VocabularyServiceImpl implements VocabularyService {
|
||||
|
||||
@Resource
|
||||
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
||||
@Resource
|
||||
private WordMasteryLogDOMapper wordMasteryLogDOMapper;
|
||||
|
||||
@Override
|
||||
public List<VocabularyBankDO> findVocabularyBankDOListById(List<Integer> ids) {
|
||||
return vocabularyBankDOMapper.selectVocabularyBankDOListByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindStudentWordDetailRspVO findStudentWordDetail(Integer studentId) {
|
||||
Integer wordMastery = wordMasteryLogDOMapper.selectMasteryCount(studentId);
|
||||
Integer wordNotMastery = wordMasteryLogDOMapper.selectNotMasteryCount(studentId);
|
||||
Integer total = vocabularyBankDOMapper.selectWordTotal();
|
||||
return FindStudentWordDetailRspVO.builder().masteredWordCount(wordMastery).unmasteredWordCount(wordNotMastery).pendingReviewWordCount(total - wordMastery - wordNotMastery).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.yinlihupo.enlish.service.job;
|
||||
package com.yinlihupo.enlish.service.task;
|
||||
|
||||
import com.yinlihupo.enlish.service.service.ExamWordsJudgeService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AutoJudgeExamWordsTask {
|
||||
|
||||
@Resource
|
||||
@@ -15,7 +15,9 @@ public class AutoJudgeExamWordsTask {
|
||||
|
||||
@Scheduled(fixedRate = 5000)
|
||||
public void autoJudgeExamWords() {
|
||||
System.out.println("【固定频率】开始自动判卷,时间:" + LocalDateTime.now());
|
||||
if (examWordsJudgeService.getExamUnfinishedCount() != 0) {
|
||||
log.info("有试卷待检测,开始检测");
|
||||
examWordsJudgeService.judgeExamWords(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@ 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 com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||
import com.yinlihupo.enlish.service.model.bo.Sentence;
|
||||
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -14,21 +17,25 @@ import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class DifyArticleClient {
|
||||
public class DifyClient {
|
||||
|
||||
@Value("${ai.key}")
|
||||
private String apiKey;
|
||||
private String anaKey = "app-hrUFcopdcpnflsvpHWRuBfCp";
|
||||
@Value("${ai.analyzeKey}")
|
||||
private String analyzeKey;
|
||||
@Value("${ai.sentenceKey}")
|
||||
private String sentenceKey;
|
||||
@Value("${ai.url}")
|
||||
private String baseUrl;
|
||||
private final HttpClient httpClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
// 构造函数
|
||||
public DifyArticleClient() {
|
||||
public DifyClient() {
|
||||
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10)) // 连接超时
|
||||
@@ -36,6 +43,48 @@ public class DifyArticleClient {
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
public List<Sentence> sendSentenceAnalyze(List<VocabularyBankDO> list, String grade) throws Exception {
|
||||
// 1. 构建请求体对象
|
||||
ChatRequest payload = new ChatRequest();
|
||||
payload.setQuery(JsonUtils.toJsonString(list.stream().map(VocabularyBankDO::getWord).toList()));
|
||||
payload.setUser("admin");
|
||||
|
||||
HashMap<String, Object> objectObjectHashMap = new HashMap<>();
|
||||
objectObjectHashMap.put("grade", grade);
|
||||
payload.setResponseMode("blocking"); // 使用阻塞模式,一次性返回
|
||||
payload.setInputs(objectObjectHashMap);
|
||||
|
||||
// 2. 序列化为 JSON 字符串
|
||||
String jsonBody = objectMapper.writeValueAsString(payload);
|
||||
|
||||
// 3. 构建 HTTP 请求
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl))
|
||||
.header("Authorization", "Bearer " + sentenceKey)
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.timeout(Duration.ofSeconds(30)) // 读取超时
|
||||
.build();
|
||||
|
||||
// 4. 发送请求
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// 5. 检查状态码
|
||||
if (response.statusCode() != 200) {
|
||||
throw new RuntimeException("Dify 请求失败: HTTP " + response.statusCode() + " | Body: " + response.body());
|
||||
}
|
||||
|
||||
// 6. 反序列化响应体
|
||||
DifyResponse difyResponse = objectMapper.readValue(response.body(), DifyResponse.class);
|
||||
|
||||
String answer = difyResponse.getAnswer();
|
||||
answer = answer.replace("json", "");
|
||||
answer = answer.replace("```", "");
|
||||
|
||||
return JsonUtils.parseList(answer, Sentence.class);
|
||||
|
||||
}
|
||||
|
||||
public DifyResponse sendStudentAnalyze(String query) throws Exception {
|
||||
String endpoint = this.baseUrl;
|
||||
|
||||
@@ -52,7 +101,7 @@ public class DifyArticleClient {
|
||||
// 3. 构建 HTTP 请求
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(endpoint))
|
||||
.header("Authorization", "Bearer " + anaKey)
|
||||
.header("Authorization", "Bearer " + analyzeKey)
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.timeout(Duration.ofSeconds(30)) // 读取超时
|
||||
@@ -15,6 +15,7 @@ import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@@ -31,29 +32,11 @@ public class PngUtil {
|
||||
// 获取起始坐标
|
||||
public static List<CoordinatesXY> analysisXY(String imagePath) {
|
||||
|
||||
Mat binary = image2BinaryMath(imagePath);
|
||||
Mat src = Imgcodecs.imread(imagePath);
|
||||
|
||||
if (src.empty()) {
|
||||
System.out.println("无法读取图片,请检查路径。");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 预处理
|
||||
// 3.1 转换为灰度图
|
||||
Mat gray = new Mat();
|
||||
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
|
||||
|
||||
// 3.2 二值化处理 (Thresholding)
|
||||
// 使用 THRESH_BINARY_INV (反转二值化),因为我们需要找的是白色背景上的黑色块。
|
||||
// 反转后,黑色块变成白色(255),背景变成黑色(0),方便 findContours 查找。
|
||||
Mat binary = new Mat();
|
||||
// 阈值设为 50 左右即可,因为块是纯黑的
|
||||
Imgproc.threshold(gray, binary, 50, 255, Imgproc.THRESH_BINARY_INV);
|
||||
|
||||
// 4. 查找轮廓
|
||||
List<MatOfPoint> contours = new ArrayList<>();
|
||||
Mat hierarchy = new Mat();
|
||||
// RETR_EXTERNAL 只检测最外层轮廓,忽略块内部可能存在的噪点
|
||||
Imgproc.findContours(binary, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
|
||||
|
||||
System.out.println("检测到的轮廓总数: " + contours.size());
|
||||
@@ -89,67 +72,44 @@ public class PngUtil {
|
||||
System.out.println("------------------------------------------------");
|
||||
list.add(CoordinatesXY.builder().x(rect.x).y(rect.y).width(rect.width).height(rect.height).build());
|
||||
// 可选:在原图上画出框,用于调试验证
|
||||
// Imgproc.rectangle(src, rect, new Scalar(0, 0, 255), 2); // 红色框
|
||||
// Imgproc.putText(src, "#" + blockCount, new Point(rect.x, rect.y - 5), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 255), 1);
|
||||
// Imgcodecs.imwrite("output_red.png", src);
|
||||
Imgproc.rectangle(src, rect, new Scalar(0, 0, 255), 2); // 红色框
|
||||
Imgproc.putText(src, "#" + blockCount, new Point(rect.x, rect.y - 5), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 255), 1);
|
||||
}
|
||||
}
|
||||
Imgcodecs.imwrite("output_red.png", src);
|
||||
System.out.println("找到 " + blockCount + " 个黑色块。");
|
||||
|
||||
// 获取每一列的宽度
|
||||
list.sort(Comparator.comparingInt(CoordinatesXY::getHeight));
|
||||
int height = list.get(list.size() - 1).getHeight() / ExamWordsConstant.PGN_COL;
|
||||
|
||||
// 删除两列答题卡区块
|
||||
list.sort(Comparator.comparingInt(CoordinatesXY::getWidth));
|
||||
list.remove(list.size() - 1);
|
||||
list.remove(list.size() - 1);
|
||||
// 计算起始坐标
|
||||
list.sort(Comparator.comparingInt(CoordinatesXY::getX));
|
||||
|
||||
// 计算起始坐标
|
||||
List<CoordinatesXY> ans = getCoordinatesXIES(list, height);
|
||||
list.forEach(coordinatesXY -> coordinatesXY.setHeight(coordinatesXY.getHeight() / 51));
|
||||
list.forEach(coordinatesXY -> coordinatesXY.setWidth(coordinatesXY.getWidth() / 3));
|
||||
list.forEach(coordinatesXY -> coordinatesXY.setX(coordinatesXY.getX() + coordinatesXY.getWidth() * 2));
|
||||
|
||||
src.release();
|
||||
binary.release();
|
||||
hierarchy.release();
|
||||
binary.release();
|
||||
log.info("起始坐标: {}", list);
|
||||
|
||||
return ans;
|
||||
return list;
|
||||
}
|
||||
|
||||
// 获取(未背熟)单词的 id
|
||||
public static List<Integer> analyzePngForUnmemorizedWordIds(String filePath, List<Integer> wordIds, List<CoordinatesXY> coordinatesXYList) {
|
||||
|
||||
Mat src = Imgcodecs.imread(filePath);
|
||||
if (src.empty()) {
|
||||
log.error("无法读取图片,请检查路径: {}", filePath);
|
||||
throw new RuntimeException("无法读取图片");
|
||||
}
|
||||
|
||||
Mat gray = new Mat();
|
||||
Mat binary = new Mat();
|
||||
Mat binary = image2BinaryMath(filePath);
|
||||
|
||||
try {
|
||||
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
|
||||
// 建议:如果光照不均匀,考虑使用 THRESH_OTSU 自动阈值,或者自适应阈值
|
||||
Imgproc.threshold(gray, binary, 150, 255, Imgproc.THRESH_BINARY_INV);
|
||||
// 调试时打印
|
||||
// Imgcodecs.imwrite("output_binary.png", binary);
|
||||
|
||||
List<Integer> answer = new ArrayList<>();
|
||||
int words_index = 0;
|
||||
|
||||
for (int i = 0; i < coordinatesXYList.size(); i++) {
|
||||
CoordinatesXY coordinatesXY = coordinatesXYList.get(i);
|
||||
|
||||
for (CoordinatesXY coordinatesXY : coordinatesXYList) {
|
||||
int width = coordinatesXY.getWidth();
|
||||
int height = coordinatesXY.getHeight();
|
||||
int currentX = coordinatesXY.getX();
|
||||
int currentY = coordinatesXY.getY();
|
||||
|
||||
int count = i == 0 ? ExamWordsConstant.PGN_COL - 1 : ExamWordsConstant.PGN_COL;
|
||||
int currentY = coordinatesXY.getY() + height;
|
||||
|
||||
// 内层循环:遍历这一列的每一行
|
||||
for (int j = 0; j < count; j++) {
|
||||
for (int j = 0; j < 50; j++) {
|
||||
// 安全检查:防止单词列表比格子少导致越界
|
||||
if (words_index >= wordIds.size()) {
|
||||
log.warn("单词列表耗尽,停止检测。格子数多于单词数。");
|
||||
@@ -168,11 +128,14 @@ public class PngUtil {
|
||||
Rect rect = new Rect(currentX + 1, currentY + 1, width - 2, height - 2);
|
||||
Mat region = binary.submat(rect);
|
||||
int countNonZero = Core.countNonZero(region);
|
||||
|
||||
if (countNonZero > 800) {
|
||||
log.info("当前位置为 words_index={},坐标为 x={} y={} 当前区域非零像素数: {}", words_index, currentX, currentY, countNonZero);
|
||||
if (countNonZero > 1000) {
|
||||
Integer id = wordIds.get(words_index);
|
||||
answer.add(id);
|
||||
log.info("检测到标记(未背熟):ID={}", id);
|
||||
log.info("检测到标记(未背熟):ID={}, 当前坐标 x = {} y = {} ", id, currentX + 1, currentY + 1);
|
||||
}
|
||||
if (countNonZero == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
region.release();
|
||||
@@ -185,8 +148,6 @@ public class PngUtil {
|
||||
|
||||
} finally {
|
||||
|
||||
src.release();
|
||||
gray.release();
|
||||
binary.release();
|
||||
}
|
||||
}
|
||||
@@ -206,21 +167,21 @@ public class PngUtil {
|
||||
Rect roiRect = new Rect(0, 0, left.getX(), left.getY());
|
||||
Mat roi = new Mat(src, roiRect);
|
||||
|
||||
// 3. 图像预处理 (提高 OCR 准确率)
|
||||
// 3.1 转为灰度图
|
||||
Mat gray = new Mat();
|
||||
Imgproc.cvtColor(roi, gray, Imgproc.COLOR_BGR2GRAY);
|
||||
|
||||
// 3.2 二值化 (Thresholding)
|
||||
// 使用 OTSU 算法自动寻找最佳阈值,或者手动指定阈值
|
||||
Mat binary = new Mat();
|
||||
Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
|
||||
// // 3. 图像预处理 (提高 OCR 准确率)
|
||||
// // 3.1 转为灰度图
|
||||
// Mat gray = new Mat();
|
||||
// Imgproc.cvtColor(roi, gray, Imgproc.COLOR_BGR2GRAY);
|
||||
//
|
||||
// // 3.2 二值化 (Thresholding)
|
||||
// // 使用 OTSU 算法自动寻找最佳阈值,或者手动指定阈值
|
||||
// Mat binary = new Mat();
|
||||
// Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
|
||||
|
||||
// 可选:保存预处理后的图片查看效果
|
||||
// Imgcodecs.imwrite("debug_roi.jpg", binary);
|
||||
Imgcodecs.imwrite("debug_roi.jpg", src);
|
||||
|
||||
// 4. 将 OpenCV Mat 转换为 BufferedImage (供 Tess4J 使用)
|
||||
BufferedImage processedImage = matToBufferedImage(binary);
|
||||
BufferedImage processedImage = matToBufferedImage(src);
|
||||
|
||||
// 5. 使用 Tesseract 进行 OCR 识别
|
||||
ITesseract instance = new Tesseract();
|
||||
@@ -245,6 +206,50 @@ public class PngUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Mat image2BinaryMath(String imagePath) {
|
||||
|
||||
if (!new File(imagePath).exists()) {
|
||||
log.error("图片不存在,请检查路径: {}", imagePath);
|
||||
throw new RuntimeException("图片不存在");
|
||||
}
|
||||
|
||||
Mat src = Imgcodecs.imread(imagePath);
|
||||
|
||||
if (src.empty()) {
|
||||
log.info("无法读取图片,请检查路径: {}", imagePath);
|
||||
throw new RuntimeException("无法读取图片");
|
||||
}
|
||||
|
||||
Mat gray = new Mat();
|
||||
//转换为灰度图
|
||||
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
|
||||
|
||||
Imgproc.GaussianBlur(gray, gray, new Size(5, 5), 0);
|
||||
|
||||
Mat binary = new Mat();
|
||||
Imgproc.adaptiveThreshold(gray, binary, 255,
|
||||
Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
Imgproc.THRESH_BINARY_INV, 25, 10);
|
||||
|
||||
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
|
||||
|
||||
//开运算 (Open):先腐蚀后膨胀,用于去除背景中的微小噪点
|
||||
Imgproc.morphologyEx(binary, binary, Imgproc.MORPH_OPEN, kernel);
|
||||
|
||||
// 闭运算 (Close):先膨胀后腐蚀,用于连接断裂的区域并填充块内部的空洞
|
||||
// 如果块比较大且内部反光严重,可以将 Size(3,3) 改为 Size(5,5) 或更大
|
||||
Mat closeKernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(7, 7));
|
||||
Imgproc.morphologyEx(binary, binary, Imgproc.MORPH_CLOSE, closeKernel);
|
||||
|
||||
// 保存二值化过程图用于调试 (生产环境可注释)
|
||||
Imgcodecs.imwrite("debug_binary_natural.png", binary);
|
||||
|
||||
src.release();
|
||||
gray.release();
|
||||
|
||||
return binary;
|
||||
}
|
||||
|
||||
private static @NonNull StudentExamId getStudentExamId(Pattern pattern, String result) {
|
||||
Matcher matcher = pattern.matcher(result);
|
||||
StudentExamId studentExamId = new StudentExamId(0, 0);
|
||||
@@ -276,21 +281,4 @@ public class PngUtil {
|
||||
return image;
|
||||
}
|
||||
|
||||
private static @NonNull List<CoordinatesXY> getCoordinatesXIES(List<CoordinatesXY> list, int height) {
|
||||
List<CoordinatesXY> ans = new ArrayList<>();
|
||||
CoordinatesXY left = new CoordinatesXY();
|
||||
left.setX(list.get(1).getX());
|
||||
left.setWidth(list.get(1).getWidth());
|
||||
left.setHeight(height);
|
||||
left.setY(list.get(0).getY() + left.getHeight());
|
||||
ans.add(left);
|
||||
|
||||
CoordinatesXY right = new CoordinatesXY();
|
||||
right.setX(list.get(2).getX());
|
||||
right.setY(list.get(0).getY());
|
||||
right.setWidth(list.get(1).getWidth());
|
||||
right.setHeight(height);
|
||||
ans.add(right);
|
||||
return ans;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.yinlihupo.enlish.service.utils;
|
||||
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.openai.OpenAiAudioSpeechModel;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class TTSUtil {
|
||||
|
||||
private final OpenAiAudioSpeechModel speechModel;
|
||||
|
||||
public TTSUtil(OpenAiAudioSpeechModel speechModel) {
|
||||
this.speechModel = speechModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成语音并将二进制流写入 HTTP 响应
|
||||
*
|
||||
* @param text 需要转换的文本
|
||||
* @param response HTTP 响应对象
|
||||
*/
|
||||
public void generateWordVoice(String text, HttpServletResponse response) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(text)) {
|
||||
sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Input text cannot be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 3. 调用 OpenAI 接口获取音频数据
|
||||
byte[] audioBytes = speechModel.call(text);
|
||||
|
||||
// 4. 设置 HTTP 响应头
|
||||
response.setContentType("audio/mpeg");
|
||||
response.setContentLength(audioBytes.length);
|
||||
|
||||
// 可选:如果不希望浏览器自动播放,而是强制下载,请取消下面这行的注释
|
||||
// response.setHeader("Content-Disposition", "attachment; filename=\"speech.mp3\"");
|
||||
|
||||
// 5. 将音频数据写入响应流
|
||||
try (ServletOutputStream outputStream = response.getOutputStream()) {
|
||||
outputStream.write(audioBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("TTS Generation failed: {}", e.getMessage());
|
||||
sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "TTS Generation failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:发送错误响应
|
||||
private void sendError(HttpServletResponse response, int status, String message) {
|
||||
try {
|
||||
response.sendError(status, message);
|
||||
} catch (IOException e) {
|
||||
// 忽略这里的错误,因为响应可能已经提交
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,25 @@ package com.yinlihupo.enlish.service.utils;
|
||||
|
||||
import com.deepoove.poi.XWPFTemplate;
|
||||
import com.deepoove.poi.config.Configure;
|
||||
import com.deepoove.poi.data.PictureType;
|
||||
import com.deepoove.poi.data.Pictures;
|
||||
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsDO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
@@ -23,6 +36,8 @@ public class WordExportUtil {
|
||||
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||
config = Configure.builder()
|
||||
.bind("words", policy)
|
||||
.bind("words1", policy)
|
||||
.bind("words2", policy)
|
||||
.bind("answer", policy)
|
||||
.build();
|
||||
|
||||
@@ -37,18 +52,23 @@ public class WordExportUtil {
|
||||
.bind("mixedDrill", policyLessonPlanWeekday)
|
||||
.bind("checkList", policyLessonPlanWeekday)
|
||||
.bind("checkListAns", policyLessonPlanWeekday)
|
||||
.bind("sentences", policyLessonPlanWeekday)
|
||||
.bind("sentencesAns", policyLessonPlanWeekday)
|
||||
.bind("words1", policy)
|
||||
.bind("words2", policy)
|
||||
.build();
|
||||
|
||||
LoopRowTableRenderPolicy policyLessonPlan = new LoopRowTableRenderPolicy();
|
||||
configLessonPlanWeekend = Configure.builder()
|
||||
.bind("checkList", policyLessonPlan)
|
||||
.bind("words1", policy)
|
||||
.bind("words2", policy)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共入口:根据数据量决定是导出单文件还是压缩包
|
||||
*/
|
||||
public static void generateExamWords(List<Map<String, Object>> data, HttpServletResponse response, String templateWordPath) {
|
||||
public static void generateExamWords(List<Map<String, Object>> data, ExamWordsDO examWordsDO, HttpServletResponse response, String templateWordPath) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -56,10 +76,10 @@ public class WordExportUtil {
|
||||
try {
|
||||
if (data.size() == 1) {
|
||||
// 如果只有一份数据,直接导出 docx,用户体验更好
|
||||
generateExamWordsDocx(data.get(0), response, templateWordPath);
|
||||
generateExamWordsDocx(data.get(0), examWordsDO, response, templateWordPath);
|
||||
} else {
|
||||
// 如果有多份数据,打包导出
|
||||
generateExamWordsZip(data, response, templateWordPath);
|
||||
generateExamWordsZip(data, examWordsDO, response, templateWordPath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@@ -67,8 +87,8 @@ public class WordExportUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateLessonPlanDocx(Map<String, Object> map, String fileName, HttpServletResponse response, String templateWordPath, boolean isWeekday) throws IOException {
|
||||
fileName = URLEncoder.encode(fileName + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||
public static void generateLessonPlanDocx(Map<String, Object> map, LessonPlansDO lessonPlan, HttpServletResponse response, String templateWordPath, boolean isWeekday) throws IOException {
|
||||
String fileName = URLEncoder.encode(lessonPlan.getTitle() + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||
|
||||
// 3. 设置响应头
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||
@@ -81,6 +101,8 @@ public class WordExportUtil {
|
||||
} else {
|
||||
template = XWPFTemplate.compile(inputStream, configLessonPlanWeekend);
|
||||
}
|
||||
String url = "http://english.yinlihupo.cn/#/plan/tts?planId=" + lessonPlan.getId();
|
||||
map.put("img", Pictures.ofBytes(generateQR(url), PictureType.PNG).create());
|
||||
OutputStream out = response.getOutputStream();
|
||||
template.render(map);
|
||||
template.write(out);
|
||||
@@ -92,9 +114,9 @@ public class WordExportUtil {
|
||||
/**
|
||||
* 核心补充:批量渲染并打包为 ZIP
|
||||
*/
|
||||
private static void generateExamWordsZip(List<Map<String, Object>> data, HttpServletResponse response, String templateWordPath) throws IOException {
|
||||
private static void generateExamWordsZip(List<Map<String, Object>> data, ExamWordsDO examWordsDO, HttpServletResponse response, String templateWordPath) throws IOException {
|
||||
// 1. 设置响应头为 ZIP
|
||||
String zipName = URLEncoder.encode("批量导出_摸底测试.zip", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||
String zipName = URLEncoder.encode("批量导出_" + examWordsDO.getTitle() + ".zip", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||
response.setContentType("application/zip");
|
||||
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + zipName);
|
||||
|
||||
@@ -106,7 +128,7 @@ public class WordExportUtil {
|
||||
for (Map<String, Object> itemData : data) {
|
||||
// 3. 确定压缩包内的文件名
|
||||
// 优先从 map 中获取 'fileName' 字段,否则使用默认编号
|
||||
String entryName = (String) itemData.getOrDefault("fileName", "摸底测试_" + index);
|
||||
String entryName = (String) itemData.getOrDefault("fileName", + index);
|
||||
// 确保文件名后缀正确
|
||||
if (!entryName.endsWith(".docx")) {
|
||||
entryName += ".docx";
|
||||
@@ -143,9 +165,9 @@ public class WordExportUtil {
|
||||
/**
|
||||
* 现有的单文件导出逻辑
|
||||
*/
|
||||
private static void generateExamWordsDocx(Map<String, Object> data, HttpServletResponse response, String templateWordPath) throws IOException {
|
||||
private static void generateExamWordsDocx(Map<String, Object> data, ExamWordsDO examWordsDO, HttpServletResponse response, String templateWordPath) throws IOException {
|
||||
|
||||
String fileName = URLEncoder.encode("摸底测试" + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||
String fileName = URLEncoder.encode(examWordsDO.getTitle() + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||
|
||||
// 3. 设置响应头
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||
@@ -160,4 +182,38 @@ public class WordExportUtil {
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] generateQR(String content) {
|
||||
int width = 300;
|
||||
int height = 300;
|
||||
String format = "png";
|
||||
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
|
||||
hints.put(EncodeHintType.MARGIN, 1);
|
||||
|
||||
try {
|
||||
// 1. 生成比特矩阵
|
||||
BitMatrix bitMatrix = new MultiFormatWriter().encode(
|
||||
content,
|
||||
BarcodeFormat.QR_CODE,
|
||||
width,
|
||||
height,
|
||||
hints
|
||||
);
|
||||
|
||||
// 2. 将 BitMatrix 转为字节数组
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
// MatrixToImageWriter 是 Zxing 提供的工具类
|
||||
MatrixToImageWriter.writeToStream(bitMatrix, format, outputStream);
|
||||
|
||||
return outputStream.toByteArray();
|
||||
|
||||
} catch (WriterException | IOException e) {
|
||||
// 建议增加对 IOException 的捕获,因为涉及流操作
|
||||
throw new RuntimeException("生成二维码失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver # 指定数据库驱动类
|
||||
# 数据库连接信息
|
||||
url: jdbc:mysql://124.220.58.5:3306/enlish?allowMultiQueries=true
|
||||
url: jdbc:mysql://124.220.58.5:3306/dev_english?allowMultiQueries=true
|
||||
username: root # 数据库用户名
|
||||
password: YLHP@admin123 # 数据库密码
|
||||
data:
|
||||
@@ -19,21 +19,32 @@ spring:
|
||||
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
min-idle: 0 # 连接池中的最小空闲连接
|
||||
max-idle: 10 # 连接池中的最大空闲连接
|
||||
ai:
|
||||
openai:
|
||||
api-key: your_api_key_here
|
||||
base-url: http://124.220.58.5:2233
|
||||
audio:
|
||||
speech:
|
||||
options:
|
||||
model: tts-1
|
||||
voice: alloy
|
||||
|
||||
|
||||
templates:
|
||||
word: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\assessment_v5.docx
|
||||
word: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\assessment_v9.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
|
||||
weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v7.docx
|
||||
weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v3.docx
|
||||
plan_day: 7
|
||||
tmp:
|
||||
png: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\tmp\png\
|
||||
png:
|
||||
|
||||
ai:
|
||||
key: app-loC6IrJpj4cS54MAYp73QtGl
|
||||
analyzeKey: app-hrUFcopdcpnflsvpHWRuBfCp
|
||||
sentenceKey: app-Emk5YQBaD2YruRXuE5sK1vEU
|
||||
url: https://chat.cosonggle.com/v1/chat-messages
|
||||
|
||||
aliyun:
|
||||
|
||||
52
enlish-service/src/main/resources/config/application-pro.yml
Normal file
52
enlish-service/src/main/resources/config/application-pro.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver # 指定数据库驱动类
|
||||
# 数据库连接信息
|
||||
url: jdbc:mysql://124.220.58.5:3306/enlish?allowMultiQueries=true
|
||||
username: root # 数据库用户名
|
||||
password: YLHP@admin123 # 数据库密码
|
||||
data:
|
||||
redis:
|
||||
database: 6 # Redis 数据库索引(默认为 0)
|
||||
host: 124.220.58.5 # Redis 服务器地址
|
||||
port: 6543 # Redis 服务器连接端口
|
||||
password: 741963 # Redis 服务器连接密码(默认为空)
|
||||
timeout: 5s # 读超时时间
|
||||
connect-timeout: 5s # 链接超时时间
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 200 # 连接池最大连接数
|
||||
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
min-idle: 0 # 连接池中的最小空闲连接
|
||||
max-idle: 10 # 连接池中的最大空闲连接
|
||||
ai:
|
||||
openai:
|
||||
api-key: your_api_key_here
|
||||
base-url: http://124.220.58.5:2233
|
||||
audio:
|
||||
speech:
|
||||
options:
|
||||
model: tts-1
|
||||
voice: alloy
|
||||
|
||||
|
||||
templates:
|
||||
word: assessment_v9.docx
|
||||
count: 100
|
||||
data:
|
||||
plan:
|
||||
weekday: tem_study_plan_v7.docx
|
||||
weekend: study_plan_review_v3.docx
|
||||
plan_day: 7
|
||||
tmp:
|
||||
png:
|
||||
|
||||
ai:
|
||||
key: app-loC6IrJpj4cS54MAYp73QtGl
|
||||
analyzeKey: app-hrUFcopdcpnflsvpHWRuBfCp
|
||||
sentenceKey: app-Emk5YQBaD2YruRXuE5sK1vEU
|
||||
url: https://chat.cosonggle.com/v1/chat-messages
|
||||
|
||||
aliyun:
|
||||
accessKeyId:
|
||||
accessKeySecret:
|
||||
@@ -3,12 +3,16 @@ server:
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: dev # 默认激活 dev 本地开发环境
|
||||
|
||||
active: pro # 默认激活 dev 本地开发环境
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 30MB
|
||||
max-request-size: 30MB
|
||||
mybatis:
|
||||
# MyBatis xml 配置文件路径
|
||||
mapper-locations: classpath:/mapper/**/*.xml
|
||||
|
||||
|
||||
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
|
||||
sa-token:
|
||||
# token 名称(同时也是 cookie 名称)
|
||||
@@ -27,3 +31,6 @@ sa-token:
|
||||
is-share: true
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
#logging:
|
||||
# level:
|
||||
# com.yinlihupo.enlish.service.domain.mapper: debug
|
||||
@@ -45,7 +45,7 @@
|
||||
targetProject="src/main/java"/>
|
||||
|
||||
<!-- 需要生成的表-实体类 -->
|
||||
<table tableName="user_role_rel" domainObjectName="UserRoleRelDO"
|
||||
<table tableName="plan_exam" domainObjectName="PlanExamDO"
|
||||
enableCountByExample="false"
|
||||
enableUpdateByExample="false"
|
||||
enableDeleteByExample="false"
|
||||
|
||||
@@ -14,10 +14,16 @@
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into exam_words (grade_id, level, title, word_ids, created_at)
|
||||
VALUES (#{gradeId}, #{level}, #{title}, #{wordIds, typeHandler=com.yinlihupo.enlish.service.config.ListWordIdTypeHandler}, #{createdAt})
|
||||
insert into exam_words (grade_id, level, title, word_ids, type, created_at)
|
||||
VALUES (#{gradeId}, #{level}, #{title}, #{wordIds, typeHandler=com.yinlihupo.enlish.service.config.ListWordIdTypeHandler}, #{type}, #{createdAt})
|
||||
</insert>
|
||||
|
||||
<update id="updateWordIdsOrder">
|
||||
update exam_words
|
||||
set word_ids = #{wordIds, typeHandler=com.yinlihupo.enlish.service.config.ListWordIdTypeHandler}
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="selectById" resultMap="ResultMapWithBLOBs">
|
||||
select * from exam_words where id = #{id}
|
||||
</select>
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
select count(1)
|
||||
from exam_words_judge_result
|
||||
</select>
|
||||
|
||||
<select id="selectDetailById" resultMap="ResultMapWithBLOBs">
|
||||
select *
|
||||
from exam_words_judge_result
|
||||
@@ -81,4 +82,22 @@
|
||||
and start_date between date_sub(now(), interval 7 day) and now()
|
||||
</select>
|
||||
|
||||
<select id="selectByPageAndStudentIds" resultMap="BaseResultMap">
|
||||
select *
|
||||
from exam_words_judge_result
|
||||
where student_id in
|
||||
<foreach item="item" index="index" collection="studentIds"
|
||||
open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
order by start_date
|
||||
limit #{startIndex}, #{pageSize}
|
||||
</select>
|
||||
|
||||
<select id="selectUnfinishedCount" resultType="java.lang.Integer">
|
||||
select count(*)
|
||||
from exam_words_judge_result
|
||||
where is_finished = 0
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -39,4 +39,15 @@
|
||||
where id = #{lessonId}
|
||||
</select>
|
||||
|
||||
<select id="selectByStudentId" resultMap="BaseResultMap">
|
||||
select *
|
||||
from lesson_plans
|
||||
where id in (
|
||||
select student_lesson_plans.plan_id
|
||||
from student_lesson_plans
|
||||
where student_id = #{studentId}
|
||||
and is_finished = 0
|
||||
)
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.yinlihupo.enlish.service.domain.mapper.PlanExamDOMapper">
|
||||
<resultMap id="BaseResultMap" type="com.yinlihupo.enlish.service.domain.dataobject.PlanExamDO">
|
||||
<id column="id" jdbcType="INTEGER" property="id" />
|
||||
<result column="plan_id" jdbcType="INTEGER" property="planId" />
|
||||
<result column="exam_id" jdbcType="INTEGER" property="examId" />
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into plan_exam (plan_id, exam_id)
|
||||
values (#{planId}, #{examId})
|
||||
</insert>
|
||||
<select id="selectByExamId" resultMap="BaseResultMap">
|
||||
select * from plan_exam where exam_id = #{examId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -75,4 +75,24 @@
|
||||
where class_id = #{classId}
|
||||
and is_deleted = 0
|
||||
</select>
|
||||
|
||||
<select id="selectStudentDOListByClassId" resultMap="BaseResultMap">
|
||||
select *
|
||||
from student
|
||||
where class_id = #{classId}
|
||||
and is_deleted = 0
|
||||
</select>
|
||||
|
||||
<select id="selectStudentDOListByGradeId" resultMap="BaseResultMap">
|
||||
select *
|
||||
from student
|
||||
where grade_id = #{gradeId}
|
||||
and is_deleted = 0
|
||||
</select>
|
||||
<select id="selectStudentDOListByName" resultMap="BaseResultMap">
|
||||
select *
|
||||
from student
|
||||
where name like concat('%', #{name}, '%')
|
||||
and is_deleted = 0
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -21,7 +21,6 @@
|
||||
from student_exam_words
|
||||
where student_id = #{studentId}
|
||||
and exam_words_id = #{examWordsId}
|
||||
and is_completed = 0
|
||||
</select>
|
||||
|
||||
<update id="updateStudentExamWordsFinished">
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.yinlihupo.enlish.service.domain.mapper.StudentStageLearningRemarkDOMapper">
|
||||
<resultMap id="BaseResultMap" type="com.yinlihupo.enlish.service.domain.dataobject.StudentStageLearningRemarkDO">
|
||||
<id column="id" jdbcType="INTEGER" property="id" />
|
||||
<result column="student_id" jdbcType="INTEGER" property="studentId" />
|
||||
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
|
||||
</resultMap>
|
||||
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.yinlihupo.enlish.service.domain.dataobject.StudentStageLearningRemarkDO">
|
||||
<result column="comment_content" jdbcType="LONGVARCHAR" property="commentContent" />
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert">
|
||||
insert into student_stage_learning_remark (student_id, create_time, comment_content)
|
||||
values (#{studentId,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, #{commentContent,jdbcType=LONGVARCHAR})
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
@@ -101,5 +101,10 @@
|
||||
select count(*)
|
||||
from unit
|
||||
</select>
|
||||
<select id="selectByUnitName" resultType="com.yinlihupo.enlish.service.domain.dataobject.UnitDO">
|
||||
select *
|
||||
from unit
|
||||
where title like concat(#{unitName}, '%')
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -20,6 +20,28 @@
|
||||
values (#{phone}, #{name}, #{password})
|
||||
</insert>
|
||||
|
||||
<update id="updatePassword">
|
||||
update user
|
||||
set password = #{password}
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="updateUserInfo">
|
||||
update user
|
||||
<set>
|
||||
<if test="password != null">
|
||||
password = #{password},
|
||||
</if>
|
||||
<if test="name != null">
|
||||
`name` = #{name},
|
||||
</if>
|
||||
<if test="phone != null">
|
||||
phone = #{phone},
|
||||
</if>
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="selectByPhone" resultMap="BaseResultMap">
|
||||
select *
|
||||
from user
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<select id="selectAll" resultMap="BaseResultMap">
|
||||
select *
|
||||
from user_role_rel
|
||||
where is_deleted = 0
|
||||
</select>
|
||||
|
||||
<select id="selectByUserIds" resultMap="BaseResultMap">
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
and id in (
|
||||
select word_id
|
||||
from word_mastery_log
|
||||
where memory_strength < 0
|
||||
where memory_strength <= 0
|
||||
and student_id = #{studentId}
|
||||
)
|
||||
]]>
|
||||
@@ -135,4 +135,31 @@
|
||||
limit #{wordCount}
|
||||
</select>
|
||||
|
||||
<select id="selectByUnitIds" resultMap="BaseResultMap">
|
||||
select *
|
||||
from vocabulary_bank
|
||||
where unit_id in
|
||||
<foreach item="unitId" collection="unitIds" separator="," open="(" close=")">
|
||||
#{unitId}
|
||||
</foreach>
|
||||
order by rand()
|
||||
limit 100
|
||||
</select>
|
||||
<select id="selectByGradeIdAndNotMatchIds"
|
||||
resultType="com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO">
|
||||
select *
|
||||
from vocabulary_bank
|
||||
where unit_id in (
|
||||
select unit_id
|
||||
from grade_unit
|
||||
where grade_id = #{gradeId}
|
||||
)
|
||||
and id not in
|
||||
<foreach item="id" collection="ids" separator="," open="(" close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
order by rand()
|
||||
limit #{wordCount}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -22,6 +22,7 @@
|
||||
order by memory_strength desc
|
||||
limit #{limit}
|
||||
</select>
|
||||
|
||||
<select id="selectStudentStrengthCount" resultType="java.lang.Integer">
|
||||
select count(*)
|
||||
from word_mastery_log
|
||||
@@ -58,4 +59,27 @@
|
||||
where student_id = #{studentId}
|
||||
and update_time between date_sub(now(), interval 7 day) and now()
|
||||
</select>
|
||||
|
||||
<select id="selectAllByStudentId" resultMap="BaseResultMap">
|
||||
select *
|
||||
from word_mastery_log
|
||||
where student_id = #{studentId}
|
||||
</select>
|
||||
|
||||
<select id="selectMasteryCount" resultType="java.lang.Integer">
|
||||
select count(*)
|
||||
from word_mastery_log
|
||||
where student_id = #{studentId}
|
||||
and memory_strength >= 1
|
||||
</select>
|
||||
<select id="selectNotMasteryCount" resultType="java.lang.Integer">
|
||||
<![CDATA[
|
||||
select count(*)
|
||||
from word_mastery_log
|
||||
where student_id = #{studentId}
|
||||
and memory_strength < 0
|
||||
]]>
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
||||
BIN
enlish-service/src/main/resources/templates/assessment_v6.docx
Normal file
BIN
enlish-service/src/main/resources/templates/assessment_v6.docx
Normal file
Binary file not shown.
BIN
enlish-service/src/main/resources/templates/assessment_v7.docx
Normal file
BIN
enlish-service/src/main/resources/templates/assessment_v7.docx
Normal file
Binary file not shown.
BIN
enlish-service/src/main/resources/templates/assessment_v8.docx
Normal file
BIN
enlish-service/src/main/resources/templates/assessment_v8.docx
Normal file
Binary file not shown.
BIN
enlish-service/src/main/resources/templates/assessment_v9.docx
Normal file
BIN
enlish-service/src/main/resources/templates/assessment_v9.docx
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 461 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,47 +1,32 @@
|
||||
package com.yinlihupo.enlish.service.ai;
|
||||
|
||||
import com.yinlihupo.enlish.service.utils.DifyArticleClient;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||
import com.yinlihupo.enlish.service.domain.mapper.VocabularyBankDOMapper;
|
||||
import com.yinlihupo.enlish.service.model.bo.Sentence;
|
||||
import com.yinlihupo.enlish.service.utils.DifyClient;
|
||||
import com.yinlihupo.enlish.service.utils.StringToPlanMapUtil;
|
||||
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.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@SpringBootTest
|
||||
public class AITest {
|
||||
|
||||
@Resource
|
||||
private DifyArticleClient client;
|
||||
private DifyClient client;
|
||||
@Resource
|
||||
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
||||
|
||||
@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<String, String> 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();
|
||||
|
||||
@@ -35,60 +35,68 @@ public class TestVocabularyBankInsert {
|
||||
private GradeUnitDOMapper gradeUnitDOMapper;
|
||||
@Test
|
||||
void test() {
|
||||
String file = "C:\\project\\java\\enlish_edu\\enlish\\enlish-service\\src\\test\\java\\com\\yinlihupo\\enlish\\service\\mapper\\3上.xlsx";
|
||||
HashMap<String, Integer> map = new HashMap<>();
|
||||
int gradeId = 3;
|
||||
try (FileInputStream fis = new FileInputStream(file); Workbook workbook = new XSSFWorkbook(fis)) {
|
||||
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
|
||||
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
String word = row.getCell(0).getStringCellValue();
|
||||
String meaning = row.getCell(1) != null ? row.getCell(1).getStringCellValue() : "";
|
||||
String gradeUnit = row.getCell(2) != null ? row.getCell(2).getStringCellValue() : "";
|
||||
|
||||
if (word.contains("Unit")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int gradeUnitId = 0;
|
||||
if (map.containsKey(gradeUnit)) {
|
||||
gradeUnitId = map.get(gradeUnit);
|
||||
} else {
|
||||
UnitDO unitDO = unitDOMapper.selectByTitle(gradeUnit);
|
||||
if (unitDO == null) {
|
||||
unitDO = UnitDO.builder()
|
||||
.title(gradeUnit)
|
||||
.version("人教版")
|
||||
.createAt(LocalDateTime.now())
|
||||
.build();
|
||||
unitDOMapper.insert(unitDO);
|
||||
gradeUnitDOMapper.insert(GradeUnitDO.builder().unitId(unitDO.getId()).gradeId(gradeId).build());
|
||||
gradeUnitId = unitDO.getId();
|
||||
} else {
|
||||
gradeUnitId = unitDO.getId();
|
||||
}
|
||||
|
||||
map.put(gradeUnit, gradeUnitId);
|
||||
}
|
||||
VocabularyBankDO vocabularyBankDO = VocabularyBankDO.builder()
|
||||
.word(word)
|
||||
.definition(meaning)
|
||||
.pronunciation("")
|
||||
.unitId(gradeUnitId)
|
||||
.build();
|
||||
vocabularyBankMapper.insertSelective(vocabularyBankDO);
|
||||
log.info("插入数据 {} ", vocabularyBankDO);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// String file = "C:\\project\\java\\enlish_edu\\enlish\\enlish-service\\src\\test\\java\\com\\yinlihupo\\enlish\\service\\mapper\\八下.xlsx";
|
||||
// HashMap<String, Integer> map = new HashMap<>();
|
||||
// int gradeId = 8;
|
||||
// try (FileInputStream fis = new FileInputStream(file); Workbook workbook = new XSSFWorkbook(fis)) {
|
||||
//
|
||||
// Sheet sheet = workbook.getSheetAt(0);
|
||||
//
|
||||
// for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||
// Row row = sheet.getRow(i);
|
||||
// if (row == null) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// String word = row.getCell(0).getStringCellValue();
|
||||
// String pronunciation = row.getCell(1) != null ? row.getCell(1).getStringCellValue() : "";
|
||||
// String pos = row.getCell(2) != null ? row.getCell(2).getStringCellValue() : "";
|
||||
// String meaning = row.getCell(3) != null ? row.getCell(3).getStringCellValue() : "";
|
||||
// String gradeUnit = row.getCell(4) != null ? row.getCell(4).getStringCellValue() : "";
|
||||
//
|
||||
// String word = row.getCell(0).getStringCellValue();
|
||||
// String meaning = row.getCell(1) != null ? row.getCell(1).getStringCellValue() : "";
|
||||
// String gradeUnit = row.getCell(2) != null ? row.getCell(2).getStringCellValue() : "";
|
||||
// String pronunciation = "";
|
||||
// String pos = "";
|
||||
// if (word.contains("Unit")) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// int gradeUnitId;
|
||||
// if (map.containsKey(gradeUnit)) {
|
||||
// gradeUnitId = map.get(gradeUnit);
|
||||
// } else {
|
||||
// UnitDO unitDO = unitDOMapper.selectByTitle(gradeUnit);
|
||||
// if (unitDO == null) {
|
||||
// unitDO = UnitDO.builder()
|
||||
// .title(gradeUnit)
|
||||
// .version("人教版")
|
||||
// .createAt(LocalDateTime.now())
|
||||
// .build();
|
||||
// unitDOMapper.insert(unitDO);
|
||||
// gradeUnitDOMapper.insert(GradeUnitDO.builder().unitId(unitDO.getId()).gradeId(gradeId).build());
|
||||
// gradeUnitId = unitDO.getId();
|
||||
// } else {
|
||||
// gradeUnitId = unitDO.getId();
|
||||
// }
|
||||
//
|
||||
// map.put(gradeUnit, gradeUnitId);
|
||||
// }
|
||||
// VocabularyBankDO vocabularyBankDO = VocabularyBankDO.builder()
|
||||
// .word(word)
|
||||
// .definition(meaning)
|
||||
// .pronunciation(pronunciation)
|
||||
// .pos(pos)
|
||||
// .unitId(gradeUnitId)
|
||||
// .build();
|
||||
// vocabularyBankMapper.insertSelective(vocabularyBankDO);
|
||||
// log.info("插入数据 {} ", vocabularyBankDO);
|
||||
// }
|
||||
//
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user