From 85b2729a04c05b99547ee19a15aca6795a5c71e0 Mon Sep 17 00:00:00 2001 From: ovo Date: Fri, 13 Dec 2024 17:56:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E4=B9=A6=E5=88=9D=E6=AD=A5):=20?= =?UTF-8?q?=E5=9B=BE=E4=B9=A6=E5=88=9D=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 图书初步 --- .../guwan/backend/config/EasyEsConfig.java | 1 - .../guwan/backend/config/WebSocketConfig.java | 18 +++ .../backend/constant/SecurityConstants.java | 6 + .../backend/controller/BookController.java | 106 +++++++++++++++ .../backend/controller/DemoController.java | 2 +- .../backend/controller/LiveController.java | 82 ----------- .../controller/PollingChatController.java | 60 ++++++++ .../controller/ReadingNoteController.java | 127 +++++++++++++++++ .../controller/ReadingProgressController.java | 104 ++++++++++++++ .../com/guwan/backend/dto/ChatMessage.java | 1 + .../guwan/backend/dto/ReadingStatistics.java | 13 ++ .../java/com/guwan/backend/entity/Book.java | 30 ++++ .../com/guwan/backend/entity/ReadingNote.java | 27 ++++ .../guwan/backend/entity/ReadingProgress.java | 26 ++++ .../com/guwan/backend/mapper/BookMapper.java | 9 ++ .../backend/mapper/ReadingNoteMapper.java | 9 ++ .../backend/mapper/ReadingProgressMapper.java | 9 ++ .../guwan/backend/service/BookService.java | 27 ++++ .../backend/service/ReadingNoteService.java | 30 ++++ .../service/ReadingProgressService.java | 24 ++++ .../backend/service/impl/BookServiceImpl.java | 73 ++++++++++ .../service/impl/ReadingNoteServiceImpl.java | 87 ++++++++++++ .../impl/ReadingProgressServiceImpl.java | 85 ++++++++++++ .../websocket/ChatWebSocketHandler.java | 47 +++++++ .../backend/websocket/LiveChatHandler.java | 1 + .../websocket/WebSocketAuthInterceptor.java | 1 + .../websocket/message/ChatMessage.java | 1 + .../db/migration/V10__create_book_tables.sql | 71 ++++++++++ src/main/resources/static/chat.html | 82 ++++++----- src/main/resources/static/live-chat.html | 1 + src/main/resources/static/polling-chat.html | 128 ++++++++++++++++++ .../backend/service/BookServiceTest.java | 68 ++++++++++ 32 files changed, 1230 insertions(+), 126 deletions(-) create mode 100644 src/main/java/com/guwan/backend/config/WebSocketConfig.java create mode 100644 src/main/java/com/guwan/backend/controller/BookController.java delete mode 100644 src/main/java/com/guwan/backend/controller/LiveController.java create mode 100644 src/main/java/com/guwan/backend/controller/PollingChatController.java create mode 100644 src/main/java/com/guwan/backend/controller/ReadingNoteController.java create mode 100644 src/main/java/com/guwan/backend/controller/ReadingProgressController.java create mode 100644 src/main/java/com/guwan/backend/dto/ChatMessage.java create mode 100644 src/main/java/com/guwan/backend/dto/ReadingStatistics.java create mode 100644 src/main/java/com/guwan/backend/entity/Book.java create mode 100644 src/main/java/com/guwan/backend/entity/ReadingNote.java create mode 100644 src/main/java/com/guwan/backend/entity/ReadingProgress.java create mode 100644 src/main/java/com/guwan/backend/mapper/BookMapper.java create mode 100644 src/main/java/com/guwan/backend/mapper/ReadingNoteMapper.java create mode 100644 src/main/java/com/guwan/backend/mapper/ReadingProgressMapper.java create mode 100644 src/main/java/com/guwan/backend/service/BookService.java create mode 100644 src/main/java/com/guwan/backend/service/ReadingNoteService.java create mode 100644 src/main/java/com/guwan/backend/service/ReadingProgressService.java create mode 100644 src/main/java/com/guwan/backend/service/impl/BookServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/service/impl/ReadingNoteServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/service/impl/ReadingProgressServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/websocket/ChatWebSocketHandler.java create mode 100644 src/main/java/com/guwan/backend/websocket/LiveChatHandler.java create mode 100644 src/main/java/com/guwan/backend/websocket/WebSocketAuthInterceptor.java create mode 100644 src/main/java/com/guwan/backend/websocket/message/ChatMessage.java create mode 100644 src/main/resources/db/migration/V10__create_book_tables.sql create mode 100644 src/main/resources/static/live-chat.html create mode 100644 src/main/resources/static/polling-chat.html create mode 100644 src/test/java/com/guwan/backend/service/BookServiceTest.java diff --git a/src/main/java/com/guwan/backend/config/EasyEsConfig.java b/src/main/java/com/guwan/backend/config/EasyEsConfig.java index 37ab97d..7169d67 100644 --- a/src/main/java/com/guwan/backend/config/EasyEsConfig.java +++ b/src/main/java/com/guwan/backend/config/EasyEsConfig.java @@ -11,7 +11,6 @@ import org.springframework.context.annotation.Configuration; @EsMapperScan("com.guwan.backend.es.mapper") @EnableConfigurationProperties(EasyEsConfigProperties.class) public class EasyEsConfig { - @Bean public IndexStrategyFactory indexStrategyFactory() { return new IndexStrategyFactory(); diff --git a/src/main/java/com/guwan/backend/config/WebSocketConfig.java b/src/main/java/com/guwan/backend/config/WebSocketConfig.java new file mode 100644 index 0000000..010681a --- /dev/null +++ b/src/main/java/com/guwan/backend/config/WebSocketConfig.java @@ -0,0 +1,18 @@ +package com.guwan.backend.config; + +import com.guwan.backend.websocket.ChatWebSocketHandler; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(new ChatWebSocketHandler(), "/ws/chat") + .setAllowedOrigins("*"); // 允许所有来源 + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/constant/SecurityConstants.java b/src/main/java/com/guwan/backend/constant/SecurityConstants.java index 19260db..eac93a4 100644 --- a/src/main/java/com/guwan/backend/constant/SecurityConstants.java +++ b/src/main/java/com/guwan/backend/constant/SecurityConstants.java @@ -20,6 +20,12 @@ public class SecurityConstants { "/api/user/getEmailCode", // 获取邮箱验证码 "/api/user/getPhoneCode", // 获取手机验证码 "/chat.html", + "/polling-chat.html", + + "/ws/chat/**", + + "/api/polling-chat/**", + "/v3/api-docs/**", // Swagger API文档 "/swagger-ui/**", // Swagger UI "/swagger-ui.html", // Swagger UI HTML diff --git a/src/main/java/com/guwan/backend/controller/BookController.java b/src/main/java/com/guwan/backend/controller/BookController.java new file mode 100644 index 0000000..05b66c2 --- /dev/null +++ b/src/main/java/com/guwan/backend/controller/BookController.java @@ -0,0 +1,106 @@ +package com.guwan.backend.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.guwan.backend.common.Result; +import com.guwan.backend.entity.Book; +import com.guwan.backend.service.BookService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@Tag(name = "图书管理", description = "图书相关接口") +@RestController +@RequestMapping("/api/books") +@RequiredArgsConstructor +public class BookController { + + private final BookService bookService; + + @Operation(summary = "添加图书") + @PostMapping + public Result addBook(@RequestBody Book book) { + try { + return Result.success(bookService.addBook(book)); + } catch (Exception e) { + log.error("添加图书失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "更新图书信息") + @PutMapping("/{id}") + public Result updateBook(@PathVariable Long id, @RequestBody Book book) { + try { + book.setId(id); + return Result.success(bookService.updateBook(book)); + } catch (Exception e) { + log.error("更新图书失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "删除图书") + @DeleteMapping("/{id}") + public Result deleteBook(@PathVariable Long id) { + try { + bookService.deleteBook(id); + return Result.success(); + } catch (Exception e) { + log.error("删除图书失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取图书详情") + @GetMapping("/{id}") + public Result getBook(@PathVariable Long id) { + try { + return Result.success(bookService.getBookById(id)); + } catch (Exception e) { + log.error("获取图书详情失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "根据ISBN获取图书") + @GetMapping("/isbn/{isbn}") + public Result getBookByIsbn(@PathVariable String isbn) { + try { + return Result.success(bookService.getBookByIsbn(isbn)); + } catch (Exception e) { + log.error("根据ISBN获取图书失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "分页查询图书列表") + @GetMapping + public Result> getBookList( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String keyword) { + try { + return Result.success(bookService.getBookList(pageNum, pageSize, keyword)); + } catch (Exception e) { + log.error("查询图书列表失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "根据分类获取图书") + @GetMapping("/category/{category}") + public Result> getBooksByCategory( + @PathVariable String category, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + try { + return Result.success(bookService.getBooksByCategory(category, pageNum, pageSize)); + } catch (Exception e) { + log.error("根据分类获取图书失败", e); + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/guwan/backend/controller/DemoController.java b/src/main/java/com/guwan/backend/controller/DemoController.java index 87cbcea..653b2f5 100644 --- a/src/main/java/com/guwan/backend/controller/DemoController.java +++ b/src/main/java/com/guwan/backend/controller/DemoController.java @@ -60,7 +60,7 @@ public class DemoController { (bucketName, minioUtil.uploadFile(bucketName, file)))); } - @GetMapping("demo111") + @GetMapping("/demo111") public int saveTenPerson() { try { diff --git a/src/main/java/com/guwan/backend/controller/LiveController.java b/src/main/java/com/guwan/backend/controller/LiveController.java deleted file mode 100644 index ad92228..0000000 --- a/src/main/java/com/guwan/backend/controller/LiveController.java +++ /dev/null @@ -1,82 +0,0 @@ -//package com.guwan.backend.controller; -// -//import com.guwan.backend.entity.LiveRoom; -//import com.guwan.backend.entity.LiveRoomDTO; -//import com.guwan.backend.service.LiveService; -//import com.guwan.backend.util.Result; -//import com.guwan.backend.util.SecurityUtil; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.web.bind.annotation.*; -//import io.swagger.v3.oas.annotations.Operation; -//import io.swagger.v3.oas.annotations.media.Schema; -//import io.swagger.v3.oas.annotations.security.SecurityRequirement; -//import io.swagger.v3.oas.annotations.tags.Tag; -// -//@Slf4j -//@Tag(name = "直播管理", description = "直播相关接口") -//@RestController -//@RequestMapping("/api/live") -//@RequiredArgsConstructor -//public class LiveController { -// -// private final LiveService liveService; -// private final SecurityUtil securityUtil; -// -// @Operation(summary = "创建直播间") -// @SecurityRequirement(name = "bearer-jwt") -// @PostMapping("/room") -// public Result createLiveRoom(@RequestBody LiveRoomDTO dto) { -// try { -// return Result.success(liveService.createLiveRoom(dto)); -// } catch (Exception e) { -// log.error("创建直播间失败", e); -// return Result.error(e.getMessage()); -// } -// } -// -// @Operation(summary = "开始直播") -// @SecurityRequirement(name = "bearer-jwt") -// @PostMapping("/room/{id}/start") -// public Result startLive(@PathVariable Long id) { -// try { -// // 检查权限 -// checkPermission(id); -// liveService.startLive(id); -// return Result.success(); -// } catch (Exception e) { -// log.error("开始直播失败", e); -// return Result.error(e.getMessage()); -// } -// } -// -// @Operation(summary = "结束直播") -// @SecurityRequirement(name = "bearer-jwt") -// @PostMapping("/room/{id}/end") -// public Result endLive(@PathVariable Long id) { -// try { -// // 检查权限 -// checkPermission(id); -// liveService.endLive(id); -// return Result.success(); -// } catch (Exception e) { -// log.error("结束直播失败", e); -// return Result.error(e.getMessage()); -// } -// } -// -// /** -// * 检查权限 -// */ -// private void checkPermission(Long roomId) { -// LiveRoom room = liveService.getLiveRoom(roomId); -// if (room == null) { -// throw new IllegalArgumentException("直播间不存在"); -// } -// -// Long currentUserId = securityUtil.getCurrentUserId(); -// if (!room.getUserId().equals(currentUserId)) { -// throw new IllegalStateException("无权操作此直播间"); -// } -// } -//} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/controller/PollingChatController.java b/src/main/java/com/guwan/backend/controller/PollingChatController.java new file mode 100644 index 0000000..09df073 --- /dev/null +++ b/src/main/java/com/guwan/backend/controller/PollingChatController.java @@ -0,0 +1,60 @@ +package com.guwan.backend.controller; + +import com.guwan.backend.common.Result; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +@Slf4j +@RestController +@RequestMapping("/api/polling-chat") +public class PollingChatController { + + // 使用线程安全的列表存储消息 + private static final List messages = new CopyOnWriteArrayList<>(); + private static final int MAX_MESSAGES = 100; // 最多保存100条消息 + + // 发送消息 + @PostMapping("/send") + public Result send(@RequestBody Message message) { + message.setTime(LocalDateTime.now()); + messages.add(message); + + // 只保留最近的消息 + if (messages.size() > MAX_MESSAGES) { + messages.subList(0, messages.size() - MAX_MESSAGES).clear(); + } + + log.info("新消息: {}", message); + return Result.success(); + } + + // 获取消息(轮询) + @GetMapping("/messages") + public Result> getMessages( + @RequestParam(required = false) Integer lastIndex) { + if (lastIndex == null) { + // 首次请求,返回所有消息 + return Result.success(new ArrayList<>(messages)); + } else if (lastIndex < messages.size()) { + // 返回新消息 + return Result.success(messages.subList(lastIndex, messages.size())); + } else { + // 没有新消息 + return Result.success(Collections.emptyList()); + } + } + + @Data + public static class Message { + private String content; // 消息内容 + private String sender; // 发送者 + private LocalDateTime time; // 发送时间 + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/controller/ReadingNoteController.java b/src/main/java/com/guwan/backend/controller/ReadingNoteController.java new file mode 100644 index 0000000..554cb6b --- /dev/null +++ b/src/main/java/com/guwan/backend/controller/ReadingNoteController.java @@ -0,0 +1,127 @@ +package com.guwan.backend.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.guwan.backend.common.Result; +import com.guwan.backend.entity.ReadingNote; +import com.guwan.backend.service.ReadingNoteService; +import com.guwan.backend.util.SecurityUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@Tag(name = "阅读笔记", description = "阅读笔记相关接口") +@RestController +@RequestMapping("/api/reading-notes") +@RequiredArgsConstructor +public class ReadingNoteController { + + private final ReadingNoteService noteService; + private final SecurityUtil securityUtil; + + @Operation(summary = "添加笔记") + @PostMapping + public Result addNote(@RequestBody ReadingNote note) { + try { + note.setUserId(securityUtil.getCurrentUserId()); + return Result.success(noteService.addNote(note)); + } catch (Exception e) { + log.error("添加笔记失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "更新笔记") + @PutMapping("/{id}") + public Result updateNote(@PathVariable Long id, @RequestBody ReadingNote note) { + try { + note.setId(id); + note.setUserId(securityUtil.getCurrentUserId()); + return Result.success(noteService.updateNote(note)); + } catch (Exception e) { + log.error("更新笔记失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "删除笔记") + @DeleteMapping("/{id}") + public Result deleteNote(@PathVariable Long id) { + try { + noteService.deleteNote(id); + return Result.success(); + } catch (Exception e) { + log.error("删除笔记失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取笔记详情") + @GetMapping("/{id}") + public Result getNote(@PathVariable Long id) { + try { + return Result.success(noteService.getNoteById(id)); + } catch (Exception e) { + log.error("获取笔记详情失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取用户的所有笔记") + @GetMapping + public Result> getUserNotes( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + try { + Long userId = securityUtil.getCurrentUserId(); + return Result.success(noteService.getUserNotes(userId, pageNum, pageSize)); + } catch (Exception e) { + log.error("获取用户笔记失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取书籍的所有公开笔记") + @GetMapping("/book/{bookId}") + public Result> getBookNotes( + @PathVariable Long bookId, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + try { + return Result.success(noteService.getBookNotes(bookId, pageNum, pageSize)); + } catch (Exception e) { + log.error("获取书籍笔记失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取用户在特定书籍上的笔记") + @GetMapping("/book/{bookId}/my") + public Result> getUserBookNotes( + @PathVariable Long bookId, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + try { + Long userId = securityUtil.getCurrentUserId(); + return Result.success(noteService.getUserBookNotes(userId, bookId, pageNum, pageSize)); + } catch (Exception e) { + log.error("获取用户书籍笔记失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取所有公开笔记") + @GetMapping("/public") + public Result> getPublicNotes( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + try { + return Result.success(noteService.getPublicNotes(pageNum, pageSize)); + } catch (Exception e) { + log.error("获取公开笔记失败", e); + return Result.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/controller/ReadingProgressController.java b/src/main/java/com/guwan/backend/controller/ReadingProgressController.java new file mode 100644 index 0000000..1ffc8b2 --- /dev/null +++ b/src/main/java/com/guwan/backend/controller/ReadingProgressController.java @@ -0,0 +1,104 @@ +package com.guwan.backend.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.guwan.backend.common.Result; +import com.guwan.backend.dto.ReadingStatistics; +import com.guwan.backend.entity.ReadingProgress; +import com.guwan.backend.service.ReadingProgressService; +import com.guwan.backend.util.SecurityUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@Tag(name = "阅读进度", description = "阅读进度相关接口") +@RestController +@RequestMapping("/api/reading-progress") +@RequiredArgsConstructor +public class ReadingProgressController { + + private final ReadingProgressService progressService; + private final SecurityUtil securityUtil; + + @Operation(summary = "更新阅读进度") + @PostMapping + public Result updateProgress(@RequestBody ReadingProgress progress) { + try { + progress.setUserId(securityUtil.getCurrentUserId()); + return Result.success(progressService.updateProgress(progress)); + } catch (Exception e) { + log.error("更新阅读进度失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取书籍阅读进度") + @GetMapping("/book/{bookId}") + public Result getProgress(@PathVariable Long bookId) { + try { + Long userId = securityUtil.getCurrentUserId(); + return Result.success(progressService.getProgress(userId, bookId)); + } catch (Exception e) { + log.error("获取阅读进度失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取用户的所有阅读进度") + @GetMapping + public Result> getUserProgress( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + try { + Long userId = securityUtil.getCurrentUserId(); + return Result.success(progressService.getUserProgress(userId, pageNum, pageSize)); + } catch (Exception e) { + log.error("获取用户阅读进度失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取特定状态的书籍") + @GetMapping("/status/{status}") + public Result> getProgressByStatus( + @PathVariable String status, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + try { + Long userId = securityUtil.getCurrentUserId(); + return Result.success(progressService.getProgressByStatus(userId, status, pageNum, pageSize)); + } catch (Exception e) { + log.error("获取阅读状态失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "更新阅读时长") + @PostMapping("/{bookId}/reading-time") + public Result updateReadingTime( + @PathVariable Long bookId, + @RequestParam Integer minutes) { + try { + Long userId = securityUtil.getCurrentUserId(); + progressService.updateReadingTime(userId, bookId, minutes); + return Result.success(); + } catch (Exception e) { + log.error("更新阅读时长失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取阅读统计") + @GetMapping("/statistics") + public Result getReadingStatistics() { + try { + Long userId = securityUtil.getCurrentUserId(); + return Result.success(progressService.getReadingStatistics(userId)); + } catch (Exception e) { + log.error("获取阅读统计失败", e); + return Result.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/dto/ChatMessage.java b/src/main/java/com/guwan/backend/dto/ChatMessage.java new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/main/java/com/guwan/backend/dto/ChatMessage.java @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/dto/ReadingStatistics.java b/src/main/java/com/guwan/backend/dto/ReadingStatistics.java new file mode 100644 index 0000000..f055226 --- /dev/null +++ b/src/main/java/com/guwan/backend/dto/ReadingStatistics.java @@ -0,0 +1,13 @@ +package com.guwan.backend.dto; + +import lombok.Data; + +@Data +public class ReadingStatistics { + private Integer totalBooks; // 总阅读书籍数 + private Integer finishedBooks; // 已完成书籍数 + private Integer totalReadingTime; // 总阅读时长(分钟) + private Integer dailyAverage; // 日均阅读时长(分钟) + private Integer currentStreak; // 当前连续阅读天数 + private Integer longestStreak; // 最长连续阅读天数 +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/entity/Book.java b/src/main/java/com/guwan/backend/entity/Book.java new file mode 100644 index 0000000..247034f --- /dev/null +++ b/src/main/java/com/guwan/backend/entity/Book.java @@ -0,0 +1,30 @@ +package com.guwan.backend.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("book") +public class Book { + @TableId(type = IdType.AUTO) + private Long id; + + private String isbn; // ISBN编号 + private String title; // 书名 + private String author; // 作者 + private String publisher; // 出版社 + private String description; // 描述 + private String coverUrl; // 封面图片URL + private String category; // 分类 + private String tags; // 标签(逗号分隔) + private Integer totalPages; // 总页数 + private String language; // 语言 + private LocalDateTime publishDate; // 出版日期 + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedTime; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/entity/ReadingNote.java b/src/main/java/com/guwan/backend/entity/ReadingNote.java new file mode 100644 index 0000000..05b78ea --- /dev/null +++ b/src/main/java/com/guwan/backend/entity/ReadingNote.java @@ -0,0 +1,27 @@ +package com.guwan.backend.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("reading_note") +public class ReadingNote { + @TableId(type = IdType.AUTO) + private Long id; + + private Long userId; // 用户ID + private Long bookId; // 书籍ID + private String type; // 笔记类型:NOTE,HIGHLIGHT,THOUGHT + private String content; // 笔记内容 + private String chapter; // 章节 + private Integer pageNumber; // 页码 + private String audioUrl; // 语音笔记URL + private Boolean isPublic; // 是否公开 + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedTime; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/entity/ReadingProgress.java b/src/main/java/com/guwan/backend/entity/ReadingProgress.java new file mode 100644 index 0000000..19889d1 --- /dev/null +++ b/src/main/java/com/guwan/backend/entity/ReadingProgress.java @@ -0,0 +1,26 @@ +package com.guwan.backend.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("reading_progress") +public class ReadingProgress { + @TableId(type = IdType.AUTO) + private Long id; + + private Long userId; // 用户ID + private Long bookId; // 书籍ID + private String status; // 状态:WANT_TO_READ,READING,FINISHED + private Integer currentPage; // 当前页码 + private Double percentage; // 阅读进度(0-100) + private Integer readingTime; // 阅读时长(分钟) + private LocalDateTime lastReadTime; // 最后阅读时间 + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedTime; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/mapper/BookMapper.java b/src/main/java/com/guwan/backend/mapper/BookMapper.java new file mode 100644 index 0000000..9f0bc90 --- /dev/null +++ b/src/main/java/com/guwan/backend/mapper/BookMapper.java @@ -0,0 +1,9 @@ +package com.guwan.backend.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.guwan.backend.entity.Book; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface BookMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/mapper/ReadingNoteMapper.java b/src/main/java/com/guwan/backend/mapper/ReadingNoteMapper.java new file mode 100644 index 0000000..4cec6df --- /dev/null +++ b/src/main/java/com/guwan/backend/mapper/ReadingNoteMapper.java @@ -0,0 +1,9 @@ +package com.guwan.backend.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.guwan.backend.entity.ReadingNote; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ReadingNoteMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/mapper/ReadingProgressMapper.java b/src/main/java/com/guwan/backend/mapper/ReadingProgressMapper.java new file mode 100644 index 0000000..c94b138 --- /dev/null +++ b/src/main/java/com/guwan/backend/mapper/ReadingProgressMapper.java @@ -0,0 +1,9 @@ +package com.guwan.backend.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.guwan.backend.entity.ReadingProgress; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ReadingProgressMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/BookService.java b/src/main/java/com/guwan/backend/service/BookService.java new file mode 100644 index 0000000..d31e8aa --- /dev/null +++ b/src/main/java/com/guwan/backend/service/BookService.java @@ -0,0 +1,27 @@ +package com.guwan.backend.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.guwan.backend.entity.Book; + +public interface BookService { + // 添加书籍 + Book addBook(Book book); + + // 更新书籍信息 + Book updateBook(Book book); + + // 删除书籍 + void deleteBook(Long id); + + // 获取书籍详情 + Book getBookById(Long id); + + // 根据ISBN获取书籍 + Book getBookByIsbn(String isbn); + + // 分页查询书籍列表 + IPage getBookList(Integer pageNum, Integer pageSize, String keyword); + + // 根据分类获取书籍 + IPage getBooksByCategory(String category, Integer pageNum, Integer pageSize); +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/ReadingNoteService.java b/src/main/java/com/guwan/backend/service/ReadingNoteService.java new file mode 100644 index 0000000..feb2c2e --- /dev/null +++ b/src/main/java/com/guwan/backend/service/ReadingNoteService.java @@ -0,0 +1,30 @@ +package com.guwan.backend.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.guwan.backend.entity.ReadingNote; + +public interface ReadingNoteService { + // 添加笔记 + ReadingNote addNote(ReadingNote note); + + // 更新笔记 + ReadingNote updateNote(ReadingNote note); + + // 删除笔记 + void deleteNote(Long id); + + // 获取笔记详情 + ReadingNote getNoteById(Long id); + + // 获取用户的所有笔记 + IPage getUserNotes(Long userId, Integer pageNum, Integer pageSize); + + // 获取书籍的所有笔记 + IPage getBookNotes(Long bookId, Integer pageNum, Integer pageSize); + + // 获取用户在特定书籍上的笔记 + IPage getUserBookNotes(Long userId, Long bookId, Integer pageNum, Integer pageSize); + + // 获取公开的笔记 + IPage getPublicNotes(Integer pageNum, Integer pageSize); +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/ReadingProgressService.java b/src/main/java/com/guwan/backend/service/ReadingProgressService.java new file mode 100644 index 0000000..5277c77 --- /dev/null +++ b/src/main/java/com/guwan/backend/service/ReadingProgressService.java @@ -0,0 +1,24 @@ +package com.guwan.backend.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.guwan.backend.entity.ReadingProgress; + +public interface ReadingProgressService { + // 创建或更新阅读进度 + ReadingProgress updateProgress(ReadingProgress progress); + + // 获取用户的阅读进度 + ReadingProgress getProgress(Long userId, Long bookId); + + // 获取用户的所有阅读进度 + IPage getUserProgress(Long userId, Integer pageNum, Integer pageSize); + + // 获取用户特定状态的书籍 + IPage getProgressByStatus(Long userId, String status, Integer pageNum, Integer pageSize); + + // 更新阅读时长 + void updateReadingTime(Long userId, Long bookId, Integer minutes); + + // 获取用户的阅读统计 + ReadingStatistics getReadingStatistics(Long userId); +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/BookServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/BookServiceImpl.java new file mode 100644 index 0000000..0ce70ac --- /dev/null +++ b/src/main/java/com/guwan/backend/service/impl/BookServiceImpl.java @@ -0,0 +1,73 @@ +package com.guwan.backend.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.guwan.backend.entity.Book; +import com.guwan.backend.mapper.BookMapper; +import com.guwan.backend.service.BookService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class BookServiceImpl implements BookService { + + private final BookMapper bookMapper; + + @Override + @Transactional + public Book addBook(Book book) { + bookMapper.insert(book); + return book; + } + + @Override + @Transactional + public Book updateBook(Book book) { + bookMapper.updateById(book); + return book; + } + + @Override + @Transactional + public void deleteBook(Long id) { + bookMapper.deleteById(id); + } + + @Override + public Book getBookById(Long id) { + return bookMapper.selectById(id); + } + + @Override + public Book getBookByIsbn(String isbn) { + return bookMapper.selectOne( + new LambdaQueryWrapper() + .eq(Book::getIsbn, isbn) + ); + } + + @Override + public IPage getBookList(Integer pageNum, Integer pageSize, String keyword) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (keyword != null && !keyword.isEmpty()) { + wrapper.like(Book::getTitle, keyword) + .or() + .like(Book::getAuthor, keyword) + .or() + .like(Book::getDescription, keyword); + } + return bookMapper.selectPage(new Page<>(pageNum, pageSize), wrapper); + } + + @Override + public IPage getBooksByCategory(String category, Integer pageNum, Integer pageSize) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Book::getCategory, category); + return bookMapper.selectPage(new Page<>(pageNum, pageSize), wrapper); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/ReadingNoteServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/ReadingNoteServiceImpl.java new file mode 100644 index 0000000..cd5234a --- /dev/null +++ b/src/main/java/com/guwan/backend/service/impl/ReadingNoteServiceImpl.java @@ -0,0 +1,87 @@ +package com.guwan.backend.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.guwan.backend.entity.ReadingNote; +import com.guwan.backend.mapper.ReadingNoteMapper; +import com.guwan.backend.service.ReadingNoteService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ReadingNoteServiceImpl implements ReadingNoteService { + + private final ReadingNoteMapper noteMapper; + + @Override + @Transactional + public ReadingNote addNote(ReadingNote note) { + noteMapper.insert(note); + return note; + } + + @Override + @Transactional + public ReadingNote updateNote(ReadingNote note) { + noteMapper.updateById(note); + return note; + } + + @Override + @Transactional + public void deleteNote(Long id) { + noteMapper.deleteById(id); + } + + @Override + public ReadingNote getNoteById(Long id) { + return noteMapper.selectById(id); + } + + @Override + public IPage getUserNotes(Long userId, Integer pageNum, Integer pageSize) { + return noteMapper.selectPage( + new Page<>(pageNum, pageSize), + new LambdaQueryWrapper() + .eq(ReadingNote::getUserId, userId) + .orderByDesc(ReadingNote::getCreatedTime) + ); + } + + @Override + public IPage getBookNotes(Long bookId, Integer pageNum, Integer pageSize) { + return noteMapper.selectPage( + new Page<>(pageNum, pageSize), + new LambdaQueryWrapper() + .eq(ReadingNote::getBookId, bookId) + .eq(ReadingNote::getIsPublic, true) + .orderByDesc(ReadingNote::getCreatedTime) + ); + } + + @Override + public IPage getUserBookNotes(Long userId, Long bookId, Integer pageNum, Integer pageSize) { + return noteMapper.selectPage( + new Page<>(pageNum, pageSize), + new LambdaQueryWrapper() + .eq(ReadingNote::getUserId, userId) + .eq(ReadingNote::getBookId, bookId) + .orderByDesc(ReadingNote::getCreatedTime) + ); + } + + @Override + public IPage getPublicNotes(Integer pageNum, Integer pageSize) { + return noteMapper.selectPage( + new Page<>(pageNum, pageSize), + new LambdaQueryWrapper() + .eq(ReadingNote::getIsPublic, true) + .orderByDesc(ReadingNote::getCreatedTime) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/ReadingProgressServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/ReadingProgressServiceImpl.java new file mode 100644 index 0000000..6ee9094 --- /dev/null +++ b/src/main/java/com/guwan/backend/service/impl/ReadingProgressServiceImpl.java @@ -0,0 +1,85 @@ +package com.guwan.backend.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.guwan.backend.entity.ReadingProgress; +import com.guwan.backend.mapper.ReadingProgressMapper; +import com.guwan.backend.service.ReadingProgressService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ReadingProgressServiceImpl implements ReadingProgressService { + + private final ReadingProgressMapper progressMapper; + + @Override + @Transactional + public ReadingProgress updateProgress(ReadingProgress progress) { + progress.setLastReadTime(LocalDateTime.now()); + + // 检查是否存在记录 + ReadingProgress existing = getProgress(progress.getUserId(), progress.getBookId()); + if (existing != null) { + progress.setId(existing.getId()); + progressMapper.updateById(progress); + } else { + progressMapper.insert(progress); + } + return progress; + } + + @Override + public ReadingProgress getProgress(Long userId, Long bookId) { + return progressMapper.selectOne( + new LambdaQueryWrapper() + .eq(ReadingProgress::getUserId, userId) + .eq(ReadingProgress::getBookId, bookId) + ); + } + + @Override + public IPage getUserProgress(Long userId, Integer pageNum, Integer pageSize) { + return progressMapper.selectPage( + new Page<>(pageNum, pageSize), + new LambdaQueryWrapper() + .eq(ReadingProgress::getUserId, userId) + .orderByDesc(ReadingProgress::getLastReadTime) + ); + } + + @Override + public IPage getProgressByStatus(Long userId, String status, Integer pageNum, Integer pageSize) { + return progressMapper.selectPage( + new Page<>(pageNum, pageSize), + new LambdaQueryWrapper() + .eq(ReadingProgress::getUserId, userId) + .eq(ReadingProgress::getStatus, status) + .orderByDesc(ReadingProgress::getLastReadTime) + ); + } + + @Override + @Transactional + public void updateReadingTime(Long userId, Long bookId, Integer minutes) { + ReadingProgress progress = getProgress(userId, bookId); + if (progress != null) { + progress.setReadingTime(progress.getReadingTime() + minutes); + progress.setLastReadTime(LocalDateTime.now()); + progressMapper.updateById(progress); + } + } + + @Override + public ReadingStatistics getReadingStatistics(Long userId) { + // TODO: 实现阅读统计逻辑 + return new ReadingStatistics(); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/websocket/ChatWebSocketHandler.java b/src/main/java/com/guwan/backend/websocket/ChatWebSocketHandler.java new file mode 100644 index 0000000..0a39497 --- /dev/null +++ b/src/main/java/com/guwan/backend/websocket/ChatWebSocketHandler.java @@ -0,0 +1,47 @@ +package com.guwan.backend.websocket; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class ChatWebSocketHandler extends TextWebSocketHandler { + + // 存储所有连接的会话 + private static final Map sessions = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + String sessionId = session.getId(); + sessions.put(sessionId, session); + log.info("新的连接: {}", sessionId); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String payload = message.getPayload(); + log.info("收到消息: {}", payload); + + // 广播消息给所有连接的客户端 + TextMessage response = new TextMessage(payload); + for (WebSocketSession webSocketSession : sessions.values()) { + if (webSocketSession.isOpen()) { + webSocketSession.sendMessage(response); + } + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + String sessionId = session.getId(); + sessions.remove(sessionId); + log.info("连接关闭: {}", sessionId); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/websocket/LiveChatHandler.java b/src/main/java/com/guwan/backend/websocket/LiveChatHandler.java new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/main/java/com/guwan/backend/websocket/LiveChatHandler.java @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/websocket/WebSocketAuthInterceptor.java b/src/main/java/com/guwan/backend/websocket/WebSocketAuthInterceptor.java new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/main/java/com/guwan/backend/websocket/WebSocketAuthInterceptor.java @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/websocket/message/ChatMessage.java b/src/main/java/com/guwan/backend/websocket/message/ChatMessage.java new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/main/java/com/guwan/backend/websocket/message/ChatMessage.java @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/db/migration/V10__create_book_tables.sql b/src/main/resources/db/migration/V10__create_book_tables.sql new file mode 100644 index 0000000..e515ece --- /dev/null +++ b/src/main/resources/db/migration/V10__create_book_tables.sql @@ -0,0 +1,71 @@ +-- 图书表 +CREATE TABLE `book` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '图书ID', + `isbn` varchar(20) DEFAULT NULL COMMENT 'ISBN编号', + `title` varchar(100) NOT NULL COMMENT '书名', + `author` varchar(100) DEFAULT NULL COMMENT '作者', + `publisher` varchar(100) DEFAULT NULL COMMENT '出版社', + `description` text COMMENT '描述', + `cover_url` varchar(255) DEFAULT NULL COMMENT '封面图片URL', + `category` varchar(50) DEFAULT NULL COMMENT '分类', + `tags` varchar(255) DEFAULT NULL COMMENT '标签(逗号分隔)', + `total_pages` int DEFAULT NULL COMMENT '总页数', + `language` varchar(20) DEFAULT NULL COMMENT '语言', + `publish_date` datetime DEFAULT NULL COMMENT '出版日期', + `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_isbn` (`isbn`), + KEY `idx_category` (`category`), + KEY `idx_created_time` (`created_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='图书表'; + +-- 阅读进度表 +CREATE TABLE `reading_progress` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '进度ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `book_id` bigint NOT NULL COMMENT '图书ID', + `status` varchar(20) NOT NULL COMMENT '状态:WANT_TO_READ,READING,FINISHED', + `current_page` int DEFAULT NULL COMMENT '当前页码', + `percentage` decimal(5,2) DEFAULT NULL COMMENT '阅读进度(0-100)', + `reading_time` int DEFAULT '0' COMMENT '阅读时长(分钟)', + `last_read_time` datetime DEFAULT NULL COMMENT '最后阅读时间', + `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_book` (`user_id`,`book_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_book_id` (`book_id`), + KEY `idx_status` (`status`), + KEY `idx_last_read_time` (`last_read_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='阅读进度表'; + +-- 阅读笔记表 +CREATE TABLE `reading_note` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '笔记ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `book_id` bigint NOT NULL COMMENT '图书ID', + `type` varchar(20) NOT NULL COMMENT '笔记类型:NOTE,HIGHLIGHT,THOUGHT', + `content` text NOT NULL COMMENT '笔记内容', + `chapter` varchar(100) DEFAULT NULL COMMENT '章节', + `page_number` int DEFAULT NULL COMMENT '页码', + `audio_url` varchar(255) DEFAULT NULL COMMENT '语音笔记URL', + `is_public` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否公开', + `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_book_id` (`book_id`), + KEY `idx_type` (`type`), + KEY `idx_is_public` (`is_public`), + KEY `idx_created_time` (`created_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='阅读笔记表'; + +-- 添加外键约束 +ALTER TABLE `reading_progress` + ADD CONSTRAINT `fk_progress_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`), + ADD CONSTRAINT `fk_progress_book` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`); + +ALTER TABLE `reading_note` + ADD CONSTRAINT `fk_note_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`), + ADD CONSTRAINT `fk_note_book` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`); \ No newline at end of file diff --git a/src/main/resources/static/chat.html b/src/main/resources/static/chat.html index 2c93e4f..63b1896 100644 --- a/src/main/resources/static/chat.html +++ b/src/main/resources/static/chat.html @@ -1,67 +1,65 @@ - WebSocket Chat + + WebSocket 聊天室
- - - + + diff --git a/src/main/resources/static/live-chat.html b/src/main/resources/static/live-chat.html new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/main/resources/static/live-chat.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/static/polling-chat.html b/src/main/resources/static/polling-chat.html new file mode 100644 index 0000000..7454952 --- /dev/null +++ b/src/main/resources/static/polling-chat.html @@ -0,0 +1,128 @@ + + + + + 轮询聊天室 + + + +

轮询聊天室

+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/src/test/java/com/guwan/backend/service/BookServiceTest.java b/src/test/java/com/guwan/backend/service/BookServiceTest.java new file mode 100644 index 0000000..6917a7c --- /dev/null +++ b/src/test/java/com/guwan/backend/service/BookServiceTest.java @@ -0,0 +1,68 @@ +package com.guwan.backend.service; + +import com.guwan.backend.entity.Book; +import com.guwan.backend.mapper.BookMapper; +import com.guwan.backend.service.impl.BookServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.time.LocalDateTime; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +class BookServiceTest { + + @Mock + private BookMapper bookMapper; + + @InjectMocks + private BookServiceImpl bookService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void addBook_Success() { + // 准备测试数据 + Book book = new Book(); + book.setTitle("测试书籍"); + book.setAuthor("测试作者"); + book.setIsbn("9787000000000"); + + when(bookMapper.insert(any(Book.class))).thenReturn(1); + + // 执行测试 + Book result = bookService.addBook(book); + + // 验证结果 + assertNotNull(result); + assertEquals("测试书籍", result.getTitle()); + verify(bookMapper, times(1)).insert(any(Book.class)); + } + + @Test + void getBookByIsbn_Success() { + // 准备测试数据 + Book book = new Book(); + book.setId(1L); + book.setTitle("测试书籍"); + book.setIsbn("9787000000000"); + + when(bookMapper.selectOne(any())).thenReturn(book); + + // 执行测试 + Book result = bookService.getBookByIsbn("9787000000000"); + + // 验证结果 + assertNotNull(result); + assertEquals("测试书籍", result.getTitle()); + assertEquals("9787000000000", result.getIsbn()); + } +} \ No newline at end of file