Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ff972e9
完善了代码
Apr 25, 2026
6512292
完善了代码2
Apr 26, 2026
0e04c28
完善了代码3
Apr 26, 2026
401f351
docs: add getHotTrend fix design spec
Apr 26, 2026
2a91f8d
完善了代码4
Apr 26, 2026
00c5219
docs: add queryAllComments重构设计
Apr 27, 2026
0437e6d
docs: add queryAllComments重构实现计划
Apr 27, 2026
ab276c8
refactor: queryAllComments返回PageVO<Comment>
Apr 27, 2026
369b151
feat: add CommentConverter手写字段映射
Apr 27, 2026
814f792
refactor: Controller层组装CommentWithUserVO
Apr 27, 2026
f3741e6
fix: CommentConverter移除不存在的updateDate字段
Apr 27, 2026
a8b490c
docs: add document search refactor design spec
Apr 27, 2026
73539ee
feat: Tag新增color字段, SearchDocument新增tagNames和categoryName字段
Apr 27, 2026
32197f3
feat: 新增SearchQuery和DocSearchVO用于文档搜索接口
Apr 27, 2026
af209dc
feat: add searchDocuments method to ElasticService
Apr 27, 2026
1d217bd
feat: DocumentService.search 支持 SearchQuery 参数
Apr 27, 2026
7211254
feat: DocumentController.search GET转POST并添加登录认证
Apr 27, 2026
98d747f
feat: ES索引同步 - 文档上传时同步更新ES索引
Apr 27, 2026
59e5d46
fix: StringToEnumConverter null check for getCode()
Apr 27, 2026
6429cfa
fix: StringToEnumConverter generic type to Enum<T> & BaseEnum
Apr 27, 2026
328ffa7
fix: StringCodeToEnumConverterFactory generic type to Enum<?> for com…
Apr 27, 2026
00fdb2b
fix: remove diamond operator for StringToEnumConverter type inference
Apr 27, 2026
67877e6
fix: use existing findByFileIds method for category filtering
Apr 27, 2026
b13913e
fix: use Collectors.toList() instead of toList() for mutable list
Apr 27, 2026
5c525ff
fix: use document ID (UUID) as ES document ID instead of MD5
Apr 27, 2026
a32418b
完善了代码5
Apr 27, 2026
18d84eb
docs: add spec for document list fulltext search
Apr 28, 2026
3710ebb
docs: add implementation plan for document list fulltext search
Apr 28, 2026
7659cf6
feat: add paginated full-text search with multi-highlight support
Apr 28, 2026
68e7c91
feat: add fulltext search routing in DocumentServiceImpl.list()
Apr 28, 2026
ffff0f0
完善了代码6
Apr 28, 2026
c7d1fa3
完善了代码7
Apr 28, 2026
76f9158
docs: add statistics API design spec
Apr 29, 2026
4d145e8
docs: add statistics API implementation plan
Apr 29, 2026
ec170df
feat: extend statistics API with new endpoints
Apr 29, 2026
4ed67e2
增加了统计功能
Apr 29, 2026
77875a8
fix(*): 修改了文档检索的错误
May 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,21 @@
"Bash(mvn install:*)",
"Bash(mvn spring-boot:run -q)",
"Bash(git config:*)",
"Bash(env)"
"Bash(env)",
"Bash(javap -p C:/Project/java/all-docs/all-docs-infrastructure/target/classes/com/jiaruiblog/infrastructure/config/datasource/MyBatisConfig.class)",
"Bash(javap:*)",
"Bash(git log:*)",
"Bash(mvn spring-boot:run -pl all-docs-bootstrap -q)",
"Bash(taskkill //F //IM java.exe)",
"Bash(netstat -ano)",
"Bash(mvn spring-boot:run -pl all-docs-bootstrap)",
"Bash(grep:*)",
"Bash(ls -la *.md *.sql)",
"Bash(mvn dependency:tree)",
"Bash(git:*)",
"Bash(xxd \"C:\\\\Project\\\\java\\\\all-docs\\\\all-docs-bootstrap\\\\src\\\\main\\\\resources\\\\i18n\\\\messages_zh_CN.properties\")",
"Bash(./mvnw compile:*)",
"Bash(xxd \"C:\\\\Project\\\\java\\\\all-docs\\\\all-docs-infrastructure\\\\src\\\\main\\\\resources\\\\mapper\\\\CommentMapper.xml\")"
]
}
}
44 changes: 44 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 待补充功能

以下功能在 Statistics API 扩展中暂未实现,后续补充:

## 1. 浏览量统计 (viewNum)

**目标:** 在 `/statistics/all` 接口中返回真实的文档浏览次数

**方案:** 在文档浏览接口中增加 Redis ZSet 计数,key 为 `doc:hot`

**待办:**
- [ ] 在文档浏览接口中调用 `redisService.incrementDocScore(docId, 1)`
- [ ] 验证 `doc:hot` ZSet 是否正常更新

## 2. 下载次数统计 (downloadNum)

**目标:** 在 `/statistics/all` 接口中返回真实的文档下载次数

**方案:** 在文档下载接口中增加 Redis 计数

**待办:**
- [ ] 确定下载接口位置
- [ ] 增加下载计数逻辑
- [ ] 在 `all()` 方法中聚合下载次数

## 3. 搜索次数统计 (searchNum)

**目标:** 在 `/statistics/all` 接口中返回真实的搜索次数

**方案:** 使用 Redis ZSet `search:hot` 的总分数作为搜索次数

**待办:**
- [ ] 在 `all()` 方法中聚合 `search:hot` ZSet 的总分

## 4. 用户活跃度统计 (userActivity)

**目标:** 实现 `/statistics/userActivity` 接口

**方案:** 在用户登录/关键操作时记录活跃状态,按月聚合

**待办:**
- [ ] 设计用户活跃度记录机制(Redis 或数据库)
- [ ] 实现按月统计活跃用户数
- [ ] 实现 `userActivity()` 方法
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.jiaruiblog.api.config;

import com.jiaruiblog.api.filter.JwtFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author luojiarui
**/
@Configuration
public class JwtFilterConfig {

@Bean
public FilterRegistrationBean<JwtFilter> jwtFilterRegistration() {
FilterRegistrationBean<JwtFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new JwtFilter());
registration.addUrlPatterns("/*");
registration.setName("JwtFilter");
registration.setOrder(1);
return registration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ public ApiResult<Void> insert(@RequestBody CollectDTO collect, HttpServletReques
log.error("文档不存在,文档ID: {}", relationship.getDocId());
throw new BusinessException(ErrorCode.DOCUMENT_NOT_FOUND);
}
collectService.insert(relationship);
log.info("文档收藏成功,文档ID: {}, 用户ID: {}", relationship.getDocId(), relationship.getUserId());
try {
collectService.insert(relationship);
log.info("文档收藏成功,文档ID: {}, 用户ID: {}", relationship.getDocId(), relationship.getUserId());
} catch (Exception e) {
log.error("文档收藏失败,文档ID: {}, 用户ID: {}, 错误: {}", relationship.getDocId(), relationship.getUserId(), e.getMessage(), e);
throw e;
}
return ApiResult.success();
}

Expand All @@ -68,8 +73,13 @@ public ApiResult<Void> insert(@RequestBody CollectDTO collect, HttpServletReques
public ApiResult<Void> remove(@RequestBody CollectDTO collect, HttpServletRequest request) {
log.info("开始执行取消收藏操作,文档ID: {}, 用户ID: {}", collect.getDocId(), request.getAttribute("id"));
CollectDocRelationship relationship = setRelationshipValue(collect, request);
collectService.remove(relationship);
log.info("取消收藏成功,文档ID: {}, 用户ID: {}", relationship.getDocId(), relationship.getUserId());
try {
collectService.remove(relationship);
log.info("取消收藏成功,文档ID: {}, 用户ID: {}", relationship.getDocId(), relationship.getUserId());
} catch (Exception e) {
log.error("取消收藏失败,文档ID: {}, 用户ID: {}, 错误: {}", relationship.getDocId(), relationship.getUserId(), e.getMessage(), e);
throw e;
}
return ApiResult.success();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

import com.jiaruiblog.api.auth.Permission;
import com.jiaruiblog.application.service.ICommentService;
import com.jiaruiblog.application.service.converter.CommentConverter;
import com.jiaruiblog.common.ApiResult;
import com.jiaruiblog.common.exception.BusinessException;
import com.jiaruiblog.common.exception.ErrorCode;
import com.jiaruiblog.domain.entity.po.Comment;
import com.jiaruiblog.domain.entity.po.FileDocument;
import com.jiaruiblog.domain.entity.dto.BasePageDTO;
import com.jiaruiblog.domain.entity.dto.BatchIdDTO;
import com.jiaruiblog.domain.entity.dto.CommentDTO;
import com.jiaruiblog.domain.entity.dto.CommentListDTO;
import com.jiaruiblog.domain.entity.vo.CommentWithUserVO;
import com.jiaruiblog.domain.entity.vo.PageVO;
import com.jiaruiblog.common.enums.PermissionEnum;
import com.jiaruiblog.infrastructure.repository.DocumentRepository;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
Expand All @@ -22,9 +25,11 @@
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* 评论系统的控制器
Expand All @@ -40,6 +45,12 @@ public class CommentController {
@Resource
ICommentService commentService;

@Resource
CommentConverter commentConverter;

@Resource
DocumentRepository documentRepository;

@Operation(summary = "新增单个评论", description = "添加新的评论")
@PostMapping(value = "/auth/insert")
public ApiResult<Void> insert(@RequestBody CommentDTO commentDTO, HttpServletRequest request) {
Expand Down Expand Up @@ -102,15 +113,53 @@ private Comment getComment(CommentDTO commentDTO, HttpServletRequest request) {
@PostMapping(value = "/auth/myComments")
public ApiResult<PageVO<CommentWithUserVO>> queryMyComments(@RequestBody BasePageDTO pageDTO, HttpServletRequest request) {
String userId = (String) request.getAttribute("id");
PageVO<CommentWithUserVO> result = commentService.queryAllComments(pageDTO, userId, false);
PageVO<Comment> commentPage = commentService.queryAllComments(pageDTO, userId, false);
PageVO<CommentWithUserVO> result = buildCommentWithUserVO(commentPage);
return ApiResult.success(result);
}

@Operation(summary = "查询所有评论", description = "管理员查询所有用户的评论列表")
@Permission(PermissionEnum.ADMIN)
@PostMapping(value = "/auth/allComments")
public ApiResult<PageVO<CommentWithUserVO>> queryAllComments(@RequestBody BasePageDTO pageDTO) {
PageVO<CommentWithUserVO> result = commentService.queryAllComments(pageDTO, null, true);
PageVO<Comment> commentPage = commentService.queryAllComments(pageDTO, null, true);
PageVO<CommentWithUserVO> result = buildCommentWithUserVO(commentPage);
return ApiResult.success(result);
}

private PageVO<CommentWithUserVO> buildCommentWithUserVO(PageVO<Comment> commentPage) {
if (commentPage == null || commentPage.getList() == null || commentPage.getList().isEmpty()) {
return PageVO.<CommentWithUserVO>builder()
.total(0)
.list(new ArrayList<>())
.pageNum(commentPage != null ? commentPage.getPageNum() : 1)
.pageSize(commentPage != null ? commentPage.getPageSize() : 10)
.build();
}

List<String> docIdList = commentPage.getList().stream()
.map(Comment::getDocId)
.collect(Collectors.toList());

List<FileDocument> documents = documentRepository.findByIdList(docIdList);
Map<String, String> docNameMap = documents.stream()
.collect(Collectors.toMap(FileDocument::getId, FileDocument::getName));

List<CommentWithUserVO> voList = commentPage.getList().stream()
.map(comment -> {
String docName = docNameMap.get(comment.getDocId());
if (docName == null) {
docName = comment.getDocName(); // fallback to stored docName when document is deleted
}
return commentConverter.toVO(comment, docName);
})
.collect(Collectors.toList());

return PageVO.<CommentWithUserVO>builder()
.total(commentPage.getTotal())
.list(voList)
.pageNum(commentPage.getPageNum())
.pageSize(commentPage.getPageSize())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
@Slf4j
@CrossOrigin
@RestController
@RequestMapping("/api/v1/doc-log")
@RequestMapping("/api/v1/docLog")
public class DocLogController {

@Resource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@Slf4j
@CrossOrigin
@RestController
@RequestMapping("/api/v1/doc-review")
@RequestMapping("/api/v1/docReview")
public class DocReviewController {

@Resource
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package com.jiaruiblog.api.controller;

import com.jiaruiblog.api.auth.Permission;
import com.jiaruiblog.common.enums.PermissionEnum;
import com.jiaruiblog.api.intercepter.SensitiveFilter;
import com.jiaruiblog.application.service.DocumentService;
import com.jiaruiblog.application.service.IDocLogService;
import com.jiaruiblog.application.service.RedisService;
import com.jiaruiblog.application.service.impl.DocLogServiceImpl;
import com.jiaruiblog.application.service.impl.RedisServiceImpl;
import com.jiaruiblog.common.ApiResult;
import com.jiaruiblog.domain.entity.po.FileDocument;
import com.jiaruiblog.domain.entity.po.User;
import com.jiaruiblog.common.enums.FilterTypeEnum;
import com.jiaruiblog.common.enums.PermissionEnum;
import com.jiaruiblog.common.exception.BusinessException;
import com.jiaruiblog.common.exception.ErrorCode;
import com.jiaruiblog.domain.entity.dto.DocumentDTO;
import com.jiaruiblog.domain.entity.dto.RemoveObjectDTO;
import com.jiaruiblog.domain.entity.dto.SearchQuery;
import com.jiaruiblog.domain.entity.dto.document.UpdateInfoDTO;
import com.jiaruiblog.domain.entity.po.FileDocument;
import com.jiaruiblog.domain.entity.po.User;
import com.jiaruiblog.domain.entity.vo.DocWithCateVO;
import com.jiaruiblog.domain.entity.vo.DocumentVO;
import com.jiaruiblog.domain.entity.vo.PageVO;
import com.jiaruiblog.common.enums.FilterTypeEnum;
import com.jiaruiblog.common.exception.BusinessException;
import com.jiaruiblog.common.exception.ErrorCode;
import com.jiaruiblog.api.intercepter.SensitiveFilter;
import com.jiaruiblog.application.service.IDocLogService;
import com.jiaruiblog.application.service.DocumentService;
import com.jiaruiblog.application.service.RedisService;
import com.jiaruiblog.application.service.impl.DocLogServiceImpl;
import com.jiaruiblog.application.service.impl.RedisServiceImpl;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Resource;
Expand Down Expand Up @@ -57,17 +57,18 @@ public class DocumentController {
public ApiResult<Object> list(@RequestBody @Schema(description = "文档查询DTO") DocumentDTO documentDTO)
throws IOException {
String userId = documentDTO.getUserId();
if (StringUtils.hasText(documentDTO.getFilterWord()) &&
documentDTO.getType() == FilterTypeEnum.FILTER) {
if (StringUtils.hasText(documentDTO.getFilterWord())) {
String filterWord = documentDTO.getFilterWord();
//非法敏感词汇判断
// 敏感词汇判断
SensitiveFilter filter = SensitiveFilter.getInstance();
int n = filter.checkSensitiveWord(filterWord, 0, 1);
//存在非法字符
// 存在非法字符时仅记录日志,不影响搜索
if (n > 0) {
log.error("这个人输入了非法字符--> {},不知道他到底要查什么~", filterWord);
} else {
// 热门搜索词所有人都能看,记录到 ZSet
redisService.incrementScoreByUserId(filterWord, RedisServiceImpl.SEARCH_KEY);
// 用户搜索历史,只在用户已登录时记录
if (StringUtils.hasText(userId)) {
redisService.addSearchHistoryByUserId(userId, filterWord);
}
Expand Down Expand Up @@ -148,31 +149,13 @@ public ApiResult<Object> hot() {
return ApiResult.success(keyList);
}

@Operation(summary = "2.4 文档全文搜索", description = "根据关键字搜索已审核且解析成功的文档")
@GetMapping(value = "/search")
@Operation(summary = "文档搜索", description = "根据多条件搜索文档")
@PostMapping(value = "/searchList")
@Permission(value = PermissionEnum.USER)
public ApiResult<Object> search(
@RequestParam(value = "keyword")
@Schema(description = "搜索关键字") String keyword,
@RequestParam(value = "page", defaultValue = "1")
@Schema(description = "页码") int page,
@RequestParam(value = "size", defaultValue = "10")
@Schema(description = "每页大小") int size) throws IOException {
if (keyword == null || keyword.trim().isEmpty()) {
return ApiResult.success(PageVO.<DocumentVO>builder()
.pageNum(page)
.pageSize(size)
.total(0)
.list(new java.util.ArrayList<>())
.build());
}
// 记录搜索词
if (StringUtils.hasText(keyword)) {
SensitiveFilter filter = SensitiveFilter.getInstance();
int n = filter.checkSensitiveWord(keyword, 0, 1);
if (n <= 0) {
redisService.incrementScoreByUserId(keyword, RedisServiceImpl.SEARCH_KEY);
}
}
return ApiResult.success(documentService.search(keyword.trim(), page, size));
@RequestBody @Schema(description = "搜索参数") SearchQuery query,
HttpServletRequest request) {
String userId = (String) request.getAttribute("id");
return ApiResult.success(documentService.search(query, userId));
}
}
Loading