002 递归评论 mongodb websocket消息推送,递归评论系统下的MongoDB与WebSocket消息推送研究

马肤
摘要:本文介绍了使用递归评论、MongoDB数据库和WebSocket消息推送技术相结合的方式。通过递归评论实现用户之间的交互评论功能,MongoDB作为数据存储的优选方案,而WebSocket则用于实时消息推送,提升用户体验。这种结合方式在Web应用中展现出强大的实时互动性能,特别是在社交网络、在线社区等场景中表现突出。

文章目录

  • 商品评论
    • CommentController.java
    • Comment.java
    • CommentServiceImpl.java
    • CommentRepository.java
    • CommentService.java
    • WebSocketConfig.java
    • WebSocketProcess.java
    • application.yaml
    • productReview.html
    • index.html
    • index.js
    • index.css
    • 订单评论
      • EvaluateMapper.xml
      • EvaluateMapper.java
      • EvaluateController.java
      • Evaluate.java
      • EvaluateServiceImpl.java
      • IEvaluateService.java
      • R.java
      • orderReview.css
      • orderReview.html
      • orderReview.js
      • mongodb
      • pom.xml
      • 递归评论
          • 前端
          • 后端
          • 前后端

            商品评论

            CommentController.java

            package com.fshop.controller;
            import com.fshop.entity.Comment;
            import com.fshop.service.CommentService;
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.web.bind.annotation.*;
            import java.util.List;
            @RestController
            @RequestMapping("/productReview/comments")
            public class CommentController {
                @Autowired
                private CommentService commentService;
                // 添加根据fruitId获取评论的映射方法
                @GetMapping("/byFruitId/{fruitId}")
                public List getCommentsByFruitId(@PathVariable Integer fruitId) {
                    return commentService.getCommentsByFruitId(fruitId);
                }
                @GetMapping
                public List getAllComments() {
                    return commentService.getAllComments();
                }
                @PostMapping
                public Comment addComment(@RequestBody Comment comment) {
                    return commentService.addComment(comment);
                }
                @PostMapping("/{id}/replies")
                public Comment addReply(@PathVariable String id, @RequestBody Comment.Reply reply) {
                    return commentService.addReply(id, reply);
                }
                @PutMapping("/{id}")
                public Comment updateComment(@PathVariable String id, @RequestBody Comment comment) {
                    return commentService.updateComment(id, comment);
                }
                @DeleteMapping("/{id}")
                public void deleteComment(@PathVariable String id) {
                    commentService.deleteComment(id);
                }
            }
            

            Comment.java

            package com.fshop.entity;
            import lombok.Data;
            import org.springframework.data.annotation.Id;
            import org.springframework.data.mongodb.core.mapping.Document;
            import java.util.ArrayList;
            import java.util.List;
            @Document(collection = "comments")
            @Data
            public class Comment {
                @Id
                private String id;
                private int evaluateId;
                private int fruitId;
                private int score;
                private int status;
                private OriginalPoster originalPoster;
                private List replies = new ArrayList();
                @Data
                public static class OriginalPoster {
                    private int userId;
                    private String content;
                    private String postedAt;
                }
                @Data
                public static class Reply {
                    @Id
                    private String id;
                    private int userId;
                    private String content;
                    private String postedAt;
                    private String parentId;
                    private List replies = new ArrayList();
                }
            }
            

            CommentServiceImpl.java

            package com.fshop.service.impl;
            import com.fshop.entity.Comment;
            import com.fshop.service.CommentRepository;
            import com.fshop.service.CommentService;
            import com.fshop.websocket2.WebSocketProcess;
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.stereotype.Service;
            import java.util.ArrayList;
            import java.util.List;
            import java.util.UUID;
            @Service
            public class CommentServiceImpl implements CommentService {
                @Autowired
                private CommentRepository commentRepository;
                @Autowired
                private WebSocketProcess websocketProcess;
                @Override
                public List getAllComments() {
                    return commentRepository.findAll();
                }
                @Override
                public List getCommentsByFruitId(Integer fruitId) {
                    return commentRepository.findByFruitId(fruitId);
                }
                @Override
                public Comment addComment(Comment comment) {
                    comment.setId(UUID.randomUUID().toString()); // 生成随机的 `_id`
                    comment.setReplies(new ArrayList()); // 确保 `replies` 是一个空列表
                    return commentRepository.save(comment);
                }
                @Override
                public Comment updateComment(String id, Comment comment) {
                    comment.setId(id);
                    return commentRepository.save(comment);
                }
                @Override
                public void deleteComment(String id) {
                    commentRepository.deleteById(id);
                }
                @Override
                public Comment addReply(String id, Comment.Reply reply) {
                    reply.setId(UUID.randomUUID().toString()); // 生成随机的 `_id` 为回复
                    // 查找目标评论或回复
                    Comment targetComment = findCommentById(id);
                    if (targetComment != null) {
                        // 如果找到了目标评论,添加回复
                        addReplyToCommentOrReplies(targetComment, reply);
                        websocketProcess.sendMsg(reply.getUserId(),reply.getContent());
                        return commentRepository.save(targetComment);
                    } else {
                        // 否则,递归查找所有评论的嵌套回复
                        List allComments = commentRepository.findAll();
                        for (Comment comment : allComments) {
                            if (addReplyToNestedReplies(comment.getReplies(), id, reply)) {
                                websocketProcess.sendMsg(reply.getUserId(), reply.getContent());
                                return commentRepository.save(comment);
                            }
                        }
                        throw new RuntimeException("Comment not found");
                    }
                }
                private Comment findCommentById(String id) {
                    return commentRepository.findById(id).orElse(null);
                }
                private boolean addReplyToNestedReplies(List replies, String parentId, Comment.Reply replyToAdd) {
                    for (Comment.Reply reply : replies) {
                        if (reply.getId().equals(parentId)) {
                            reply.getReplies().add(replyToAdd);
                            return true;
                        } else {
                            if (addReplyToNestedReplies(reply.getReplies(), parentId, replyToAdd)) {
                                return true;
                            }
                        }
                    }
                    return false;
                }
                private void addReplyToCommentOrReplies(Comment comment, Comment.Reply reply) {
                    if (comment.getId().equals(reply.getParentId())) {
                        comment.getReplies().add(reply);
                    } else {
                        addReplyToNestedReplies(comment.getReplies(), reply.getParentId(), reply);
                    }
                }
            }
            

            CommentRepository.java

            package com.fshop.service;
            import com.fshop.entity.Comment;
            import org.springframework.data.mongodb.repository.MongoRepository;
            import org.springframework.stereotype.Repository;
            import java.util.List;
            @Repository
            public interface CommentRepository extends MongoRepository {
                // 添加根据fruitId查找评论的方法
                List findByFruitId(Integer fruitId);
            }
            

            CommentService.java

            package com.fshop.service;
            
            import com.fshop.entity.Comment;
            import java.util.List;
            public interface CommentService {
                List getAllComments();
                Comment addComment(Comment comment);
                Comment updateComment(String id, Comment comment);
                void deleteComment(String id);
            //    Comment findCommentById(String id);
                Comment addReply(String id, Comment.Reply reply);
                // 添加根据fruitId获取评论列表的方法声明
                List getCommentsByFruitId(Integer fruitId);
            }
            

            WebSocketConfig.java

            package com.fshop.websocket2;
            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.web.socket.server.standard.ServerEndpointExporter;
            @Configuration
            public class WebSocketConfig {
                @Bean
                public ServerEndpointExporter serverEndpointExporter(){
                    return new ServerEndpointExporter();
                }
            }
            

            WebSocketProcess.java

            package com.fshop.websocket2;
            
            /**
             * 该类封装了 客户端与服务器端的Websocket 通讯的
             *  (1) 连接对象的管理   ConcurrentHashMap
             *  (2) 事件监听      @OnOpen ,  @OnMessage,  @OnClose , @OnError
             *  (3) 服务器向 (所有/单个)客户端 发送消息
             */
            
            import org.springframework.stereotype.Component;
            import javax.websocket.OnClose;
            import javax.websocket.OnMessage;
            import javax.websocket.OnOpen;
            import javax.websocket.Session;
            import javax.websocket.server.PathParam;
            import javax.websocket.server.ServerEndpoint;
            import java.io.IOException;
            import java.util.Map;
            import java.util.Set;
            import java.util.concurrent.ConcurrentHashMap;
            /**
             * 1. manage client and sever socket object(concurrentHashMap)
             * 2. event trigger :
             *      receive client connect : onopen
             *      receive message from client : onmessage
             *      client socket close :onclose
             *
             * 3. server  send message to client
             */
            @Component
            @ServerEndpoint(value = "/testWebSocket/{id}")
            public class WebSocketProcess {
                private static ConcurrentHashMap map = new ConcurrentHashMap();
                private Session session;
                @OnOpen
                public void onOpen(Session session, @PathParam("id") Integer clientId){
                    this.session = session;
                    map.put(clientId,this);
                    System.out.println("server get a socket from client :"  + clientId);
                }
                //    receive message from client : onmessage
                @OnMessage
                public void onMessage(String message, @PathParam("id") Integer clientId){
                    System.out.println("server get message from client id:" + clientId+", and message is :" + message);
                }
                @OnClose
                public void onClose(Session session, @PathParam("id") Integer clientId){
                    map.remove(clientId);
                }
                //    server  send message to client
                public  void sendMsg(Integer clientId,String message)  {
                    WebSocketProcess socket =  map.get(clientId);
                    if(socket!=null){
                        if(socket.session.isOpen()){
                            try {
                                socket.session.getBasicRemote().sendText(message);
                                System.out.println("server has send message to  client :"+clientId +", and message is:"+ message);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }else{
                            System.out.println("this client "+clientId +" socket has closed");
                        }
                    }else{
                        System.out.println("this client "+clientId +" socket has exit");
                    }
                }
                public  void sendMsg(String message) throws IOException {
                    Set entrySet = map.entrySet();
                    for(Map.Entry entry: entrySet ){
                        Integer clientId = entry.getKey();
                        WebSocketProcess socket = entry.getValue();
                        if(socket!=null){
                            if(socket.session.isOpen()){
                                socket.session.getBasicRemote().sendText(message);
                            }else{
                                System.out.println("this client "+clientId +" socket has closed");
                            }
                        }else{
                            System.out.println("this client "+clientId +" socket has exit");
                        }
                    }
            
                }
            }
            

            application.yaml

            spring:
              datasource:
                druid:
                  driver-class-name: com.mysql.cj.jdbc.Driver
                  url: jdbc:mysql://localhost:3306/fshop_app?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
                  username: dev
                  password: 123456
                  initial-size: 5 # 初始化连接池大小
                  max-active: 20  # 最大连接数
                  min-idle: 10    # 最小连接数
                  max-wait: 60000 # 超时等待时间
                  min-evictable-idle-time-millis: 600000  # 连接在连接池中的最小生存时间
                  max-evictable-idle-time-millis: 900000  # 连接在连接池中的最大生存时间
                  time-between-eviction-runs-millis: 2000 # 配置间隔多久进行一次检测,检测需要关闭的空闲连接
                  test-while-idle: true # 从连接池中获取连接时,当连接空闲时间大于timeBetweenEvictionRunsMillis时检查连接有效性
                  phy-max-use-count: 1000 # 配置一个连接最大使用次数,避免长时间使用相同连接造成服务器端负载不均衡
            spring:
              data:
                mongodb:
                  uri: mongodb://abc:123456@localhost:27017/commentDB
            

            productReview.html

            
            
            
              Fruit Comments
              
              
                body {
                  font-family: Arial, sans-serif;
                  color: #333;
                }
                .comment, .reply {
                  margin-bottom: 20px;
                  padding: 15px;
                  border: 1px solid #ddd;
                  border-radius: 8px;
                  background-color: #fff;
                  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                }
                .reply {
                  margin-left: 60px; /* 增加缩进以更好地显示嵌套回复 */
                }
                .reply-form {
                  margin-top: 10px;
                  margin-left: 40px;
                }
                .reply-form textarea {
                  width: calc(100% - 20px); /* 留出一些空间 */
                  padding: 10px;
                  border: 1px solid #ddd;
                  border-radius: 5px;
                  resize: vertical;
                }
                .reply-button {
                  cursor: pointer;
                  color: #007BFF; /* 蓝色 */
                  text-decoration: underline;
                  font-weight: bold; /* 加粗字体 */
                }
                .reply-button:hover {
                  color: #0056b3; /* 鼠标悬停颜色变深 */
                }
                button[type="submit"] {
                  padding: 5px 10px;
                  background-color: #007BFF;
                  color: white;
                  border: none;
                  border-radius: 5px;
                  cursor: pointer;
                }
                button[type="submit"]:hover {
                  background-color: #0056b3; /* 鼠标悬停颜色变深 */
                }
              
            
            
            

            Comments

            // 从URL参数中获取fruitId function getQueryParameterByName(name, url) { if (!url) url = window.location.href; name = name.replace(/[\[\]]/g, '\\$&'); var regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)'), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, ' ')); } $(document).ready(function() { // // 获取fruitId // var fruitId = getParameterByName('fruitId'); // if (!fruitId) { // alert('No fruitId provided!'); // return; // } //解析userId function getUserIdFromToken(token) { if (!token) { return null; } const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); try { const decodedPayload = JSON.parse(atob(base64)); return decodedPayload.userId; // 假设JWT的payload中包含userId字段 } catch (e) { console.error('Error decoding JWT payload', e); return null; } } // 使用示例 const token = localStorage.getItem('token'); // 假设你将token保存在localStorage中 const userId = getUserIdFromToken(token); console.log(userId); // 输出userId loadComments(); function loadComments() { var fruitId = 43; $.ajax({ url: 'comments/byFruitId/'+ fruitId, // 在这里使用fruitId method: 'GET', success: function(data) { let commentsHtml = ''; data.forEach(comment => { commentsHtml += renderComment(comment); }); $('#comments').html(commentsHtml); } }); } function renderComment(comment) { let commentHtml = ''; commentHtml += '

            ' + comment.originalPoster.userId + ': ' + comment.originalPoster.content + ' (' + new Date(comment.originalPoster.postedAt).toLocaleString() + ')

            '; commentHtml += ' + comment.id + '">Reply'; if (comment.replies && comment.replies.length > 0) { commentHtml += renderReplies(comment.replies); } commentHtml += ' + comment.id + '" >'; commentHtml += '
            '; commentHtml += 'Submit'; commentHtml += ''; commentHtml += ''; return commentHtml; } function renderReplies(replies) { let repliesHtml = ''; replies.forEach(reply => { repliesHtml += ''; repliesHtml += '

            ' + reply.userId + ': ' + reply.content + ' (' + new Date(reply.postedAt).toLocaleString() + ')

            '; repliesHtml += ' + reply.id + '">Reply'; if (reply.replies && reply.replies.length > 0) { repliesHtml += renderReplies(reply.replies); } repliesHtml += ' + reply.id + '" >'; repliesHtml += '
            '; repliesHtml += 'Submit'; repliesHtml += ''; repliesHtml += ''; }); repliesHtml += ''; return repliesHtml; } $(document).on('click', '.reply-button', function() { let replyFormId = '#reply-form-' + $(this).data('id'); $(replyFormId).toggle(); }); window.addReply = function(parentId) { let replyContentId = '#reply-content-' + parentId; let content = $(replyContentId).val(); let reply = { userId: userId, content: content, postedAt: new Date().toISOString(), parentId: parentId }; $.ajax({ url: 'comments/' + parentId + '/replies', method: 'POST', contentType: 'application/json', data: JSON.stringify(reply), success: function() { loadComments(); } }); } });

            index.html

            
            
            
                
                果粒优选
                
                
                
                
                
                
                
                
                    body{
                        z-index: -100;
                    }
                    #app{
                        width: 100%;
                        height: 100vh;
                    }
                    #bg{
                        position: fixed;
                        left: 50%;
                        z-index: -50;
                        height: 100vh;
                        width: 1300px;
                        //transform: translate(-50%, 0); /*水平居中*/
                        //background-color: #F2F6FC;
                    }
                
            
            
                
            
            
            
                var userId;
                $(document).ready(function() {
                    $.ajax({
                        url: 'http://localhost:8080/fshop/user/loginUserName',
                        type: 'GET', // 或者 'POST', 'PUT', 'DELETE' 等
                        dataType: 'json', // 预期服务器返回的数据类型,如 'json', 'xml', 'html', 'text'
                        // 如果请求需要发送数据,可以使用 data 属性
                        // data: {
                        //     key1: 'value1',
                        //     key2: 'value2'
                        // },
                        // 如果请求需要认证信息,如设置请求头,可以使用 headers 属性
                        headers:{'token': localStorage.getItem("token")},
                        success: function(response, textStatus, jqXHR) {
                            // 请求成功时调用的函数
                            console.log('请求成功:', response.data.userId);
                            userId = response.data.userId;
                            // 在这里处理返回的数据
                            let ws ;
                            if('WebSocket' in window){
                                console.log("this broswer is  support websocket.");
                                console.log("USERID是:"+userId);
                                
                                ws = new WebSocket("ws://localhost:8080/fshop/testWebSocket/"+userId);
                                ws.onopen = function (){
                                    console.log("用户1已经连接");
                                }
                                // receive message from server
                                ws.onmessage = function (event){
                                    var message = event.data;
                                    // 显示WebSocket接收到的消息到弹框内
                                    $("#message-popup .message-text").text("用户id=1发了条消息,消息是: " + message);
                                    // 可以设置延迟显示弹框,以提供更好的用户体验
                                    setTimeout(function() {
                                        $('#message-popup').removeClass('hidden'); // 显示弹框
                                    }, 2000); // 延迟2秒显示弹框,你可以根据需要调整这个时间
                                    // 自动隐藏弹框(可选)
                                    setTimeout(function() {
                                        $('#message-popup').addClass('hidden'); // 隐藏弹框
                                    }, 5000); // 5秒后自动隐藏弹框
                                }
                                ws.onclose = function (){
                                    console.log("client id= 1" +"has closed, disconnect to server")
                                }
                            }else{
                                console.log("this browser is not support websocket.")
                            }
                        },
                        error: function(jqXHR, textStatus, errorThrown) {
                            // 请求失败时调用的函数
                            console.error('请求失败:', textStatus, errorThrown);
                            // 在这里处理错误情况
                        }
                    });
                    // 监听关闭按钮的点击事件
                    $('#close-popup').click(function() {
                        // 清空p标签的内容
                        $('.message-text').text(''); // 使用text('')来清空文本内容
                        // 同时隐藏消息弹框(如果之前没有隐藏的话)
                        $('#message-popup').addClass('hidden');
                    });
            
                })
            
            
            
            

            index.js

            new Vue({
                el: '#app',
                data() {
                    return {
                        //需要跳转的页面
                        //默认页面
                        slider: './index-slider.html',
                        //首页
                        index: './index-slider.html',
                        //排行榜
                        ranking: './html/fruit/fruit-ranking.html',
                        //当季热卖
                        //活动
                        //领券广场
                        //关于我们
                    }
                },
                methods: {
                    //分类鼠标移入函数
                    handleMouseEnter() {
                        this.$refs.ul_show.style.display = 'block'
                    },
                    //分类鼠标移出函数
                    handleMouseLeave() {
                        this.$refs.ul_show.style.display = 'none'
                    },
                    //点击排行榜页面跳转
                    clickRanking() {
                        this.slider = this.ranking;
                    },
                    //点击首页跳转
                    clickIndex() {
                        this.slider = this.index;
                    }
                }
            });
            let token = localStorage.getItem('token');
            console.log(token);
            let loginIf = document.getElementById('login-if');
            let loginIfTitle = document.getElementById('login-if-title');
            let loginIfBody = document.getElementById('login-if-body');
            let loginIfHistory = document.getElementById('login-if-history');
            if (token != null && token !== '') {
                // 已经登陆,在工具栏显示用户名
                $.ajax({
                    url: '/fshop/user/loginUserName',
                    type: 'GET',
                    headers: {'token': token},
                    dataType: 'JSON',
                    success: function (result) {
                        if (result.code === 1) {
                            loginIfTitle.innerText = result.data.userName;
                            loginIfTitle.setAttribute('href', './html/user/user-evaluate.html');
                            loginIf.classList.add('submenu');
                            loginIfHistory.removeAttribute('hidden');
                        }
                    }
                    });
            } else {
                loginIf.classList.remove('submenu');
                loginIfTitle.innerText = '请登录/注册';
                loginIfTitle.setAttribute('href', 'https://blog.csdn.net/m0_46695127/article/details/html/user/login.html');
                loginIfHistory.setAttribute('hidden', 'hidden');
            }
            //浏览器关闭删除localstroge中的数据
                window.addEventListener('beforeunload', function (event) {
                    var fruitId = localStorage.getItem('fruitId')
                    var fruitCount = localStorage.getItem('fruitCount')
                    var fruitStandard = localStorage.getItem('fruitStandard')
                    if(fruitId != '' || fruitCount != '' || fruitStandard != ''){
                    localStorage.removeItem('fruitId');
                    localStorage.removeItem('fruitCount')
                    localStorage.removeItem('fruitStandard')
                }
            });
            

            index.css

            /* 顶部工具栏 */
            #tool-nav {
                /* 工具栏位置固定 */
                position: fixed;
                z-index: 1000;
                width: 100%;
                height: 50px;
                background-color: rgb(8, 5, 0);
                font-size: 14px;
                color: rgb(194, 191, 191);
                line-height: 50px;
            }
            .tool-nav-li {
                float: left;
            }
            .tool-nav-li a,
            .tool-nav-li span {
                display: block;
                padding: 0 20px;
            }
            .enable-click:hover {
                background-color: #484848;
            }
            /* 可折叠菜单 */
            .submenu-title {
                cursor: pointer;
            }
            .submenu-detail {
                display: none;
            }
            img.submenu-detail {
                width: 85px;
                box-shadow: 10px 10px 10px;
            }
            ul.submenu-detail {
                background-color: #f8f7f7;
                box-shadow: 5px 5px 10px;
                color: #484848;
            }
            ul.submenu-detail li:hover {
                background-color: #dbdada;
            }
            .submenu:hover .submenu-detail {
                display: inline-block;
                position: absolute;
                top: 100%;
                z-index: 1000;
            }
            /* 顶部搜索栏 */
            #head-search {
                position: relative;
                top: 55px;
                height: 140px;
            }
            #head-search>* {
                float: left;
                height: 100%;
            }
            #head-search h1 img {
                height: 100%;
                cursor: pointer;
            }
            #head-search form {
                position: relative;
                width: 600px;
                height: 100%;
                margin-left: 20px;
            }
            #head-search form input[type='text'] {
                position: absolute;
                top: 50%;
                transform: translate(0%, -50%);
                width: 80%;
                height: 45px;
                padding: 0 20px;
                border: 1px solid rgb(255, 119, 0);
                border-radius: 45px 0 0 45px;
                outline: none;
            }
            #head-search form button {
                position: absolute;
                top: 50%;
                right: 0;
                transform: translate(0, -50%);
                width: 20%;
                height: 45px;
                border-radius: 0 45px 45px 0;
                background-color: rgb(255, 119, 0);
                color: white;
                cursor: pointer;
            }
            .btn-normal-designer {
                width: 200px;
                height: 45px;
                border-radius: 45px;
                background-color: rgb(255, 119, 0);
                color: white;
                text-align: center;
                line-height: 45px;
            }
            #head-search form button:hover,
            .btn-normal-designer:hover {
                background-color: rgb(217, 102, 2);
                color: white;
            }
            #cart {
                position: absolute;
                top: 50%;
                transform: translate(0, -50%);
                margin-left: 20px;
            }
            /* 顶部导航栏 */
            #head-nav{
                position: relative;
                top: 50px;
                width: 100%;
                height: 50px;
                background-color: rgb(255, 119, 0);
                color: rgb(231, 231, 231);
                line-height: 50px;
                
            }   
            #head-nav li{
                float: left;
                padding: 0 40px;
            }
            #head-nav li:hover{
                color: white;
            }
            #app{
                width: 100%;
                height: 100%;
            }
            #iframe{
                position: relative;
                top: 60px;
                width: 100%;
                height: 65vh;
            }
            #login-if-body{
                width: 102px;
                text-align: center;
            }
            /*弹框*/
            
            #message-popup {
                position: absolute;
                right: 20px; /* 根据需要调整位置 */
                top: 50px; /* 根据需要调整位置 */
                width: 300px; /* 根据需要调整宽度 */
                background-color: #fff;
                border-radius: 5px;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
                padding: 15px;
                z-index: 1000; /* 确保弹框在其他元素之上 */
                display: flex;
                flex-direction: column;
                align-items: flex-start;
            }
            #message-popup.hidden {
                display: none;
            }
            .message-content {
                display: flex;
                flex-direction: column;
                align-items: flex-start;
            }
            .message-text {
                font-size: 16px;
                line-height: 1.5;
                margin-bottom: 10px;
            }
            #close-popup {
                font-size: 14px;
                padding: 5px 10px;
                background-color: #eee;
                border: none;
                border-radius: 3px;
                cursor: pointer;
                transition: background-color 0.2s ease;
            }
            #close-popup:hover {
                background-color: #ddd;
            }
            

            订单评论

            EvaluateMapper.xml

            
            
            
                
                
                
                
                    select user.user_id, user.user_name, user.user_avatar_url, fruit.fruit_id, evaluate.evaluate_info
                    from user,
                         fruit,
                         (select myorder_id, user_id, fruit_id from myorder where myorder_status = 3 and status = 0) myorder,
                         (select myorder_id, evaluate_info, evaluate_create_time from evaluate where status = 1) evaluate
                    where myorder.myorder_id = evaluate.myorder_id
                      and myorder.fruit_id = #{fruitId}
                    order by evaluate.evaluate_create_time desc limit #{currentPage},#{queryCount}
                
            
            

            EvaluateMapper.java

            package com.fshop.mapper;
            import com.fshop.dto.EvaluateDto;
            import com.fshop.entity.Evaluate;
            import com.baomidou.mybatisplus.core.mapper.BaseMapper;
            import org.apache.ibatis.annotations.Param;
            import java.util.List;
            /**
             * 

            * 订单评价表 Mapper 接口 *

            * * @author dev * @since 2024-04-23 */ public interface EvaluateMapper extends BaseMapper { List getEvaluateInfo(@Param("fruitId") Integer fruitId , @Param("currentPage") Integer currentPage , @Param("queryCount") Integer queryCount ); }

            EvaluateController.java

            package com.fshop.controller;
            import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
            import com.fshop.common.R;
            import com.fshop.entity.Evaluate;
            import com.fshop.service.CommentService;
            import com.fshop.service.IEvaluateService;
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.web.bind.annotation.*;
            import javax.servlet.http.HttpServletRequest;
            /**
             * 

            * 订单评价表 前端控制器 *

            * * @author dev * @since 2024-04-23 */ @RestController @RequestMapping("/evaluate") public class EvaluateController { @Autowired private IEvaluateService evaluateService; // 分页查询 @GetMapping public R getAll(HttpServletRequest request, Integer pageNum) { // 获取token String token = request.getHeader("token"); // System.out.println(token); // System.out.println(pageNum); return evaluateService.getAll(token, pageNum); } // 按ID查询评论 @GetMapping("/{evaluateId}") public R getEvaluateById(@PathVariable String evaluateId) { return null; } // 删除评论 @PostMapping("remove") public R removeEvaluate(HttpServletRequest request, String evaluateId) { // 获取token String token = request.getHeader("token"); //System.out.println(token); // System.out.println(evaluateId); return evaluateService.removeEvaluate(token, evaluateId); } // 添加评论 @PostMapping("save") public R saveEvaluate(Evaluate evaluate,HttpServletRequest request) { String token = request.getHeader("token"); System.out.println("controller层"+token); return evaluateService.saveEvaluate(token,evaluate); } //查询所有评论并返回用户ID、用户名称、用户头像以及用户评论 @GetMapping("getEvaluateInfo/{fruitId}/{currentPage}/{queryCount}") public R getEvaluateInfo(@PathVariable("fruitId") Integer fruitId, @PathVariable("currentPage") Integer currentPage, @PathVariable("queryCount") Integer queryCount){ return evaluateService.getEvaluateInfo(fruitId,currentPage,queryCount); } }

            Evaluate.java

            package com.fshop.entity;
            import com.baomidou.mybatisplus.annotation.IdType;
            import com.baomidou.mybatisplus.annotation.TableId;
            import com.fasterxml.jackson.annotation.JsonFormat;
            import lombok.AllArgsConstructor;
            import lombok.Data;
            import lombok.NoArgsConstructor;
            import java.io.Serializable;
            import java.time.LocalDateTime;
            /**
             * 

            * 订单评价表 *

            * * @author dev * @since 2024-04-23 */ @Data @NoArgsConstructor @AllArgsConstructor public class Evaluate implements Serializable { private static final long serialVersionUID = 1L; /** * 评价ID */ @TableId(value = "evaluate_id", type = IdType.AUTO) private String evaluateId; /** * 用户ID,外键(关联用户表) */ private Integer userId; /** * 评价内容 */ private byte[] evaluateInfo; /** * 评价分数 */ private Integer evaluateScore; /** * 订单ID,外键(关联订单表) */ private Integer myorderId; /** * 评价状态 */ private Integer status; /** * 版本 */ private Integer version; /** * 创建(评价)时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime evaluateCreateTime; /** * 最近更新时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime; private String other1; private String other2; private Integer fruitId; }

            EvaluateServiceImpl.java

            package com.fshop.service.impl;
            import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
            import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
            import com.fshop.common.PageHelper;
            import com.fshop.common.R;
            import com.fshop.dto.LoginUserDto;
            import com.fshop.entity.Comment;
            import com.fshop.entity.Evaluate;
            import com.fshop.mapper.EvaluateMapper;
            import com.fshop.service.CommentRepository;
            import com.fshop.service.IEvaluateService;
            import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
            import com.fshop.util.JwtUtil;
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.stereotype.Service;
            import java.time.LocalDateTime;
            import java.time.format.DateTimeFormatter;
            import java.util.ArrayList;
            import java.util.List;
            import java.util.UUID;
            /**
             * 

            * 订单评价表 服务实现类 *

            * * @author dev * @since 2024-04-23 */ @Service public class EvaluateServiceImpl extends ServiceImpl implements IEvaluateService { @Autowired private EvaluateMapper evaluateMapper; @Autowired private CommentRepository commentRepository; @Override public R getAll(String token, Integer pageNum) { // 解析token LoginUserDto loginUser = JwtUtil.parseToken(token); // 查询所有,user_id等于loginUser.getUserId,且status等于1的评论 QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("user_id", loginUser.getUserId()).eq("status", 1); // 分页,每页显示10条评论 Page page = new Page(pageNum, PageHelper.EVALUATE_PAGE_SIZE); page = baseMapper.selectPage(page, wrapper); if (page != null) { return R.ok("查询成功", page); } return R.error("查询失败"); } @Override public R removeEvaluate(String token, String evaluateId) { // 先查询 LoginUserDto loginUser = JwtUtil.parseToken(token); QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("user_id", loginUser.getUserId()).eq("evaluate_id", evaluateId).eq("status", 1); Evaluate evaluate = baseMapper.selectOne(wrapper); // System.out.println(evaluate); if (evaluate != null) { evaluate.setStatus(0); int update = baseMapper.update(evaluate, wrapper); if (update > 0) { return R.ok("删除成功"); } } return R.error("查询失败"); } @Override public R getById(String token, String evaluateId) { // 解析token LoginUserDto loginUser = JwtUtil.parseToken(token); QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("user_id", loginUser.getUserId()).eq("evaluate_id", evaluateId).eq("status", 1); Evaluate evaluate = baseMapper.selectOne(wrapper); if (evaluate != null) { return R.ok("查询成功", evaluate); } return R.error("查询失败"); } //查询所有评论并返回用户ID、用户名称、用户头像以及用户评论 @Override public R getEvaluateInfo(Integer fruitId,Integer currentPage, Integer queryCount) { Integer preCurrentPage = (currentPage - 1) * queryCount; return R.ok(evaluateMapper.getEvaluateInfo(fruitId,preCurrentPage , queryCount)); } @Override public R saveEvaluate(String token, Evaluate evaluate){ LoginUserDto loginUser = JwtUtil.parseToken(token); Integer tokenUserId = loginUser.getUserId(); Integer userId = evaluate.getUserId(); Comment comment = new Comment(); comment.setId(UUID.randomUUID().toString()); // 生成随机的 `_id` evaluate.setEvaluateId(comment.getId()); comment.setFruitId(43);//假数据 comment.setScore(evaluate.getEvaluateScore()); comment.setStatus(1); // 创建一个OriginalPoster对象并设置其字段值 Comment.OriginalPoster originalPoster = new Comment.OriginalPoster(); originalPoster.setUserId(evaluate.getUserId()); // 假设用户ID是123 originalPoster.setContent( new String(evaluate.getEvaluateInfo())); LocalDateTime now = LocalDateTime.now(); // 对于LocalDateTime,应该直接使用ISO_LOCAL_DATE_TIME DateTimeFormatter isoLocalDateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; String iso8601String = now.format(isoLocalDateTimeFormatter); originalPoster.setPostedAt(iso8601String); // 使用ISO 8601格式的日期时间字符串 comment.setOriginalPoster(originalPoster); comment.setReplies(new ArrayList()); // 确保 `replies` 是一个空列表 commentRepository.save(comment); // var evaluationData = { // //userId: userId, // myorderId: myorderId, // evaluateInfo: evaluateInfo, // evaluateScore: evaluateScore // }; if (tokenUserId == null || userId == null) { // 至少有一个ID是null,因此它们不相等 return R.error("添加失败"); } else if (tokenUserId != null && tokenUserId.equals(userId)) { // 两个ID相等 evaluate.setStatus(1); evaluate.setEvaluateCreateTime(LocalDateTime.now()); evaluate.setUpdateTime(LocalDateTime.now()); evaluate.setFruitId(43); int insert = evaluateMapper.insert(evaluate); if(insert > 0){ return R.ok("添加成功"); }else{ return R.error("添加失败"); } } else { // 两个ID都不为null,但不相等 return R.error("添加失败"); } } }

            IEvaluateService.java

            package com.fshop.service;
            import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
            import com.fshop.common.R;
            import com.fshop.entity.Evaluate;
            import com.baomidou.mybatisplus.extension.service.IService;
            import java.util.List;
            /**
             * 

            * 订单评价表 服务类 *

            * * @author dev * @since 2024-04-23 * */ public interface IEvaluateService extends IService { R getAll(String token, Integer pageNum); R removeEvaluate(String token, String evaluateId); R getById(String token, String evaluateId); //商品详情页获取所有评论 R getEvaluateInfo(Integer fruitId, Integer currentPage, Integer queryCount); //增加评论 R saveEvaluate(String token, Evaluate evaluate); }

            R.java

            package com.fshop.common;
            import com.fasterxml.jackson.annotation.JsonInclude;
            import lombok.Getter;
            import java.io.Serializable;
            // @JsonInclude 保证序列化json的时候, 如果是null的对象, key也会消失
            @Getter
            @JsonInclude(JsonInclude.Include.NON_NULL)
            public class R implements Serializable {
                private static final long serialVersionUID = 7735505903525411467L;
                // 成功值,默认为1
                private static final int SUCCESS_CODE = 1;
                // 失败值,默认为0
                private static final int ERROR_CODE = 0;
                // 状态码
                private final int code;
                // 消息
                private String msg;
                // 返回数据
                private T data;
                private R(int code) {
                    this.code = code;
                }
                private R(int code, T data) {
                    this.code = code;
                    this.data = data;
                }
                private R(int code, String msg) {
                    this.code = code;
                    this.msg = msg;
                }
                private R(int code, String msg, T data) {
                    this.code = code;
                    this.msg = msg;
                    this.data = data;
                }
                public static  R ok() {
                    return new R(SUCCESS_CODE, "success");
                }
                public static  R ok(String msg) {
                    return new R(SUCCESS_CODE, msg);
                }
                public static  R ok(T data) {
                    return new R(SUCCESS_CODE, data);
                }
                public static  R ok(String msg, T data) {
                    return new R(SUCCESS_CODE, msg, data);
                }
                public static  R error() {
                    return new R(ERROR_CODE, "error");
                }
                public static  R error(String msg) {
                    return new R(ERROR_CODE, msg);
                }
                public static  R error(int code, String msg) {
                    return new R(code, msg);
                }
                public static  R error(ResponseCode res) {
                    return new R(res.getCode(), res.getMessage());
                }
                @Override
                public String toString() {
                    return "R{" +
                            "code=" + code +
                            ", msg='" + msg + '\'' +
                            ", data=" + data +
                            '}';
                }
            }
            

            orderReview.css

            body {
              font-family: Arial, sans-serif;
              background-color: #f4f4f9;
              margin: 0;
              padding: 0;
            }
            .evaluation-container {
              max-width: 600px;
              margin: 50px auto;
              padding: 20px;
              background-color: #fff;
              border-radius: 8px;
              box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            }
            h1 {
              text-align: center;
              color: #333;
            }
            .form-group {
              margin-bottom: 20px;
            }
            .form-group label {
              display: block;
              font-weight: bold;
              margin-bottom: 5px;
              color: #555;
            }
            .form-group input, .form-group textarea, .form-group select {
              width: 100%;
              padding: 10px;
              border: 1px solid #ccc;
              border-radius: 4px;
              font-size: 16px;
              box-sizing: border-box;
            }
            button {
              width: 100%;
              padding: 15px;
              background-color: #007bff;
              border: none;
              border-radius: 4px;
              color: #fff;
              font-size: 18px;
              cursor: pointer;
            }
            button:hover {
              background-color: #0056b3;
            }
            

            orderReview.html

            
            
            
                
                
                评价页面
                
            
            
                

            评价页面

            评价内容:
            评价分数: 1 2 3 4 5
            提交评价

            orderReview.js

            $(document).ready(function() {
                // 从 URL 中获取参数并赋值给输入框
                function getQueryParams() {
                    var params = new URLSearchParams(window.location.search);
                    var userId = params.get('userId');
                    var myorderId = params.get('myorderId');
                    if (userId) {
                        $('#userId').val(userId);
                    }
                    if (myorderId) {
                        $('#myorderId').val(myorderId);
                    }
                }
                getQueryParams();
            
              $('#submitBtn').click(function() {
                  var userId = $('#userId').val();
                  var myorderId = $('#myorderId').val();
                  var evaluateInfo = $('#evaluateInfo').val();
                  var evaluateScore = $('#evaluateScore').val();
                  // 这里可以添加对输入的验证代码
                  if(userId && myorderId && evaluateInfo && evaluateScore) {
                      var evaluationData = {
                          userId: userId,
                          myorderId: myorderId,
                          evaluateInfo: evaluateInfo,
                          evaluateScore: evaluateScore
                      };
                      // 发送评价数据到服务器
                      $.ajax({
                          url: 'http://localhost:8080/fshop/evaluate/save',
                          type: 'POST',
                          data: evaluationData,
                          headers:{'token': localStorage.getItem("token")},
                          success: function(response) {
                              if(response.code == 1){
                                  alert('评价提交成功!');
                              }else{
                                  alert("评价提交失败! ");
                              }
                          },
                          error: function(error) {
                              alert('提交失败,请重试。');
                          }
                      });
                  } else {
                      alert('请填写所有字段。');
                  }
              });
            });
            
            

            mongodb

            db.createUser({ user: "abc", pwd: "123456", roles: [{ role: "dbOwner", db: "commentDB" }] });
            
            db.comments.insert({
                "_id": "1",
                "evaluateId": 8888,
                "fruitId": 44,
                "score": 3,
                "status": 1,
                "originalPoster": {
                    "userId": 4,
                    "content": "I'm not satisfied with this product.",
                    "postedAt": ISODate("2023-04-24T10:00:00Z")
                },
                "replies": [{
                    "_id": "2",
                    "userId": 5,
                    "content": "Sorry to hear that, can you please elaborate?",
                    "postedAt": ISODate("2023-04-24T11:00:00Z"),
                    "parentId": "8888",
                    "replies": []
                }]
            });
            
            

            pom.xml

            
            
                4.0.0
                
                    org.springframework.boot
                    spring-boot-starter-parent
                    2.7.6
                     
                
                com.fshop
                fshop-app
                0.0.1-SNAPSHOT
                fshop-app
                fshop-app
                war
                
                    1.8
                
                
                    
                        org.springframework.boot
                        spring-boot-starter-websocket
                    
                    
                    
                        org.springframework.boot
                        spring-boot-starter-data-mongodb
                    
                    
                    
                        org.apache.httpcomponents
                        httpclient
                        4.5.13
                    
                    
                        org.apache.httpcomponents
                        httpcore
                        4.4.15
                    
                    
                    
                        org.springframework.boot
                        spring-boot-starter-web
                    
                    
                        org.springframework.boot
                        spring-boot-starter-tomcat
                        provided
                    
                    
                        org.springframework.boot
                        spring-boot-starter-test
                        test
                    
                    
                    
                        mysql
                        mysql-connector-java
                        8.0.33
                    
                    
                    
                        com.baomidou
                        mybatis-plus-boot-starter
                        3.5.1
                    
                    
                    
                        com.alibaba
                        druid-spring-boot-starter
                        1.2.9
                    
                    
                    
                        org.springframework.boot
                        spring-boot-starter-data-redis
                    
                    
                    
                        io.jsonwebtoken
                        jjwt
                        0.9.1
                    
                    
                    
                        com.alipay.sdk
                        alipay-sdk-java
                        3.1.0
                    
                    
                    
                        org.projectlombok
                        lombok
                    
                    
                    
                        org.springframework.boot
                        spring-boot-starter-amqp
                    
                    
                        com.fasterxml.jackson.dataformat
                        jackson-dataformat-xml
                        2.9.10
                    
                     
                    
                    
                    
                        com.qiniu
                        qiniu-java-sdk
                        7.13.0
                    
                    
                        com.squareup.okhttp3
                        okhttp
                        3.14.2
                        compile
                    
                    
                        com.google.code.gson
                        gson
                        2.8.5
                        compile
                    
                    
                        com.qiniu
                        happy-dns-java
                        0.1.6
                        test
                    
                    
                        junit
                        junit
                        4.12
                        test
                    
                    
                    
                        org.springframework.boot
                        spring-boot-starter-websocket
                    
                
                
                    
                        
                            org.springframework.boot
                            spring-boot-maven-plugin
                            2.7.6
                        
                    
                
            
            
            

            递归评论

            前端

            递归渲染逻辑图

            renderComment(comment)
               |
               |-- commentHtml (包含评论内容和回复按钮)
               |
               |-- if (comment.replies 存在)
                       |
                       |-- renderReplies(comment.replies)
                                   |
                                   |-- repliesHtml (包含每个回复)
                                   |
                                   |-- for each reply in comment.replies
                                           |
                                           |-- replyHtml (包含回复内容和回复按钮)
                                           |
                                           |-- if (reply.replies 存在)
                                                   |
                                                   |-- renderReplies(reply.replies)
            
            comment1
              ├── reply1.1
              |     ├── reply1.1.1
              |     └── reply1.1.2
              ├── reply1.2
              |     └── reply1.2.1
              └── reply1.3
            comment2
              └── reply2.1
                    ├── reply2.1.1
                    └── reply2.1.2
            comment3
              ├── reply3.1
              └── reply3.2
                    └── reply3.2.1
            
            
            渲染主评论:
            调用renderComment(comment),生成主评论的HTML。
            如果该评论有回复,调用renderReplies(comment.replies)进行渲染。
            渲染回复:
            renderReplies(replies)会遍历每个回复,生成每个回复的HTML。
            对每个回复,如果存在嵌套回复,再次调用renderReplies(reply.replies),以此类推,直到没有更多的回复。
            
            function renderComment(comment) {
              let commentHtml = '';
              commentHtml += '

            ' + comment.originalPoster.userId + ': ' + comment.originalPoster.content + ' (' + new Date(comment.originalPoster.postedAt).toLocaleString() + ')

            '; commentHtml += 'Reply'; if (comment.replies && comment.replies.length > 0) { commentHtml += renderReplies(comment.replies); } commentHtml += ''; commentHtml += '
            '; commentHtml += 'Submit'; commentHtml += ''; commentHtml += ''; return commentHtml; } function renderReplies(replies) { let repliesHtml = ''; replies.forEach(reply => { repliesHtml += ''; repliesHtml += '

            ' + reply.userId + ': ' + reply.content + ' (' + new Date(reply.postedAt).toLocaleString() + ')

            '; repliesHtml += 'Reply'; if (reply.replies && reply.replies.length > 0) { repliesHtml += renderReplies(reply.replies); // 递归调用renderReplies } repliesHtml += ''; repliesHtml += '
            '; repliesHtml += 'Submit'; repliesHtml += ''; repliesHtml += ''; }); repliesHtml += ''; return repliesHtml; }

            后端

            递归逻辑主要体现在 addReplyToNestedReplies 方法中:
            方法签名:private boolean addReplyToNestedReplies(List replies, String parentId, Comment.Reply replyToAdd)
            目标:向嵌套回复中添加一个新回复 replyToAdd,其父ID为 parentId。
            遍历当前回复列表 replies。
            如果找到回复的ID等于 parentId,则将新回复添加到这个回复的 replies 列表中并返回 true。
            如果没有找到,递归调用 addReplyToNestedReplies 方法,检查当前回复的嵌套回复列表。
            如果在任何嵌套层次找到匹配的父ID并成功添加回复,则返回 true。
            如果遍历完所有回复及其嵌套回复后仍未找到匹配的父ID,则返回 false
            
            addReplyToNestedReplies(replies, parentId, replyToAdd)
              |
              |-- for (Comment.Reply reply : replies)
                    |
                    |-- if (reply.getId().equals(parentId))
                          |-- reply.getReplies().add(replyToAdd)
                          |-- return true
                    |-- else
                          |-- if (addReplyToNestedReplies(reply.getReplies(), parentId, replyToAdd))
                                |-- return true
              |
              |-- return false
            
            

            前后端

            后端逻辑
            后端代码的核心功能是管理评论和回复,包括添加、更新、删除评论,以及在评论或回复中添加嵌套回复。递归的主要目的是在嵌套层次中查找特定的评论或回复,并在其下添加新回复。
            后端递归逻辑详解
            1.添加回复的方法 addReply:
            addReply 方法负责向特定评论或回复中添加新的回复。
            该方法首先通过 findCommentById 查找目标评论。
            如果找到了目标评论,调用 addReplyToCommentOrReplies 方法将回复添加到该评论或其嵌套回复中。
            如果没有找到目标评论,则递归查找所有评论及其嵌套回复,通过 addReplyToNestedReplies 方法查找并添加回复。
            2.递归查找并添加回复的方法 addReplyToNestedReplies:
            该方法递归遍历回复列表,查找目标回复。
            如果找到目标回复,添加新回复并返回 true。
            如果没有找到,递归调用自身查找嵌套回复,直到找到目标回复并添加新回复,或者遍历完所有回复返回 false。
            3.向评论或其嵌套回复中添加回复的方法 addReplyToCommentOrReplies:
            该方法检查目标评论是否为回复的父级,如果是,则直接添加回复。
            否则,调用 addReplyToNestedReplies 方法递归查找并添加回复。
            前端逻辑
            前端代码负责显示评论和回复,并提供用户交互界面以添加新回复。递归逻辑主要体现在显示嵌套回复时。
            前端递归逻辑详解
            1.加载并显示评论的方法 loadComments:
            通过 AJAX 请求从后端获取指定水果 ID 的评论。
            获取到评论后,遍历每个评论,调用 renderComment 方法生成 HTML。
            2.渲染单个评论的方法 renderComment:
            生成单个评论的 HTML 结构,包括显示评论内容和回复按钮。
            如果评论有嵌套回复,调用 renderReplies 方法递归生成嵌套回复的 HTML。
            3.渲染嵌套回复的方法 renderReplies:
            递归遍历回复列表,生成每个回复的 HTML 结构。
            每个回复也可能包含嵌套回复,因此再次调用 renderReplies 方法生成这些嵌套回复的 HTML。
            4.显示和隐藏回复表单的事件处理:
            使用 jQuery 的 on('click', '.reply-button', function() { ... }) 处理回复按钮的点击事件,显示或隐藏对应的回复表单。
            5.添加回复的方法 addReply:
            从表单获取回复内容,生成回复对象。
            通过 AJAX 请求将回复发送到后端,并在成功后重新加载评论。
            前后端结合的递归逻辑流程
            1.用户交互:
            用户在前端页面点击“回复”按钮,显示回复表单。
            用户在回复表单中输入内容并点击“提交”按钮。
            2.前端处理:
            前端通过 addReply 方法将新回复发送到后端。
            3.后端处理:
            后端接收到回复请求,调用 addReply 方法处理。
            如果目标评论或回复存在,调用 addReplyToCommentOrReplies 方法。
            addReplyToCommentOrReplies 方法通过递归查找嵌套回复,并添加新回复。
            4.前端显示更新:
            后端返回成功响应,前端调用 loadComments 方法重新加载并显示最新的评论和回复。
            loadComments 方法调用 renderComment 和 renderReplies 方法生成嵌套的评论和回复结构,通过递归实现嵌套显示。
            
            递归流程图
            复制代码
            前端:
            - 用户点击“回复”按钮
              -> 显示回复表单
            - 用户输入回复内容并点击“提交”按钮
              -> 调用 addReply 方法
                -> 发送 AJAX 请求到后端
            后端:
            - 接收到回复请求
              -> 调用 addReply 方法
                -> 生成新回复 ID
                -> 查找目标评论或回复
                  -> 找到目标评论
                    -> 调用 addReplyToCommentOrReplies 方法
                      -> 直接添加回复
                      -> 或调用 addReplyToNestedReplies 方法
                        -> 递归查找并添加回复
                  -> 未找到目标评论
                    -> 遍历所有评论
                      -> 调用 addReplyToNestedReplies 方法
                        -> 递归查找并添加回复
            前端:
            - 后端返回成功响应
              -> 调用 loadComments 方法重新加载评论
                -> 调用 renderComment 方法生成评论 HTML
                  -> 调用 renderReplies 方法生成嵌套回复 HTML
                    -> 递归调用 renderReplies 方法生成所有嵌套回复的 HTML
            通过这种方式,前后端结合实现了评论和回复的递归管理和显示,确保嵌套回复可以正确地被添加和显示。
            

0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码