feat: Chatgpt
This commit is contained in:
parent
844dbba527
commit
e4c6b14576
@ -1,6 +1,5 @@
|
|||||||
package com.novelreader.config;
|
package com.novelreader.config;
|
||||||
|
|
||||||
import com.novelreader.model.ChapterDO;
|
|
||||||
import com.novelreader.model.NovelDO;
|
import com.novelreader.model.NovelDO;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -26,14 +25,17 @@ public class MongoIndexConfig {
|
|||||||
@EventListener(ApplicationReadyEvent.class)
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
public void ensureIndexes() {
|
public void ensureIndexes() {
|
||||||
log.info("开始检查和创建MongoDB索引...");
|
log.info("开始检查和创建MongoDB索引...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Chapter集合索引 - 这是最关键的,因为有2800万条数据
|
// Chapter集合索引 - 这是最关键的,因为有2800万条数据
|
||||||
createChapterIndexes();
|
createChapterIndexes();
|
||||||
|
|
||||||
// Novel集合索引
|
// Novel集合索引
|
||||||
createNovelIndexes();
|
createNovelIndexes();
|
||||||
|
|
||||||
|
// History集合索引
|
||||||
|
createHistoryIndexes();
|
||||||
|
|
||||||
log.info("MongoDB索引检查完成");
|
log.info("MongoDB索引检查完成");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("创建索引失败", e);
|
log.error("创建索引失败", e);
|
||||||
@ -42,39 +44,60 @@ public class MongoIndexConfig {
|
|||||||
|
|
||||||
private void createChapterIndexes() {
|
private void createChapterIndexes() {
|
||||||
IndexOperations indexOps = mongoTemplate.indexOps("chapterDO");
|
IndexOperations indexOps = mongoTemplate.indexOps("chapterDO");
|
||||||
|
|
||||||
// 复合索引: novelId + index (最重要的索引,用于章节查询和排序)
|
// 复合索引: novelId index (最重要的索引,用于章节查询和排序)
|
||||||
indexOps.ensureIndex(new Index()
|
indexOps.ensureIndex(new Index()
|
||||||
.on("novelId", Sort.Direction.ASC)
|
.on("novelId", Sort.Direction.ASC)
|
||||||
.on("index", Sort.Direction.ASC)
|
.on("index", Sort.Direction.ASC)
|
||||||
.named("idx_novelId_index")
|
.named("idx_novelId_index")
|
||||||
.background()); // 后台创建,不阻塞
|
.background()); // 后台创建,不阻塞
|
||||||
|
|
||||||
log.info("Chapter索引已确保: idx_novelId_index");
|
log.info("Chapter索引已确保: idx_novelId_index");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNovelIndexes() {
|
private void createNovelIndexes() {
|
||||||
IndexOperations indexOps = mongoTemplate.indexOps(NovelDO.class);
|
IndexOperations indexOps = mongoTemplate.indexOps(NovelDO.class);
|
||||||
|
|
||||||
// 名称索引 (用于搜索)
|
// 名称索引 (用于搜索)
|
||||||
indexOps.ensureIndex(new Index()
|
indexOps.ensureIndex(new Index()
|
||||||
.on("name", Sort.Direction.ASC)
|
.on("name", Sort.Direction.ASC)
|
||||||
.named("idx_name")
|
.named("idx_name")
|
||||||
.background());
|
.background());
|
||||||
|
|
||||||
// 作者索引 (用于搜索)
|
// 作者索引 (用于搜索)
|
||||||
indexOps.ensureIndex(new Index()
|
indexOps.ensureIndex(new Index()
|
||||||
.on("author", Sort.Direction.ASC)
|
.on("author", Sort.Direction.ASC)
|
||||||
.named("idx_author")
|
.named("idx_author")
|
||||||
.background());
|
.background());
|
||||||
|
|
||||||
// 标签索引 (用于标签筛选)
|
// 标签索引 (用于标签筛选)
|
||||||
indexOps.ensureIndex(new Index()
|
indexOps.ensureIndex(new Index()
|
||||||
.on("tags", Sort.Direction.ASC)
|
.on("tags", Sort.Direction.ASC)
|
||||||
.named("idx_tags")
|
.named("idx_tags")
|
||||||
.background());
|
.background());
|
||||||
|
|
||||||
log.info("Novel索引已确保: idx_name, idx_author, idx_tags");
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,11 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.domain.Pageable;
|
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 org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@ -14,36 +19,41 @@ import java.util.Optional;
|
|||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class HistoryService {
|
public class HistoryService {
|
||||||
|
|
||||||
private final HistoryRepository historyRepository;
|
private final HistoryRepository historyRepository;
|
||||||
|
private final MongoTemplate mongoTemplate;
|
||||||
private static final String DEFAULT_USER_ID = "default_user";
|
private static final String DEFAULT_USER_ID = "default_user";
|
||||||
|
|
||||||
public Page<History> getHistory(String userId, int page, int size) {
|
public Page<History> getHistory(String userId, int page, int size) {
|
||||||
String uid = userId != null ? userId : DEFAULT_USER_ID;
|
String uid = userId != null ? userId : DEFAULT_USER_ID;
|
||||||
Pageable pageable = PageRequest.of(page, size);
|
Pageable pageable = PageRequest.of(page, size);
|
||||||
return historyRepository.findByUserIdOrderByLastReadAtDesc(uid, pageable);
|
return historyRepository.findByUserIdOrderByLastReadAtDesc(uid, pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<History> getHistoryByNovel(String userId, String novelId) {
|
public Optional<History> getHistoryByNovel(String userId, String novelId) {
|
||||||
String uid = userId != null ? userId : DEFAULT_USER_ID;
|
String uid = userId != null ? userId : DEFAULT_USER_ID;
|
||||||
return historyRepository.findByUserIdAndNovelId(uid, novelId);
|
return historyRepository.findByUserIdAndNovelId(uid, novelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public History updateHistory(String userId, String novelId, String chapterId, Integer position) {
|
public History updateHistory(String userId, String novelId, String chapterId, Integer position) {
|
||||||
String uid = userId != null ? userId : DEFAULT_USER_ID;
|
String uid = userId != null ? userId : DEFAULT_USER_ID;
|
||||||
|
|
||||||
Optional<History> existing = historyRepository.findByUserIdAndNovelId(uid, novelId);
|
Query query = new Query(Criteria.where("userId").is(uid).and("novelId").is(novelId));
|
||||||
|
Update update = new Update()
|
||||||
History history = existing.orElse(new History());
|
.set("userId", uid)
|
||||||
history.setUserId(uid);
|
.set("novelId", novelId)
|
||||||
history.setNovelId(novelId);
|
.set("chapterId", chapterId)
|
||||||
history.setChapterId(chapterId);
|
.set("position", position != null ? position : 0)
|
||||||
history.setPosition(position != null ? position : 0);
|
.set("lastReadAt", Instant.now());
|
||||||
history.setLastReadAt(Instant.now());
|
|
||||||
|
return mongoTemplate.findAndModify(
|
||||||
return historyRepository.save(history);
|
query,
|
||||||
|
update,
|
||||||
|
FindAndModifyOptions.options().returnNew(true).upsert(true),
|
||||||
|
History.class
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteHistory(String userId, String novelId) {
|
public void deleteHistory(String userId, String novelId) {
|
||||||
String uid = userId != null ? userId : DEFAULT_USER_ID;
|
String uid = userId != null ? userId : DEFAULT_USER_ID;
|
||||||
historyRepository.deleteByUserIdAndNovelId(uid, novelId);
|
historyRepository.deleteByUserIdAndNovelId(uid, novelId);
|
||||||
|
|||||||
@ -13,8 +13,17 @@ const App = (function() {
|
|||||||
handleRoute();
|
handleRoute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录上一个路由,用于返回
|
||||||
|
let lastHash = '#/shelf';
|
||||||
|
let lastNonReadHash = '#/shelf';
|
||||||
|
|
||||||
function handleRoute() {
|
function handleRoute() {
|
||||||
const hash = window.location.hash || '#/shelf';
|
const hash = window.location.hash || '#/shelf';
|
||||||
|
const prevHash = lastHash;
|
||||||
|
lastHash = hash;
|
||||||
|
if (!hash.includes('#/read/')) {
|
||||||
|
lastNonReadHash = hash;
|
||||||
|
}
|
||||||
const [path, ...params] = hash.slice(2).split('/');
|
const [path, ...params] = hash.slice(2).split('/');
|
||||||
|
|
||||||
// 更新导航状态
|
// 更新导航状态
|
||||||
@ -52,6 +61,9 @@ const App = (function() {
|
|||||||
case 'read':
|
case 'read':
|
||||||
if (params[0]) {
|
if (params[0]) {
|
||||||
const chapterIndex = parseInt(params[1]) || 0;
|
const chapterIndex = parseInt(params[1]) || 0;
|
||||||
|
// 记录进入阅读页前的路由,用于返回时回到详情页或来源页
|
||||||
|
const backRoute = prevHash && !prevHash.includes('#/read/') ? prevHash : lastNonReadHash;
|
||||||
|
Store.setState('prevRouteBeforeRead', backRoute || '#/shelf');
|
||||||
showPage('reader');
|
showPage('reader');
|
||||||
Reader.init(params[0], chapterIndex);
|
Reader.init(params[0], chapterIndex);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -507,12 +507,17 @@ const Reader = (function() {
|
|||||||
// 解绑事件
|
// 解绑事件
|
||||||
unbindEvents();
|
unbindEvents();
|
||||||
|
|
||||||
// 返回详情页(showPage会处理导航栏显示状态)
|
// 使用记录的前一页面替换当前阅读页,避免回退再次进入阅读
|
||||||
if (currentNovelId) {
|
const prevRoute = Store.getState('prevRouteBeforeRead');
|
||||||
window.location.hash = '#/novel/' + currentNovelId;
|
if (prevRoute) {
|
||||||
|
window.location.replace(prevRoute);
|
||||||
|
} else if (currentNovelId) {
|
||||||
|
window.location.replace('#/novel/' + currentNovelId);
|
||||||
} else {
|
} else {
|
||||||
window.location.hash = '#/shelf';
|
window.location.replace('#/shelf');
|
||||||
}
|
}
|
||||||
|
// 清理记录
|
||||||
|
Store.setState('prevRouteBeforeRead', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToTop() {
|
function scrollToTop() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user