|
|
@@ -5,11 +5,13 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.etotem.cfc.dto.ReadingStatsDTO;
|
|
|
import com.etotem.cfc.entity.Article;
|
|
|
import com.etotem.cfc.entity.ArticleCategory;
|
|
|
+import com.etotem.cfc.entity.ArticleQuizRecord;
|
|
|
import com.etotem.cfc.entity.ArticleReadingRecord;
|
|
|
import com.etotem.cfc.entity.Child;
|
|
|
import com.etotem.cfc.entity.PointsLog;
|
|
|
import com.etotem.cfc.entity.Task;
|
|
|
import com.etotem.cfc.mapper.ArticleMapper;
|
|
|
+import com.etotem.cfc.mapper.ArticleQuizRecordMapper;
|
|
|
import com.etotem.cfc.mapper.ArticleReadingRecordMapper;
|
|
|
import com.etotem.cfc.mapper.ChildMapper;
|
|
|
import com.etotem.cfc.mapper.PointsLogMapper;
|
|
|
@@ -55,6 +57,15 @@ public class ArticleService {
|
|
|
@Resource
|
|
|
private EnergyService energyService;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private PointsService pointsService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private AIService aiService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ArticleQuizRecordMapper articleQuizRecordMapper;
|
|
|
+
|
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
|
|
public Page<Article> getPublicList(Long categoryId, String keyword, String dimensionCode, int page, int size,
|
|
|
@@ -410,4 +421,133 @@ public class ArticleService {
|
|
|
fallback.put("coverImage", null);
|
|
|
return fallback;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据文章内容和用户画像生成 2 道选择题
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public Map<String, Object> getAiQuestions(Long articleId, Long userId, Long childId) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+
|
|
|
+ // 1. 获取文章
|
|
|
+ Article article = articleMapper.selectById(articleId);
|
|
|
+ if (article == null) {
|
|
|
+ throw new RuntimeException("文章不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 构建 Dify prompt
|
|
|
+ String contentPreview = article.getContent() != null ? article.getContent() : "";
|
|
|
+ if (contentPreview.length() > 800) {
|
|
|
+ contentPreview = contentPreview.substring(0, 800);
|
|
|
+ }
|
|
|
+ String prompt = "你是一个家庭教育助手。根据以下文章内容,为儿童生成 2 道选择题(每题 4 个选项)。"
|
|
|
+ + "题目应该适合该年龄段的孩子理解。"
|
|
|
+ + "请以 JSON 数组格式返回,格式:[{\"question\":\"问题\",\"options\":[\"A.选项 1\",\"B.选项 2\",\"C.选项 3\",\"D.选项 4\"],\"answer\":\"A\"}]"
|
|
|
+ + "不要返回其他文字,只返回 JSON 数组。"
|
|
|
+ + "\n\n文章标题:" + article.getTitle()
|
|
|
+ + "\n文章内容:" + contentPreview;
|
|
|
+
|
|
|
+ // 3. 调用 Dify AI
|
|
|
+ Map<String, Object> aiResult = aiService.sendMessage(prompt, String.valueOf(userId), "", null);
|
|
|
+ String aiResponse = (String) aiResult.get("answer");
|
|
|
+ if (aiResponse == null) {
|
|
|
+ aiResponse = "";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 提取 JSON 数组
|
|
|
+ String questionsJson = extractJsonFromResponse(aiResponse);
|
|
|
+
|
|
|
+ // 5. 保存记录
|
|
|
+ ArticleQuizRecord record = new ArticleQuizRecord();
|
|
|
+ record.setArticleId(articleId);
|
|
|
+ record.setUserId(userId);
|
|
|
+ record.setChildId(childId);
|
|
|
+ record.setQuestions(questionsJson);
|
|
|
+ record.setAnswers(null);
|
|
|
+ record.setScore(0);
|
|
|
+ record.setPointsEarned(0);
|
|
|
+ record.setCreatedAt(new Date());
|
|
|
+ record.setUpdatedAt(new Date());
|
|
|
+ articleQuizRecordMapper.insert(record);
|
|
|
+
|
|
|
+ result.put("recordId", record.getId());
|
|
|
+ result.put("questions", questionsJson);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String extractJsonFromResponse(String response) {
|
|
|
+ if (response == null || response.isEmpty()) return "[]";
|
|
|
+ int start = response.indexOf('[');
|
|
|
+ int end = response.lastIndexOf(']');
|
|
|
+ if (start >= 0 && end > start) {
|
|
|
+ return response.substring(start, end + 1);
|
|
|
+ }
|
|
|
+ return "[]";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ public Map<String, Object> submitAnswers(Long recordId, String answersJson, Long userId, Long childId) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+
|
|
|
+ // 1. 获取记录
|
|
|
+ ArticleQuizRecord record = articleQuizRecordMapper.selectById(recordId);
|
|
|
+ if (record == null) {
|
|
|
+ throw new RuntimeException("答题记录不存在");
|
|
|
+ }
|
|
|
+ if (record.getAnswers() != null) {
|
|
|
+ throw new RuntimeException("已作答,不可重复提交");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 解析题目和答案,计算分数
|
|
|
+ int score = 0;
|
|
|
+ int totalQuestions = 0;
|
|
|
+ try {
|
|
|
+ List<Map<String, Object>> questions = objectMapper.readValue(record.getQuestions(), objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class));
|
|
|
+ List<Map<String, Object>> answers = objectMapper.readValue(answersJson, objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class));
|
|
|
+ totalQuestions = questions.size();
|
|
|
+
|
|
|
+ Map<String, Object> answerMap = new HashMap<>();
|
|
|
+ for (Map<String, Object> a : answers) {
|
|
|
+ answerMap.put(String.valueOf(a.get("questionIndex")), a.get("selected"));
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < questions.size(); i++) {
|
|
|
+ Map<String, Object> q = questions.get(i);
|
|
|
+ String correct = (String) q.get("answer");
|
|
|
+ String selected = (String) answerMap.get(String.valueOf(i));
|
|
|
+ if (correct != null && correct.equalsIgnoreCase(selected != null ? selected.toString() : "")) {
|
|
|
+ score++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析答题结果失败:{}", e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 计算积分:完全答对 +5 分
|
|
|
+ int pointsEarned = 0;
|
|
|
+ if (score == totalQuestions && totalQuestions > 0) {
|
|
|
+ pointsEarned = 5;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 更新记录
|
|
|
+ record.setAnswers(answersJson);
|
|
|
+ record.setScore(score);
|
|
|
+ record.setPointsEarned(pointsEarned);
|
|
|
+ record.setUpdatedAt(new Date());
|
|
|
+ articleQuizRecordMapper.updateById(record);
|
|
|
+
|
|
|
+ // 5. 发放积分
|
|
|
+ if (pointsEarned > 0 && childId != null) {
|
|
|
+ try {
|
|
|
+ pointsService.awardSystemPoints(childId, pointsEarned, "AI 阅读答题奖励");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("积分发放失败:{}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ result.put("score", score);
|
|
|
+ result.put("totalQuestions", totalQuestions);
|
|
|
+ result.put("pointsEarned", pointsEarned);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
}
|