小工具知识网

当前位置: 首页 > 行业知识 > 002 递归评论 mongodb websocket消息推送

行业知识

002 递归评论 mongodb websocket消息推送

2026-10-27 17:26:00 来源:互联网转载

文章目录

  • 商品评论
    • 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")@Datapublic 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;@Servicepublic 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;@Repositorypublic 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;@Configurationpublic 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@AllArgsConstructorpublic 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 */@Servicepublic 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.3comment2  └── reply2.1        ├── reply2.1.1        └── reply2.1.2comment3  ├── 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通过这种方式,前后端结合实现了评论和回复的递归管理和显示,确保嵌套回复可以正确地被添加和显示。

上一篇:服务器怎么开放例外端口?

下一篇:服务器流量是怎么计算的?