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;
|
||||||
@ -34,6 +33,9 @@ public class MongoIndexConfig {
|
|||||||
// Novel集合索引
|
// Novel集合索引
|
||||||
createNovelIndexes();
|
createNovelIndexes();
|
||||||
|
|
||||||
|
// History集合索引
|
||||||
|
createHistoryIndexes();
|
||||||
|
|
||||||
log.info("MongoDB索引检查完成");
|
log.info("MongoDB索引检查完成");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("创建索引失败", e);
|
log.error("创建索引失败", e);
|
||||||
@ -43,7 +45,7 @@ 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)
|
||||||
@ -76,5 +78,26 @@ public class MongoIndexConfig {
|
|||||||
|
|
||||||
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;
|
||||||
@ -16,6 +21,7 @@ import java.util.Optional;
|
|||||||
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) {
|
||||||
@ -32,16 +38,20 @@ public class HistoryService {
|
|||||||
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()
|
||||||
|
.set("userId", uid)
|
||||||
|
.set("novelId", novelId)
|
||||||
|
.set("chapterId", chapterId)
|
||||||
|
.set("position", position != null ? position : 0)
|
||||||
|
.set("lastReadAt", Instant.now());
|
||||||
|
|
||||||
History history = existing.orElse(new History());
|
return mongoTemplate.findAndModify(
|
||||||
history.setUserId(uid);
|
query,
|
||||||
history.setNovelId(novelId);
|
update,
|
||||||
history.setChapterId(chapterId);
|
FindAndModifyOptions.options().returnNew(true).upsert(true),
|
||||||
history.setPosition(position != null ? position : 0);
|
History.class
|
||||||
history.setLastReadAt(Instant.now());
|
);
|
||||||
|
|
||||||
return historyRepository.save(history);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteHistory(String userId, String novelId) {
|
public void deleteHistory(String userId, String 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