From e4c6b145764afdcc94d63e50d910d0a3dea9c141 Mon Sep 17 00:00:00 2001 From: irony Date: Sat, 6 Dec 2025 19:46:34 +0800 Subject: [PATCH] feat: Chatgpt --- .../novelreader/config/MongoIndexConfig.java | 45 ++++++++++++++----- .../novelreader/service/HistoryService.java | 42 ++++++++++------- src/main/resources/static/js/app.js | 12 +++++ src/main/resources/static/js/reader.js | 13 ++++-- 4 files changed, 81 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/novelreader/config/MongoIndexConfig.java b/src/main/java/com/novelreader/config/MongoIndexConfig.java index ed6d04d..1b464d2 100644 --- a/src/main/java/com/novelreader/config/MongoIndexConfig.java +++ b/src/main/java/com/novelreader/config/MongoIndexConfig.java @@ -1,6 +1,5 @@ package com.novelreader.config; -import com.novelreader.model.ChapterDO; import com.novelreader.model.NovelDO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,14 +25,17 @@ public class MongoIndexConfig { @EventListener(ApplicationReadyEvent.class) public void ensureIndexes() { log.info("开始检查和创建MongoDB索引..."); - + try { // Chapter集合索引 - 这是最关键的,因为有2800万条数据 createChapterIndexes(); - + // Novel集合索引 createNovelIndexes(); - + + // History集合索引 + createHistoryIndexes(); + log.info("MongoDB索引检查完成"); } catch (Exception e) { log.error("创建索引失败", e); @@ -42,39 +44,60 @@ public class MongoIndexConfig { private void createChapterIndexes() { IndexOperations indexOps = mongoTemplate.indexOps("chapterDO"); - - // 复合索引: novelId + index (最重要的索引,用于章节查询和排序) + + // 复合索引: novelId index (最重要的索引,用于章节查询和排序) indexOps.ensureIndex(new Index() .on("novelId", Sort.Direction.ASC) .on("index", Sort.Direction.ASC) .named("idx_novelId_index") .background()); // 后台创建,不阻塞 - + log.info("Chapter索引已确保: idx_novelId_index"); } private void createNovelIndexes() { IndexOperations indexOps = mongoTemplate.indexOps(NovelDO.class); - + // 名称索引 (用于搜索) indexOps.ensureIndex(new Index() .on("name", Sort.Direction.ASC) .named("idx_name") .background()); - + // 作者索引 (用于搜索) indexOps.ensureIndex(new Index() .on("author", Sort.Direction.ASC) .named("idx_author") .background()); - + // 标签索引 (用于标签筛选) indexOps.ensureIndex(new Index() .on("tags", Sort.Direction.ASC) .named("idx_tags") .background()); - + log.info("Novel索引已确保: idx_name, idx_author, idx_tags"); } + + private void createHistoryIndexes() { + IndexOperations indexOps = mongoTemplate.indexOps("history"); + + // 唯一索引: userId novelId,防止重复历史记录 + indexOps.ensureIndex(new Index() + .on("userId", Sort.Direction.ASC) + .on("novelId", Sort.Direction.ASC) + .unique() + .named("ux_user_novel") + .background()); + + // 辅助索引:按时间排序 + indexOps.ensureIndex(new Index() + .on("userId", Sort.Direction.ASC) + .on("lastReadAt", Sort.Direction.DESC) + .named("idx_user_lastReadAt") + .background()); + + log.info("History索引已确保: ux_user_novel, idx_user_lastReadAt"); + } } diff --git a/src/main/java/com/novelreader/service/HistoryService.java b/src/main/java/com/novelreader/service/HistoryService.java index 6aabc24..f6fc4a5 100644 --- a/src/main/java/com/novelreader/service/HistoryService.java +++ b/src/main/java/com/novelreader/service/HistoryService.java @@ -6,6 +6,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.core.FindAndModifyOptions; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Service; import java.time.Instant; @@ -14,36 +19,41 @@ import java.util.Optional; @Service @RequiredArgsConstructor public class HistoryService { - + private final HistoryRepository historyRepository; + private final MongoTemplate mongoTemplate; private static final String DEFAULT_USER_ID = "default_user"; - + public Page getHistory(String userId, int page, int size) { String uid = userId != null ? userId : DEFAULT_USER_ID; Pageable pageable = PageRequest.of(page, size); return historyRepository.findByUserIdOrderByLastReadAtDesc(uid, pageable); } - + public Optional getHistoryByNovel(String userId, String novelId) { String uid = userId != null ? userId : DEFAULT_USER_ID; return historyRepository.findByUserIdAndNovelId(uid, novelId); } - + public History updateHistory(String userId, String novelId, String chapterId, Integer position) { String uid = userId != null ? userId : DEFAULT_USER_ID; - - Optional existing = historyRepository.findByUserIdAndNovelId(uid, novelId); - - History history = existing.orElse(new History()); - history.setUserId(uid); - history.setNovelId(novelId); - history.setChapterId(chapterId); - history.setPosition(position != null ? position : 0); - history.setLastReadAt(Instant.now()); - - return historyRepository.save(history); + + Query query = new Query(Criteria.where("userId").is(uid).and("novelId").is(novelId)); + Update update = new Update() + .set("userId", uid) + .set("novelId", novelId) + .set("chapterId", chapterId) + .set("position", position != null ? position : 0) + .set("lastReadAt", Instant.now()); + + return mongoTemplate.findAndModify( + query, + update, + FindAndModifyOptions.options().returnNew(true).upsert(true), + History.class + ); } - + public void deleteHistory(String userId, String novelId) { String uid = userId != null ? userId : DEFAULT_USER_ID; historyRepository.deleteByUserIdAndNovelId(uid, novelId); diff --git a/src/main/resources/static/js/app.js b/src/main/resources/static/js/app.js index 096919e..3c26c81 100644 --- a/src/main/resources/static/js/app.js +++ b/src/main/resources/static/js/app.js @@ -13,8 +13,17 @@ const App = (function() { handleRoute(); } + // 记录上一个路由,用于返回 + let lastHash = '#/shelf'; + let lastNonReadHash = '#/shelf'; + function handleRoute() { const hash = window.location.hash || '#/shelf'; + const prevHash = lastHash; + lastHash = hash; + if (!hash.includes('#/read/')) { + lastNonReadHash = hash; + } const [path, ...params] = hash.slice(2).split('/'); // 更新导航状态 @@ -52,6 +61,9 @@ const App = (function() { case 'read': if (params[0]) { const chapterIndex = parseInt(params[1]) || 0; + // 记录进入阅读页前的路由,用于返回时回到详情页或来源页 + const backRoute = prevHash && !prevHash.includes('#/read/') ? prevHash : lastNonReadHash; + Store.setState('prevRouteBeforeRead', backRoute || '#/shelf'); showPage('reader'); Reader.init(params[0], chapterIndex); } diff --git a/src/main/resources/static/js/reader.js b/src/main/resources/static/js/reader.js index d06c6cb..ba19b05 100644 --- a/src/main/resources/static/js/reader.js +++ b/src/main/resources/static/js/reader.js @@ -507,12 +507,17 @@ const Reader = (function() { // 解绑事件 unbindEvents(); - // 返回详情页(showPage会处理导航栏显示状态) - if (currentNovelId) { - window.location.hash = '#/novel/' + currentNovelId; + // 使用记录的前一页面替换当前阅读页,避免回退再次进入阅读 + const prevRoute = Store.getState('prevRouteBeforeRead'); + if (prevRoute) { + window.location.replace(prevRoute); + } else if (currentNovelId) { + window.location.replace('#/novel/' + currentNovelId); } else { - window.location.hash = '#/shelf'; + window.location.replace('#/shelf'); } + // 清理记录 + Store.setState('prevRouteBeforeRead', null); } function scrollToTop() {