feat: 实现消息、文章和分类的树形结构展示功能

refactor: 重构消息、文章和疯言疯语的分页查询接口
refactor(controller): 调整疯言疯语控制器的更新接口参数
refactor(service): 优化消息服务的分页查询逻辑

fix: 修复JWT认证过滤器中的令牌验证问题
fix(properties): 修正生产环境数据库配置

style: 清理无用代码并删除HelpController
This commit is contained in:
qingfeng1121
2025-12-23 13:57:54 +08:00
parent 15eca0d0b5
commit 33498d75c5
29 changed files with 813 additions and 3789 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -44,25 +44,52 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
try {
// 获取token
String token = getTokenFromRequest(request);
System.out.println(token);
if (token != null && validateToken(token)) {
// 从token中获取用户名
String username = jwtUtils.getUsernameFromToken(token);
System.out.println("username: " + username);
// 加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// System.out.println(token);
if (token != null) {
if (validateToken(token)) {
// 从token中获取用户名
String username = jwtUtils.getUsernameFromToken(token);
// System.out.println("username: " + username);
// 加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 创建认证对象
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 创建认证对象
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 设置认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 设置认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
// 如果token无效但不为空返回401
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"message\":\"无效的认证令牌\"}");
return;
}
}
} catch (io.jsonwebtoken.ExpiredJwtException e) {
// 专门处理令牌过期返回401和明确的错误信息
logger.error("令牌已过期: {}", e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"message\":\"认证令牌已过期\"}");
return;
} catch (io.jsonwebtoken.JwtException e) {
// 其他JWT异常
logger.error("无效的token格式: {}", e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"message\":\"无效的令牌格式\"}");
return;
} catch (Exception e) {
logger.error("无法设置用户认证: {}", e);
SecurityContextHolder.clearContext();
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"message\":\"认证失败\"}");
return;
}
filterChain.doFilter(request, response);
@@ -88,8 +115,17 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
String username = jwtUtils.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return jwtUtils.validateToken(token, userDetails);
} catch (io.jsonwebtoken.ExpiredJwtException e) {
// 专门处理令牌过期,记录日志但不抛出异常
logger.error("令牌已过期: {}", e.getMessage());
return false;
} catch (io.jsonwebtoken.JwtException e) {
// 其他JWT异常
logger.error("无效的token格式: {}", e.getMessage());
return false;
} catch (Exception e) {
logger.error("无效的token: {}", e);
// 其他异常
logger.error("验证token失败: {}", e.getMessage());
return false;
}
}

View File

@@ -3,11 +3,9 @@ package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.dto.ArticleDto;
import com.qf.myafterprojecy.pojo.dto.ArticleTreeDto;
import com.qf.myafterprojecy.pojo.dto.ArriclePageDto;
import com.qf.myafterprojecy.service.IArticleService;
import org.springframework.data.domain.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -52,7 +50,7 @@ public class ArticleController {
* @return 返回包含文章列表的ResponseMessage对象
*/
@GetMapping("/status/{status}")
public ResponseMessage<List<Article>> getArticlesByStatus(@PathVariable Integer status) {
public ResponseMessage<List<ArticleTreeDto>> getArticlesByStatus(@PathVariable Integer status) {
return articleService.getArticlesByStatus(status);
}
@@ -66,7 +64,7 @@ public class ArticleController {
// api/articles/status/page?status=1&page=1&size=2
// get 只能这样不能传递json
@GetMapping("/status/page")
public ResponseMessage<Page<Article>> getArticlesByStatusWithPagination(ArriclePageDto pageDto) {
public ResponseMessage<List<ArticleTreeDto>> getArticlesByStatusWithPagination(ArriclePageDto pageDto) {
return articleService.getArticlesByStatusWithPagination(pageDto);
}
@@ -93,7 +91,7 @@ public class ArticleController {
* @return 返回包含文章列表的ResponseMessage对象
*/
@GetMapping("/title/{title}")
public ResponseMessage<List<Article>> getArticlesByTitle(@PathVariable String title) {
public ResponseMessage<List<ArticleTreeDto>> getArticlesByTitle(@PathVariable String title) {
return articleService.getArticlesByTitle(title);
}

View File

@@ -16,7 +16,7 @@ import java.util.List;
import javax.validation.Valid;
@RestController
@RequestMapping("/api/category-attributes")
@RequestMapping("/api/categoryattributes")
@Validated
public class CategoryAttributeController {

View File

@@ -3,6 +3,7 @@ package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import com.qf.myafterprojecy.pojo.dto.CategoryTreeDto;
import com.qf.myafterprojecy.service.ICategoryService;
import org.slf4j.Logger;
@@ -49,7 +50,16 @@ public class CategoryController {
log.info("接收获取所有分类列表的请求");
return categoryService.getAllCategories();
}
// 分类树
/**
* 获取分类树结构
* @return 返回分类树结构
*/
@GetMapping("/tree")
public ResponseMessage<List<CategoryTreeDto>> getCategoryTree() {
log.info("接收获取分类树结构的请求");
return categoryService.getCategoryTree();
}
/**
* 创建新分类
* @param categoryDto 分类数据传输对象

View File

@@ -1,74 +0,0 @@
package com.qf.myafterprojecy.controller;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 帮助控制器类处理前端调用api/help请求
* 提供README_API.md文件的读取和返回功能
*/
@RestController
@RequestMapping("/api/help")
public class HelpController {
/**
* 获取README_API.md文件内容
* @return 返回包含README_API.md文件内容的ResponseMessage对象
*/
@GetMapping
public ResponseMessage<String> getReadmeApi() {
try {
// 获取项目根目录
String rootPath = System.getProperty("user.dir") ;
// 构建README_API.md文件路径
File readmeFile = new File(rootPath, "README_API.md");
// 检查文件是否存在
if (!readmeFile.exists() || !readmeFile.isFile()) {
// 如果不存在,尝试使用类路径资源加载
try {
ClassPathResource resource = new ClassPathResource("README_API.md");
String markdownContent = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()), StandardCharsets.UTF_8);
// 将Markdown转换为HTML
String htmlContent = convertMarkdownToHtml(markdownContent);
return ResponseMessage.success(htmlContent, "获取API文档成功");
} catch (IOException e) {
return ResponseMessage.error("未找到README_API.md文件");
}
}
// 读取文件内容
String markdownContent = new String(FileCopyUtils.copyToByteArray(new FileInputStream(readmeFile)), StandardCharsets.UTF_8);
// 将Markdown转换为HTML
String htmlContent = convertMarkdownToHtml(markdownContent);
return ResponseMessage.success(htmlContent, "获取API文档成功");
} catch (IOException e) {
return ResponseMessage.error("读取README_API.md文件失败: " + e.getMessage());
}
}
/**
* 将Markdown文本转换为HTML
* @param markdown 原始Markdown文本
* @return 转换后的HTML字符串
*/
private String convertMarkdownToHtml(String markdown) {
Parser parser = Parser.builder().build();
Node document = parser.parse(markdown);
HtmlRenderer renderer = HtmlRenderer.builder().build();
return renderer.render(document);
}
}

View File

@@ -4,6 +4,7 @@ import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.pojo.dto.MessagePageDto;
import com.qf.myafterprojecy.pojo.dto.MessageTreeDto;
import com.qf.myafterprojecy.service.IMessageService;
import org.slf4j.Logger;
@@ -35,7 +36,7 @@ public class MessageController {
* 分页查询消息
*/
@GetMapping("/page")
public ResponseMessage<List<Message>> getMessagesByPage(MessagePageDto messagePageDto) {
public ResponseMessage<List<MessageTreeDto>> getMessagesByPage(MessagePageDto messagePageDto) {
logger.info("接收分页查询消息的请求: {}", messagePageDto);
return messageService.getMessagesByPage(messagePageDto);
}
@@ -45,8 +46,8 @@ public class MessageController {
* @return 消息数量
* 文章ID为null时返回所有消息数量
*/
@GetMapping("/count")
public ResponseMessage<Integer> getMessageCount( Integer articleId) {
@GetMapping("/count/{articleId}")
public ResponseMessage<Integer> getMessageCount(@PathVariable Integer articleId) {
logger.info("接收获取消息数量的请求: {}", articleId);
return messageService.getMessageCountByArticleId(articleId);
}
@@ -62,11 +63,11 @@ public class MessageController {
/**
* 根据文章ID获取消息列表
*/
@GetMapping("/article/{articleId}")
public ResponseMessage<List<Message>> getMessagesByArticleId(@PathVariable Integer articleId) {
logger.info("接收根据文章ID获取消息的请求: {}", articleId);
return messageService.getMessagesByArticleId(articleId);
}
// @GetMapping("/article/{articleId}")
// public ResponseMessage<List<Message>> getMessagesByArticleId(@PathVariable Integer articleId) {
// logger.info("接收根据文章ID获取消息的请求: {}", articleId);
// return messageService.getMessagesByArticleId(articleId);
// }
/**
* 获取所有根消息(非回复的消息)

View File

@@ -3,6 +3,8 @@ package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Nonsense;
import com.qf.myafterprojecy.pojo.dto.NonsenseDto;
import com.qf.myafterprojecy.pojo.dto.NonsensePageDto;
import com.qf.myafterprojecy.service.INonsenseService;
import org.slf4j.Logger;
@@ -37,15 +39,14 @@ public class NonsenseController {
}
/**
* 根据状态获取疯言疯语内容
* @param status 状态0未发表 1已发表 2已删除
* 根据分页信息获取疯言疯语内容
* @param page 分页信息
* @return 疯言疯语内容列表
*/
@GetMapping("/status/{status}")
public ResponseMessage<List<Nonsense>> getNonsenseByStatus(
@PathVariable("status") Integer status) {
logger.info("请求获取状态为{}的疯言疯语内容", status);
return nonsenseService.getNonsenseByStatus(status);
@GetMapping("/page")
public ResponseMessage<List<Nonsense>> getNonsenseByStatus(NonsensePageDto page) {
logger.info("请求获取状态为{}的疯言疯语内容, 分页信息: {}", page.getStatus(), page);
return nonsenseService.getNonsenseByStatus(page);
}
/**
@@ -78,11 +79,11 @@ public class NonsenseController {
* @param nonsenseDto 疯言疯语内容数据
* @return 更新结果
*/
@PutMapping("/{id}")
@PutMapping()
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Nonsense> updateNonsense(@PathVariable("id") Integer id, @Valid @RequestBody NonsenseDto nonsenseDto) {
logger.info("请求更新ID为{}的疯言疯语内容", id);
return nonsenseService.updateNonsense(id, nonsenseDto);
public ResponseMessage<Nonsense> updateNonsense(@RequestBody NonsenseDto nonsenseDto) {
logger.info("请求更新ID为{}的疯言疯语内容", nonsenseDto.getId());
return nonsenseService.updateNonsense(nonsenseDto);
}
/**

View File

@@ -13,6 +13,6 @@ public class GlobalExceptionHandler {
@ExceptionHandler(value=Exception.class)
public ResponseMessage<String> handleException(Exception e, HttpServletRequest request) {
logger.error("请求路径:{},异常消息:{}",request.getRequestURI(),e.getMessage());
return new ResponseMessage<>(500,"服务器异常",e.getMessage());
return new ResponseMessage<String>(500,"服务器异常",e.getMessage(),false);
}
}

View File

@@ -18,6 +18,8 @@ public class ResponseMessage<T> {
private boolean success;
// 响应数据,泛型类型,支持不同类型的数据
private T data;
// 分页总页数,仅在分页查询时使用
private Integer totalPages;
/**
* 构造方法,用于创建响应消息对象
@@ -25,11 +27,13 @@ public class ResponseMessage<T> {
* @param code 状态码
* @param message 响应消息
* @param data 响应数据
* @param totalPages 分页总页数
*/
public ResponseMessage(Integer code, String message, T data) {
public ResponseMessage(Integer code, String message, T data, Integer totalPages) {
this.code = code;
this.message = message;
this.data = data;
this.totalPages = totalPages;
// 自动根据状态码判断是否成功
this.success = code >= 200 && code < 300;
}
@@ -41,6 +45,22 @@ public class ResponseMessage<T> {
* @param data 响应数据
* @param success 是否成功
*/
public ResponseMessage(Integer code, String message, T data, boolean success, Integer totalPages) {
this.code = code;
this.message = message;
this.data = data;
this.success = success;
this.totalPages = totalPages;
}
/**
* 完整参数的构造方法
* @param code 状态码
* @param message 响应消息
* @param data 响应数据
* @param success 是否成功
* @param totalPages 分页总页数
*/
public ResponseMessage(Integer code, String message, T data, boolean success) {
this.code = code;
this.message = message;
@@ -48,6 +68,7 @@ public class ResponseMessage<T> {
this.success = success;
}
// ----------------------------------- 成功响应方法 -----------------------------------
/**
@@ -218,8 +239,8 @@ public class ResponseMessage<T> {
* @param <T> 数据类型
* @return 分页响应对象
*/
public static <T> ResponseMessage<T> page(T data, String message) {
return new ResponseMessage<>(HttpStatus.OK.value(), message, data, true);
public static <T> ResponseMessage<T> page(T data, String message, Integer totalPages) {
return new ResponseMessage<>(HttpStatus.OK.value(), message, data, true, totalPages);
}
public Integer getCode() {

View File

@@ -0,0 +1,112 @@
package com.qf.myafterprojecy.pojo.dto;
import java.time.LocalDateTime;
public class ArticleTreeDto {
private Integer articleid;
private String title;
private String content;
private String img;
private Integer viewcount;
private Integer likes; // 点赞数
private Integer status;
private String markdownscontent;
private Integer attributeid;
private String attributename;
private Integer userid;
private String username;
private Integer commentcount; // 评论数
private LocalDateTime createtime;
private LocalDateTime updatetime;
public Integer getArticleid() {
return articleid;
}
public void setArticleid(Integer articleid) {
this.articleid = articleid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public Integer getViewcount() {
return viewcount;
}
public void setViewcount(Integer viewcount) {
this.viewcount = viewcount;
}
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMarkdownscontent() {
return markdownscontent;
}
public void setMarkdownscontent(String markdownscontent) {
this.markdownscontent = markdownscontent;
}
public Integer getAttributeid() {
return attributeid;
}
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public String getAttributename() {
return attributename;
}
public void setAttributename(String attributename) {
this.attributename = attributename;
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public LocalDateTime getCreatetime() {
return createtime;
}
public void setCreatetime(LocalDateTime createtime) {
this.createtime = createtime;
}
public LocalDateTime getUpdatetime() {
return updatetime;
}
public void setUpdatetime(LocalDateTime updatetime) {
this.updatetime = updatetime;
}
public Integer getCommentcount() {
return commentcount;
}
public void setCommentcount(Integer commentcount) {
this.commentcount = commentcount;
}
}

View File

@@ -0,0 +1,40 @@
package com.qf.myafterprojecy.pojo.dto;
import java.util.List;
import com.qf.myafterprojecy.pojo.Categoryattribute;
public class CategoryTreeDto {
private Integer id;
private String name;
private List<Categoryattribute> children;
// 构造方法
public CategoryTreeDto() {
}
// 全参构造方法
public CategoryTreeDto(Integer id, String name, List<Categoryattribute> children) {
this.id = id;
this.name = name;
this.children = children;
}
// getter和setter方法
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Categoryattribute> getChildren() {
return children;
}
public void setChildren(List<Categoryattribute> children) {
this.children = children;
}
}

View File

@@ -0,0 +1,43 @@
package com.qf.myafterprojecy.pojo.dto;
import java.util.ArrayList;
import java.util.List;
public class MessageTreeDto extends MessageDto {
private List<MessageTreeDto> children = new ArrayList<>();
private int replyCount = 0;
private String replyToNickname; // 被回复者昵称
private Integer replyToId; // 被回复消息ID
public List<MessageTreeDto> getChildren() {
return children;
}
public void setChildren(List<MessageTreeDto> children) {
this.children = children;
}
public int getReplyCount() {
return replyCount;
}
public void setReplyCount(int replyCount) {
this.replyCount = replyCount;
}
public String getReplyToNickname() {
return replyToNickname;
}
public void setReplyToNickname(String replyToNickname) {
this.replyToNickname = replyToNickname;
}
public Integer getReplyToId() {
return replyToId;
}
public void setReplyToId(Integer replyToId) {
this.replyToId = replyToId;
}
}

View File

@@ -0,0 +1,35 @@
package com.qf.myafterprojecy.pojo.dto;
public class NonsensePageDto {
private Integer status;
private Integer pageNum;
private Integer pageSize;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
@Override
public String toString() {
return "Page [pageNum=" + pageNum + ", pageSize=" + pageSize + "]";
}
public NonsensePageDto(Integer pageNum, Integer pageSize) {
super();
this.pageNum = pageNum;
this.pageSize = pageSize;
}
}

View File

@@ -43,4 +43,10 @@ public interface CategoryAttributeRepository extends JpaRepository<Categoryattri
* @return 属性对象
*/
Optional<Categoryattribute> findByCategoryidAndAttributename(Integer categoryid, String attributename);
/**
* 根据分类ID获取分类属性列表
* @param categoryids 分类ID列表
* @return 返回包含所有分类属性的列表
*/
List<Categoryattribute> findByCategoryidIn(List<Integer> categoryids);
}

View File

@@ -15,69 +15,89 @@ import java.util.List;
@Repository
public interface MessageRepository extends JpaRepository<Message, Integer> {
// 根据文章ID查询消息
/**
* 根据文章ID查询消息
* @param articleid 文章ID
* @return 文章下的消息列表
*/
List<Message> findByArticleid(Integer articleid);
// 查询所有父消息(回复的根消息)
/**
* 查询所有父消息(回复的根消息)
*
* @return 根回复消息列表
*/
List<Message> findByParentidIsNull();
// 根据父消息ID查询回复
/**
* 根据父消息ID查询回复
*
* @param parentid 父消息ID
* @return 回复消息列表
*/
List<Message> findByParentid(Integer parentid);
// 根据昵称模糊查询消息
/**
* 根据昵称模糊查询消息
*
* @param nickname 昵称关键词
* @return 包含关键词的消息列表
*/
List<Message> findByNicknameContaining(String nickname);
/**
* 查询指定文章下的所有父消息(根回复)
*
* @param articleId 文章ID
* @return 根回复消息列表
*/
@Query("SELECT m FROM Message m WHERE m.articleid = :articleId AND m.parentid IS NULL ORDER BY m.createdAt DESC")
List<Message> findRootMessagesByArticleId(@Param("articleId") Integer articleId);
/**
* 点赞数增加
*
* @param messageId 消息ID
*/
@Modifying
@Query("UPDATE Message m SET m.likes = COALESCE(m.likes, 0) + 1 WHERE m.messageid = :messageId")
void incrementLikes(@Param("messageId") Integer messageId);
// 统计指定文章的评论数量
// 统计指定文章下的主留言数量
/**
* 根据文章ID分页查询消息
* @param articleid 文章ID
* @param pageable 分页信息
* 根据文章ID分页查询消息(不包括回复)
* @param articleid 文章ID (可选)
* @param pageable 分页信息
* @return 分页消息列表
*/
@Query("SELECT m FROM Message m WHERE m.articleid = :articleId ORDER BY m.createdAt DESC")
@Query("SELECT m FROM Message m WHERE m.articleid = :articleId AND m.parentid IS NULL ORDER BY m.createdAt ASC")
Page<Message> findByArticleId(@Param("articleId") Integer articleId, PageRequest pageable);
/**
* 根据页查询消息
/**
* 根据父ID列表查询所有子回复包括多级嵌套
* 注意:这里不递归查数据库,而是查出所有 parentid 属于某集合的留言,
* 然后在 Java 中递归构建树(适用于层级不深的场景,如评论)
* @param parentIds 父ID列表
* @return 分页回复消息列表
*/
@Query("SELECT m FROM Message m WHERE m.parentid IN :parentIds")
List<Message> findRepliesByParentIds( @Param("parentIds") List<Integer> parentIds);
// 根据分页查询消息(不包括回复)
/**
* 根据分页查询消息(不包括回复)
* 不包含文章ID的消息
* @param pageable 分页信息
* @return 分页消息列表
*/
@Query("SELECT m FROM Message m WHERE m.articleid IS NULL ORDER BY m.createdAt DESC")
@Query("SELECT m FROM Message m WHERE m.articleid IS NULL AND m.parentid IS NULL ORDER BY m.createdAt ASC")
Page<Message> findAllMessages(PageRequest pageable);
/**
* 根据articleId查询所有回复消息
* @param articleId 文章ID
* @return 回复消息列表
*/
@Query("SELECT m FROM Message m WHERE m.articleid = :articleId AND m.parentid IS NOT NULL ORDER BY m.createdAt ASC")
List<Message> findAllRepliesByArticleId(@Param("articleId") Integer articleId);
/**
* 统计指定文章下的回复消息数量
* @param articleId 文章ID
@@ -87,8 +107,8 @@ public interface MessageRepository extends JpaRepository<Message, Integer> {
Integer countReplyByArticleId(@Param("articleId") Integer articleId);
/**
* 统计指定文章id parentid为空的回复消息数量
* @param articleId 文章ID
* 统计所有回复消息数量
* 不包含文章ID的消息
* @return 回复消息数量
*/
@Query("SELECT COUNT(m) FROM Message m WHERE m.articleid IS NULL AND m.parentid IS NULL")

View File

@@ -5,6 +5,8 @@ import com.qf.myafterprojecy.pojo.Nonsense;
import java.util.List;
import org.springframework.data.repository.query.Param;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
@@ -18,4 +20,13 @@ public interface NonsenseRepository extends JpaRepository<Nonsense, Integer> {
*/
@Query("SELECT n FROM Nonsense n WHERE n.status = :status")
List<Nonsense> findByStatus(@Param("status") Integer status);
/**
* 根据状态分页获取文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @param pageable 分页信息
* @return 返回包含文章列表的ResponseMessage对象
*/
@Query("SELECT n FROM Nonsense n WHERE n.status = :status")
Page<Nonsense> findPageByStatus(@Param("status") Integer status, PageRequest pageable);
}

View File

@@ -4,6 +4,8 @@ import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.dto.ArriclePageDto;
import com.qf.myafterprojecy.pojo.dto.ArticleDto;
import com.qf.myafterprojecy.pojo.dto.ArticleTreeDto;
import org.springframework.data.domain.Page;
import java.util.List;
@@ -19,13 +21,13 @@ public interface IArticleService {
* @param title 文章标题的一部分,用于模糊查询
* @return 返回符合查询条件的文章列表
*/
ResponseMessage<List<Article>> getArticlesByTitle(String title);
ResponseMessage<List<ArticleTreeDto>> getArticlesByTitle(String title);
/**
* 根据状态获取文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @return 返回包含文章列表的ResponseMessage对象
*/
ResponseMessage<List<Article>> getArticlesByStatus(Integer status);
ResponseMessage<List<ArticleTreeDto>> getArticlesByStatus(Integer status);
/**
* 获取文章数量
* @param status 文章状态0未发表 1已发表 2已删除
@@ -104,5 +106,5 @@ public interface IArticleService {
* @param size 每页大小
* @return 返回包含分页文章列表的ResponseMessage对象
*/
ResponseMessage<Page<Article>> getArticlesByStatusWithPagination(ArriclePageDto arriclePageDto);
ResponseMessage<List<ArticleTreeDto>> getArticlesByStatusWithPagination(ArriclePageDto arriclePageDto);
}

View File

@@ -3,6 +3,7 @@ package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import com.qf.myafterprojecy.pojo.dto.CategoryTreeDto;
import java.util.List;
@@ -19,7 +20,11 @@ public interface ICategoryService {
* @return 返回分类列表
*/
ResponseMessage<List<Category>> getAllCategories();
/**
* 获取分类树
* @return 返回分类树
*/
ResponseMessage<List<CategoryTreeDto>> getCategoryTree();
/**
* 保存新分类
* @param categoryDto 分类数据传输对象

View File

@@ -4,8 +4,7 @@ import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.pojo.dto.MessagePageDto;
import com.qf.myafterprojecy.pojo.dto.MessageTreeDto;
import java.util.List;
@@ -22,7 +21,7 @@ public interface IMessageService {
* @param id
* @return
*/
ResponseMessage<List<Message>> getMessagesByPage(MessagePageDto messagePageDto);
ResponseMessage<List<MessageTreeDto>> getMessagesByPage(MessagePageDto messagePageDto);
/**
* 获取回复消息条数 如果id为空获取文章id为空的消息条数
* @param articleId 文章id
@@ -68,7 +67,7 @@ public interface IMessageService {
* @param articleId 文章ID
* @return 消息列表
*/
ResponseMessage<List<Message>> getMessagesByArticleId(Integer articleId);
// ResponseMessage<List<Message>> getMessagesByArticleId(Integer articleId);
/**
* 查询所有父消息(根回复)

View File

@@ -3,6 +3,7 @@ package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Nonsense;
import com.qf.myafterprojecy.pojo.dto.NonsenseDto;
import com.qf.myafterprojecy.pojo.dto.NonsensePageDto;
import java.util.List;
@@ -25,7 +26,7 @@ public interface INonsenseService {
* @param status 状态0未发表 1已发表 2已删除
* @return 疯言疯语内容列表
*/
ResponseMessage<List<Nonsense>> getNonsenseByStatus(Integer status);
ResponseMessage<List<Nonsense>> getNonsenseByStatus(NonsensePageDto page);
/**
* 更新疯言疯语内容状态
@@ -48,7 +49,7 @@ public interface INonsenseService {
* @param nonsenseDto 疯言疯语内容数据传输对象
* @return 更新结果
*/
ResponseMessage<Nonsense> updateNonsense(Integer id, NonsenseDto nonsenseDto);
ResponseMessage<Nonsense> updateNonsense(NonsenseDto nonsenseDto);
/**
* 删除疯言疯语内容

View File

@@ -5,8 +5,10 @@ import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.Categoryattribute;
import com.qf.myafterprojecy.pojo.dto.ArriclePageDto;
import com.qf.myafterprojecy.pojo.dto.ArticleDto;
import com.qf.myafterprojecy.pojo.dto.ArticleTreeDto;
import com.qf.myafterprojecy.repository.ArticleRepository;
import com.qf.myafterprojecy.repository.CategoryAttributeRepository;
import com.qf.myafterprojecy.repository.MessageRepository;
import com.qf.myafterprojecy.service.IArticleService;
import org.slf4j.Logger;
@@ -33,7 +35,10 @@ public class ArticleService implements IArticleService {
@Autowired
private CategoryAttributeRepository categoryAttributeRepository;
@Autowired
private MessageRepository messageRepository;
/**
* 根据文章ID获取文章详情
* @param id 文章ID
@@ -95,7 +100,7 @@ public class ArticleService implements IArticleService {
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getArticlesByStatus(Integer status) {
public ResponseMessage<List<ArticleTreeDto>> getArticlesByStatus(Integer status) {
try {
if (status == null) {
return ResponseMessage.badRequest("文章状态不能为空");
@@ -104,7 +109,8 @@ public class ArticleService implements IArticleService {
return ResponseMessage.badRequest("文章状态值必须在0到2之间");
}
List<Article> articles = articleRepository.findByStatus(status);
return ResponseMessage.success(articles, "根据状态查询文章成功");
List<ArticleTreeDto> articleTreeDtos = getArticleTree(articles);
return ResponseMessage.success(articleTreeDtos, "根据状态查询文章成功");
} catch (Exception e) {
log.error("根据状态查询文章列表失败: {}", e.getMessage());
return ResponseMessage.error("根据状态查询文章列表失败");
@@ -128,7 +134,7 @@ public class ArticleService implements IArticleService {
@Override
@Transactional(readOnly = true)
public ResponseMessage<Page<Article>> getArticlesByStatusWithPagination(ArriclePageDto arriclePageDto) {
public ResponseMessage<List<ArticleTreeDto>> getArticlesByStatusWithPagination(ArriclePageDto arriclePageDto) {
if (arriclePageDto.getPagenum() == null || arriclePageDto.getPagenum() < 0) {
arriclePageDto.setPagenum(0); // 默认第一页
}
@@ -136,12 +142,7 @@ public class ArticleService implements IArticleService {
arriclePageDto.setPagesize(10); // 默认每页10条最大100条
}
try {
// 如果文章状态值是否在0到2之间则根据文章状态查询文章列表
if (arriclePageDto.getStatus() < 0 || arriclePageDto.getStatus() > 2) {
return ResponseMessage.badRequest("文章状态值必须在0到2之间");
}
PageRequest pageRequest = PageRequest.of(arriclePageDto.getPagenum(), arriclePageDto.getPagesize());
// 如果文章分类ID不为空则根据文章分类ID查询文章列表
if (arriclePageDto.getCategoryid() != null && arriclePageDto.getCategoryid() > 0) {
// 如果文章分类ID不为空则根据文章分类ID查询文章列表
List<Categoryattribute> categoryAttribute = categoryAttributeRepository.findByCategoryId(arriclePageDto.getCategoryid());
@@ -152,20 +153,25 @@ public class ArticleService implements IArticleService {
List<Integer> attributeids = categoryAttribute.stream().map(Categoryattribute::getAttributeid).collect(Collectors.toList());
// 根据分类ID对应的属性ID数组分页查询文章列表
Page<Article> articlePage = articleRepository.findByStatusWithPagination(arriclePageDto.getStatus(), attributeids, pageRequest);
return ResponseMessage.success(articlePage, "根据分类ID分页查询文章成功");
List<ArticleTreeDto> articleTreeDtos = getArticleTree(articlePage.getContent());
return ResponseMessage.page( articleTreeDtos, "根据分类ID分页查询文章成功", articlePage.getTotalPages());
}
// 如果文章属性ID不为空则根据文章属性ID查询文章列表
if (arriclePageDto.getAttributeid() != null && arriclePageDto.getAttributeid() > 0) {
Page<Article> articlePage = articleRepository.findByStatusWithPagination(arriclePageDto.getStatus(), arriclePageDto.getAttributeid(), pageRequest);
return ResponseMessage.success(articlePage, "根据属性ID分页查询文章成功");
List<ArticleTreeDto> articleTreeDtos = getArticleTree(articlePage.getContent());
return ResponseMessage.page( articleTreeDtos, "根据属性ID分页查询文章成功", articlePage.getTotalPages());
}
// 如果文章标题不为空则根据文章标题查询文章列表
if (arriclePageDto.getTitle() != null && !arriclePageDto.getTitle().isEmpty()) {
Page<Article> articlePage = articleRepository.findByStatusWithPagination(arriclePageDto.getStatus(), arriclePageDto.getTitle(), pageRequest);
return ResponseMessage.success(articlePage, "根据标题分页查询文章成功");
List<ArticleTreeDto> articleTreeDtos = getArticleTree(articlePage.getContent());
return ResponseMessage.page( articleTreeDtos, "根据标题分页查询文章成功", articlePage.getTotalPages());
}
Page<Article> articlePage = articleRepository.findByStatusWithPagination(arriclePageDto.getStatus(), pageRequest);
return ResponseMessage.success(articlePage, "根据状态分页查询文章成功");
List<ArticleTreeDto> articleTreeDtos = getArticleTree(articlePage.getContent());
return ResponseMessage.page( articleTreeDtos, "根据状态分页查询文章成功", articlePage.getTotalPages());
} catch (Exception e) {
log.error("根据状态分页查询文章列表失败: {}", e.getMessage());
return ResponseMessage.error("根据状态分页查询文章列表失败");
@@ -173,13 +179,14 @@ public class ArticleService implements IArticleService {
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getArticlesByTitle(String title) {
public ResponseMessage<List<ArticleTreeDto>> getArticlesByTitle(String title) {
try {
if (title == null || title.isEmpty()) {
return ResponseMessage.badRequest("文章标题不能为空");
}
List<Article> articles = articleRepository.findByTitle(title);
return ResponseMessage.success(articles, "根据标题查询文章成功");
List<ArticleTreeDto> articleTreeDtos = getArticleTree(articles);
return ResponseMessage.success(articleTreeDtos, "根据标题查询文章成功");
} catch (Exception e) {
log.error("根据标题查询文章列表失败: {}", e.getMessage());
return ResponseMessage.error("根据标题查询文章列表失败");
@@ -208,7 +215,6 @@ public class ArticleService implements IArticleService {
article.setUpdatedAt(LocalDateTime.now());
article.setImg(articleDto.getImg() != null ? articleDto.getImg() : "");
article.setStatus(articleDto.getStatus() != null ? articleDto.getStatus() : 0);
Article savedArticle = articleRepository.save(article);
return ResponseMessage.save(true, savedArticle);
} catch (DataAccessException e) {
@@ -225,6 +231,10 @@ public class ArticleService implements IArticleService {
.orElseThrow(() -> new RuntimeException("文章不存在"));
BeanUtils.copyProperties(articleDto, article);
article.setUpdatedAt(LocalDateTime.now());
Article updatedArticle = articleRepository.save(article);
@@ -359,4 +369,41 @@ public class ArticleService implements IArticleService {
return ResponseMessage.error("获取热门文章失败");
}
}
// =========补全文章树信息
public List<ArticleTreeDto> getArticleTree( List<Article> articlePage) {
try {
List<ArticleTreeDto> articleTreeDtos = articlePage.stream()
.map(article -> {
ArticleTreeDto dto = new ArticleTreeDto();
dto.setArticleid(article.getArticleid());
dto.setTitle(article.getTitle());
dto.setContent(article.getContent());
dto.setImg(article.getImg());
dto.setViewcount(article.getViewCount());
dto.setLikes(article.getLikes());
dto.setStatus(article.getStatus());
dto.setAttributeid(article.getAttributeid());
// 获取属性名称
Categoryattribute categoryAttribute = categoryAttributeRepository.findById(article.getAttributeid()).orElse(null);
if (categoryAttribute != null) {
dto.setAttributename(categoryAttribute.getAttributename());
}
dto.setMarkdownscontent(article.getMarkdownscontent());
// 设置用户信息Article实体中没有这些字段暂时设置为null
dto.setUserid(null);
dto.setUsername(null);
// 获取评论数
Integer commentcount = messageRepository.countReplyByArticleId(article.getArticleid());
dto.setCommentcount(commentcount);
dto.setCreatetime(article.getCreatedAt());
dto.setUpdatetime(article.getUpdatedAt());
return dto;
})
.collect(Collectors.toList());
return articleTreeDtos;
} catch (DataAccessException e) {
log.error("获取文章树失败: {}", e.getMessage());
return null;
}
}
}

View File

@@ -3,6 +3,8 @@ package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import com.qf.myafterprojecy.pojo.dto.CategoryTreeDto;
import com.qf.myafterprojecy.repository.CategoryAttributeRepository;
import com.qf.myafterprojecy.repository.CategoryRepository;
import com.qf.myafterprojecy.service.ICategoryService;
@@ -15,7 +17,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class CategoryService implements ICategoryService {
@@ -24,6 +28,8 @@ public class CategoryService implements ICategoryService {
@Autowired
private CategoryRepository categoryRepository;
@Autowired
private CategoryAttributeRepository categoryAttributeRepository;
@Override
@Transactional(readOnly = true)
@@ -152,4 +158,35 @@ public class CategoryService implements ICategoryService {
return ResponseMessage.error("搜索分类失败");
}
}
// 获取分类树
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<CategoryTreeDto>> getCategoryTree() {
try {
List<Category> categories = categoryRepository.findAll();
// System.out.println(categories);
List<CategoryTreeDto> tree = buildCategoryTree(categories);
return ResponseMessage.success(tree, "获取分类树成功");
} catch (DataAccessException e) {
log.error("获取分类树失败: {}", e.getMessage());
return ResponseMessage.error("获取分类树失败");
}
}
// 递归构建分类树
private List<CategoryTreeDto> buildCategoryTree(List<Category> categories) {
List<CategoryTreeDto> tree = new ArrayList<>();
for (Category category : categories) {
tree.add(buildCategoryTreeNode(category));
}
return tree;
}
// 构建分类树节点
private CategoryTreeDto buildCategoryTreeNode(Category category) {
CategoryTreeDto node = new CategoryTreeDto();
node.setId(category.getCategoryid());
node.setName(category.getTypename());
node.setChildren(categoryAttributeRepository.findByCategoryId(category.getCategoryid()));
return node;
}
}

View File

@@ -4,6 +4,7 @@ import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.pojo.dto.MessagePageDto;
import com.qf.myafterprojecy.pojo.dto.MessageTreeDto;
import com.qf.myafterprojecy.repository.MessageRepository;
import com.qf.myafterprojecy.service.IMessageService;
@@ -11,16 +12,20 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties.Pageable;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.transaction.annotation.Transactional;
@@ -140,22 +145,23 @@ public class MessageService implements IMessageService {
}
}
@Override
public ResponseMessage<List<Message>> getMessagesByArticleId(Integer articleId) {
if (articleId == null || articleId <= 0) {
logger.warn("根据文章ID查询消息时ID无效: {}", articleId);
return ResponseMessage.badRequest("文章ID无效");
}
// @Override
// public ResponseMessage<List<Message>> getMessagesByArticleId(Integer
// articleId) {
// if (articleId == null || articleId <= 0) {
// logger.warn("根据文章ID查询消息时ID无效: {}", articleId);
// return ResponseMessage.badRequest("文章ID无效");
// }
try {
logger.info("根据文章ID查询消息: {}", articleId);
List<Message> messages = messageRepository.findByArticleid(articleId);
return ResponseMessage.success(messages, "查询成功");
} catch (DataAccessException e) {
logger.error("根据文章ID查询消息失败: {}", articleId, e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
// try {
// logger.info("根据文章ID查询消息: {}", articleId);
// List<Message> messages = messageRepository.findByArticleid(articleId);
// return ResponseMessage.success(messages, "查询成功");
// } catch (DataAccessException e) {
// logger.error("根据文章ID查询消息失败: {}", articleId, e);
// return ResponseMessage.error("查询消息失败:" + e.getMessage());
// }
// }
@Override
public ResponseMessage<List<Message>> getRootMessages() {
@@ -235,9 +241,8 @@ public class MessageService implements IMessageService {
}
}
@Override
public ResponseMessage<List<Message>> getMessagesByPage(MessagePageDto messagePageDto) {
public ResponseMessage<List<MessageTreeDto>> getMessagesByPage(MessagePageDto messagePageDto) {
if (messagePageDto == null) {
logger.warn("分页查询消息时参数为空");
return ResponseMessage.badRequest("分页参数不能为空");
@@ -251,25 +256,20 @@ public class MessageService implements IMessageService {
return ResponseMessage.badRequest("每页数量无效");
}
try {
// 如何文章id为空默认根据分页基础信息查询消息
// // 如何文章id为空默认根据分页基础信息查询消息
PageRequest pageable = PageRequest.of(messagePageDto.getPageNum(), messagePageDto.getPageSize());
if (messagePageDto.getArticleid() != null && messagePageDto.getArticleid() > 0) {
// 如果文章ID存在根据文章ID查询消息
Page<Message> messagePage = messageRepository.findByArticleId(messagePageDto.getArticleid(), pageable);
return ResponseMessage.success(messagePage.getContent(), "查询成功");
}
// 如果文章ID不存在根据分页基础信息查询所有消息
Page<Message> messagePage = messageRepository.findAllMessages(pageable);
return ResponseMessage.success(messagePage.getContent(), "查询成功");
logger.info("根据分页基础信息查询所有消息: {}", messagePageDto);
return buildMessageTreeDto(messagePageDto.getArticleid(), pageable);
} catch (DataAccessException e) {
logger.error("分页查询消息失败: {}", messagePageDto, e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
// 获取回复消息条数 如果id为空获取文章id为空的消息条数
@Override
public ResponseMessage<Integer> getMessageCountByArticleId(Integer articleId) {
try {
logger.info("获取文章回复数量: {}", articleId);
if (articleId == null || articleId <= 0) {
@@ -283,4 +283,145 @@ public class MessageService implements IMessageService {
return ResponseMessage.error("查询回复数量失败:" + e.getMessage());
}
}
/**
* 构建消息树DTO
*
* @param articleId 文章ID (可选)
* @param pageable 分页信息
* @return 消息树DTO列表
*/
public ResponseMessage<List<MessageTreeDto>> buildMessageTreeDto(Integer articleId, PageRequest pageable) {
// 1. 获取主留言(分页)
Page<Message> messagePage = null;
if (articleId == null || articleId <= 0) {
// 如果文章ID不存在根据分页基础信息查询所有消息
logger.info("根据分页基础信息查询所有消息: {}", pageable);
messagePage = messageRepository.findAllMessages(pageable);
logger.info("根据分页基础信息查询所有消息: {}", messagePage);
} else {
messagePage = messageRepository.findByArticleId(articleId, pageable);
logger.info("根据文章ID分页查询消息: {}", messagePage);
}
List<Message> mainMessages = messagePage.getContent();
int totalPages = messagePage.getTotalPages();
// 如果没有消息,直接返回空列表
if (mainMessages.isEmpty()) {
logger.info("文章ID {} 下没有消息", articleId);
return ResponseMessage.success(null);
}
// 2. 获取所有子回复(一次性查询,减少数据库访问)
// 注意:这里不递归查数据库,而是查出所有 parentid 属于某集合的留言,
// 然后在 Java 中递归构建树(适用于层级不深的场景,如评论)、
List<Message> allReplies = new ArrayList<>();
if (articleId == null || articleId <= 0) {
// 如果文章ID不存在根据分页基础信息查询所有消息
// 从主留言中提取所有父ID
List<Integer> parentIds = mainMessages.stream().map(Message::getMessageid).collect(Collectors.toList());
logger.info("根据父ID列表查询所有子回复: {}", parentIds);
allReplies = messageRepository.findRepliesByParentIds(parentIds);
} else {
allReplies = messageRepository.findAllRepliesByArticleId(articleId);
logger.info("根据文章ID查询所有子回复: {}", articleId);
}
// 3. 合并所有消息(主留言 + 子回复)
List<Message> allMessages = new ArrayList<>(mainMessages);
allMessages.addAll(allReplies);
// 4. 构建消息ID到消息对象的映射
Map<Integer, Message> messageMap = new HashMap<>();
for (Message msg : allMessages) {
messageMap.put(msg.getMessageid(), msg);
}
// 5. 构建消息树
Map<Integer, MessageTreeDto> treeNodeMap = new HashMap<>();
List<MessageTreeDto> roots = new ArrayList<>();
// 5.1 先构建所有节点
for (Message msg : allMessages) {
MessageTreeDto node = convertToTreeDto(msg);
treeNodeMap.put(msg.getMessageid(), node);
}
// 5.2 构建父子关系
for (Message msg : allMessages) {
MessageTreeDto currentNode = treeNodeMap.get(msg.getMessageid());
Integer parentId = msg.getParentid();
// 处理回复信息
Integer replyId = msg.getReplyid();
// 如果是回复消息,获取被回复者的昵称
if (replyId != null && replyId > 0) {
Message replyMsg = messageMap.get(replyId);
if (replyMsg != null) {
// 建议在MessageTreeDto中添加replyToNickname字段
currentNode.setReplyToNickname(replyMsg.getNickname());
// 而不是修改发送者昵称currentNode.setNickname("@" + replyMsg.getNickname());
}
}
if (parentId == null || parentId <= 0) {
// 主留言,直接添加到根列表
roots.add(currentNode);
} else {
// 子回复添加到父节点的children列表
MessageTreeDto parentNode = treeNodeMap.get(parentId);
if (parentNode != null) {
parentNode.getChildren().add(currentNode);
}
}
}
// 6. 计算每个节点的回复数
for (MessageTreeDto node : treeNodeMap.values()) {
int replyCount = calculateReplyCount(node);
node.setReplyCount(replyCount);
}
// 7. 排序:根节点按时间倒序(最新的在前面)
roots.sort(Comparator.comparing(MessageTreeDto::getCreatedAt).reversed());
// 8. 对每个根节点的子节点按时间排序
for (MessageTreeDto root : roots) {
sortChildrenByTime(root);
}
return ResponseMessage.page(roots, "查询成功", totalPages);
}
/**
* 将Message转换为MessageTreeDto
*/
private MessageTreeDto convertToTreeDto(Message msg) {
MessageTreeDto dto = new MessageTreeDto();
// 使用Spring的BeanUtils进行属性复制
BeanUtils.copyProperties(msg, dto);
// 初始化children列表
dto.setChildren(new ArrayList<>());
dto.setReplyCount(0);
return dto;
}
/**
* 计算节点的回复总数(包括所有子回复)
*/
private int calculateReplyCount(MessageTreeDto node) {
int count = node.getChildren().size();
for (MessageTreeDto child : node.getChildren()) {
count += calculateReplyCount(child);
}
return count;
}
/**
* 递归按时间排序子节点
*/
private void sortChildrenByTime(MessageTreeDto node) {
node.getChildren().sort(Comparator.comparing(MessageTreeDto::getCreatedAt));
for (MessageTreeDto child : node.getChildren()) {
sortChildrenByTime(child);
}
}
}

View File

@@ -3,6 +3,7 @@ package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Nonsense;
import com.qf.myafterprojecy.pojo.dto.NonsenseDto;
import com.qf.myafterprojecy.pojo.dto.NonsensePageDto;
import com.qf.myafterprojecy.repository.NonsenseRepository;
import com.qf.myafterprojecy.service.INonsenseService;
@@ -10,7 +11,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties.Pageable;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -48,38 +52,43 @@ public class NonsenseService implements INonsenseService {
if (nonsenseOptional.isPresent()) {
Nonsense nonsense = nonsenseOptional.get();
logger.info("获取ID为{}的疯言疯语内容成功,状态: {}", id, nonsense.getStatus());
return new ResponseMessage<>(200, "获取成功", nonsense, true);
return ResponseMessage.success(nonsense, "获取成功");
} else {
logger.warn("未找到ID为{}的疯言疯语内容", id);
return new ResponseMessage<>(404, "未找到指定疯言疯语内容", null, false);
return ResponseMessage.error(404, "未找到指定疯言疯语内容");
}
} catch (DataAccessException e) {
logger.error("根据ID查询疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "数据库查询异常", null, false);
return ResponseMessage.error(500, "数据库查询异常");
} catch (Exception e) {
logger.error("根据ID查询疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
return ResponseMessage.error(500, "服务器内部错误");
}
}
@Override
public ResponseMessage<List<Nonsense>> getNonsenseByStatus(Integer status) {
public ResponseMessage<List<Nonsense>> getNonsenseByStatus(NonsensePageDto page) {
try {
// 验证状态值是否有效
if (status < 0 || status > 2) {
logger.warn("无效的状态值: {}", status);
return new ResponseMessage<>(400, "无效的状态值必须是0(未发表)、1(已发表)或2(已删除)", null, false);
if (page.getStatus() < 0 || page.getStatus() > 2) {
logger.warn("无效的状态值: {}", page.getStatus());
return ResponseMessage.error(400, "无效的状态值必须是0(未发表)、1(已发表)或2(已删除)");
}
// 分页查询
PageRequest pageable = PageRequest.of(page.getPageNum(), page.getPageSize());
Page<Nonsense> pageResult = nonsenseRepository.findPageByStatus(page.getStatus(), pageable);
// 根据状态过滤已发表(1)的内容
List<Nonsense> filteredNonsense = pageResult.getContent();
List<Nonsense> nonsenseList = nonsenseRepository.findByStatus(status);
// 根据状态过滤
return new ResponseMessage<>(200, "获取成功", nonsenseList, true);
return ResponseMessage.page(filteredNonsense, "获取成功", pageResult.getTotalPages());
} catch (DataAccessException e) {
logger.error("根据状态获取疯言疯语内容失败,状态: {}", status, e);
return new ResponseMessage<>(500, "数据库查询异常", null, false);
logger.error("根据状态获取疯言疯语内容失败,状态: {}", page.getStatus(), e);
return ResponseMessage.error(500, "数据库查询异常");
} catch (Exception e) {
logger.error("根据状态获取疯言疯语内容失败,状态: {}", status, e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
logger.error("根据状态获取疯言疯语内容失败,状态: {}", page.getStatus(), e);
return ResponseMessage.error(500, "服务器内部错误");
}
}
@@ -90,7 +99,7 @@ public class NonsenseService implements INonsenseService {
// 验证状态值是否有效
if (status < 0 || status > 2) {
logger.warn("无效的状态值: {} 用于ID为{}的疯言疯语内容", status, id);
return new ResponseMessage<>(400, "无效的状态值必须是0(未发表)、1(已发表)或2(已删除)", null, false);
return ResponseMessage.error(400, "无效的状态值必须是0(未发表)、1(已发表)或2(已删除)");
}
Optional<Nonsense> nonsenseOptional = nonsenseRepository.findById(id);
@@ -101,17 +110,17 @@ public class NonsenseService implements INonsenseService {
Nonsense updatedNonsense = nonsenseRepository.save(nonsense);
logger.info("更新疯言疯语内容状态成功ID: {}, 旧状态: {}, 新状态: {}", id, oldStatus, status);
return new ResponseMessage<>(200, "状态更新成功", updatedNonsense, true);
return ResponseMessage.success(updatedNonsense, "状态更新成功");
} else {
logger.warn("更新状态失败未找到ID为{}的疯言疯语内容", id);
return new ResponseMessage<>(404, "未找到指定疯言疯语内容", null, false);
return ResponseMessage.error(404, "未找到指定疯言疯语内容");
}
} catch (DataAccessException e) {
logger.error("更新疯言疯语内容状态失败ID: {}, 状态: {}", id, status, e);
return new ResponseMessage<>(500, "数据库操作异常", null, false);
return ResponseMessage.error(500, "数据库操作异常");
} catch (Exception e) {
logger.error("更新疯言疯语内容状态失败ID: {}, 状态: {}", id, status, e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
return ResponseMessage.error(500, "服务器内部错误");
}
}
@@ -134,42 +143,42 @@ public class NonsenseService implements INonsenseService {
Nonsense savedNonsense = nonsenseRepository.save(nonsense);
logger.info("保存疯言疯语内容成功ID: {}, 状态: {}", savedNonsense.getId(), savedNonsense.getStatus());
return new ResponseMessage<>(200, "保存成功", savedNonsense, true);
return ResponseMessage.success(savedNonsense, "保存成功");
} catch (DataAccessException e) {
logger.error("保存疯言疯语内容失败", e);
return new ResponseMessage<>(500, "数据库操作异常", null, false);
return ResponseMessage.error(500, "数据库操作异常");
} catch (Exception e) {
logger.error("保存疯言疯语内容失败", e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
return ResponseMessage.error(500, "服务器内部错误");
}
}
@Override
@Transactional
public ResponseMessage<Nonsense> updateNonsense(Integer id, NonsenseDto nonsenseDto) {
public ResponseMessage<Nonsense> updateNonsense(NonsenseDto nonsenseDto) {
try {
Optional<Nonsense> nonsenseOptional = nonsenseRepository.findById(id);
Optional<Nonsense> nonsenseOptional = nonsenseRepository.findById(nonsenseDto.getId());
if (nonsenseOptional.isPresent()) {
Nonsense nonsense = nonsenseOptional.get();
// 只有当DTO中提供了status值时才更新
if (nonsenseDto.getStatus() != null) {
logger.info("更新疯言疯语内容状态ID: {}, 新状态: {}", id, nonsenseDto.getStatus());
logger.info("更新疯言疯语内容状态ID: {}, 新状态: {}", nonsenseDto.getId(), nonsenseDto.getStatus());
}
BeanUtils.copyProperties(nonsenseDto, nonsense, "id");
Nonsense updatedNonsense = nonsenseRepository.save(nonsense);
logger.info("更新疯言疯语内容成功ID: {}, 当前状态: {}", id, updatedNonsense.getStatus());
return new ResponseMessage<>(200, "更新成功", updatedNonsense, true);
logger.info("更新疯言疯语内容成功ID: {}, 当前状态: {}", nonsenseDto.getId(), updatedNonsense.getStatus());
return ResponseMessage.success(updatedNonsense, "更新成功");
} else {
logger.warn("更新失败未找到ID为{}的疯言疯语内容", id);
return new ResponseMessage<>(404, "未找到指定疯言疯语内容", null, false);
logger.warn("更新失败未找到ID为{}的疯言疯语内容", nonsenseDto.getId());
return ResponseMessage.error(404, "未找到指定疯言疯语内容");
}
} catch (DataAccessException e) {
logger.error("更新疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "数据库操作异常", null, false);
logger.error("更新疯言疯语内容失败ID: {}", nonsenseDto.getId(), e);
return ResponseMessage.error(500, "数据库操作异常");
} catch (Exception e) {
logger.error("更新疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
logger.error("更新疯言疯语内容失败ID: {}", nonsenseDto.getId(), e);
return ResponseMessage.error(500, "服务器内部错误");
}
}
@@ -186,17 +195,17 @@ public class NonsenseService implements INonsenseService {
// 物理删除
// nonsenseRepository.deleteById(id);
logger.info("删除疯言疯语内容成功ID: {}", id);
return new ResponseMessage<>(200, "删除成功", true, true);
return ResponseMessage.success(true, "删除成功");
} else {
logger.warn("删除失败未找到ID为{}的疯言疯语内容", id);
return new ResponseMessage<>(404, "未找到指定疯言疯语内容", false, false);
return ResponseMessage.error(404, "未找到指定疯言疯语内容");
}
} catch (DataAccessException e) {
logger.error("删除疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "数据库操作异常", false, false);
return ResponseMessage.error(500, "数据库操作异常");
} catch (Exception e) {
logger.error("删除疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "服务器内部错误", false, false);
return ResponseMessage.error(500, "服务器内部错误");
}
}
}

View File

@@ -18,13 +18,13 @@ import java.util.function.Function;
@Component
public class JwtUtils {
@Value("${jwt.secret:default_secret_key_for_development}")
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration:86400000}")
@Value("${jwt.expiration}")
private long expiration;
@Value("${jwt.token-prefix:Bearer}")
@Value("${jwt.token-prefix}")
private String tokenPrefix;
/**

View File

@@ -9,11 +9,14 @@ server.port=7070
# ====================================================================
# 数据库与JPA配置 - 生产用
# ====================================================================
spring.datasource.url=${DB_URL:jdbc:mysql://mysql:3306/webproject?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai}
spring.datasource.username=${DB_USERNAME:root}
spring.datasource.password=${DB_PASSWORD:root}
# spring.datasource.url=${DB_URL:jdbc:mysql://mysql:3306/webproject?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai}
# spring.datasource.username=${DB_USERNAME:root}
# spring.datasource.password=${DB_PASSWORD:root}
# spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/webproject?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接池配置(生产环境优化版)
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
@@ -24,13 +27,13 @@ spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.pool-name=WebProjectHikariCP
# JPA配置生产环境禁用自动DDL避免意外修改表结构
spring.jpa.hibernate.ddl-auto=create
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=false
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.open-in-view=false
# JPA性能优化配置
# JPA性能优化配置(生产环境开启批量处理)
spring.jpa.properties.hibernate.jdbc.batch_size=30
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
@@ -38,8 +41,8 @@ spring.jpa.properties.hibernate.order_updates=true
# ====================================================================
# JWT 配置 - 生产用(敏感信息从环境变量读取)
# ====================================================================
jwt.secret=${JWT_SECRET:}
jwt.expiration=${JWT_EXPIRATION:86400000}
jwt.secret=${JWT_SECRET:6a1f4832-29bf-4ac5-9408-a8813b6f2dfe}
jwt.expiration=${JWT_EXPIRATION:3600000}
jwt.header=Authorization
jwt.token-prefix=Bearer
@@ -47,7 +50,8 @@ jwt.token-prefix=Bearer
# 安全与CORS配置 - 生产用
# ====================================================================
# CORS配置生产环境限制为具体域名
cors.allowed-origins=http://qf1121.top,https://qf1121.top,http://www.qf1121.top,https://www.qf1121.top
# cors.allowed-origins=http://qf1121.top,https://qf1121.top,http://www.qf1121.top,https://www.qf1121.top
cors.allowed-origins=http://localhost:3000,http://localhost:8080,http://localhost:5173
cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
cors.allowed-headers=*
cors.allow-credentials=true

View File

@@ -3,8 +3,7 @@
# ====================================================================
# 环境激活配置
# 说明:默认激活开发环境,生产环境部署时应通过命令行参数或环境变量覆盖
spring.profiles.active=dev
server.port=7070
spring.profiles.active=prod
# 应用名称(通用配置)
spring.application.name=web_project